Part 1 — the ledger + remnants interlude (master-dispatched, owner ruling
2026-09-23: nothing configures the graph beyond chat models):
- The embedding ledger surface is gone: readEmbeddings/writeEmbeddings
ports, the EmbeddingsLedger contract, EMBEDDINGS_FILE (config, store
impls, graphRevision source list), and graph-deletion's vector-pruning
step. On-disk data files stay untouched (inert history).
- The extraction embedder seam dies wholesale: ExtractionPipelineDeps
drops the embedder dep (chat-handler + backfill/import-notes stubs go
with it), the 'embedding' phase leaves ExtractionPhase, the usage
capture/persist helpers die, and stages.embedding leaves
ExtractionRunUsage (historical records keep their shape — reads are
lenient). Extraction is two stages again, like the local-embedder era.
- Consumer-less remnants: latestQuizOutcomes + QUIZ_EVENT_KINDS,
clusterContentId, matchClusterLabelings + Community + ClusterLabeling,
the readClusterLabels/appendClusterLabel port seams + store impls +
LEDGER_FILES.clusterLabels. NamingRunUsage/readNamingRuns STAY
(standing ruling — the cost pill renders historical naming spend);
clusterHue + MIN_CLUSTER_SIZE stay (live).
- The 'labeler' role dies: BACKGROUND_MODEL_ROLES, ROLE_SLOT, ModelRole,
the catalog pin projections, and the UI roster strings. RolePins keys
shrink with the union.
The notes birth fallback (owner-approved 2026-09-23, surfaced by the
interlude's live smoke: production notes imports create provenance-EMPTY
nodes (importNotes passes messages: null — the file's anchor is the
mapping line), so the provenance-only birth rule left every imported
file unclustered and invisible to the prober's session list, against the
locked file-birth ruling. A provenance-empty node now births under its
FIRST mapping line's chatId (ledger order); only notes-born nodes ever
hit the fallback (chat nodes always carry provenance; a re-encountered
node's link-append re-opens the provenance rule). Deletion-safe: mapping
lines of deleted chats are stripped alongside their provenance.
Live-verified on port 3004: a fresh notes import clusters under its
src_ id with the file path as the label, end to end with no embedder.
845/845 tests, typecheck/lint/build green.
EOF
)
- thinking-default.ts: defaultOrderLevel (the ordering's first supported,
the client twin of the server's defaultThinkingLevel) + populateLevel
(an explicit level always rides; the WRITE clamps server-side, bd68486,
and the cross-surface signal re-reads the clamped truth; only absence
populates from the ordering) + THINKING_LABEL shared.
- Setup dialog (item 5): per-slot thinking selectors pre-populated with
the default-order level per picked model, editable; disabled until the
model pick; prefillLevelFromPin keeps a stored level as-is and populates
legacy pins from the ordering.
- Composer + agent section (item 6): fresh model picks always write a
level: the stored one rides, absence populates; the unset state can
never re-enter. Item 4: records with a model but no stored level display
the RESOLVED level (ordering's first supported) instead of a dash, in
the composer pill and the settings slot triggers/menus.
- Item 7 verified live end-to-end: an explicit level rides into the PUT,
the server clamps, the signal re-reads, the pill shows the clamped truth.
- lh:settings-saved CustomEvent (the lh:chat-renamed pattern, owner
ruling): every writer (composer save, SetupDialog save, the settings
write seam — one dispatch point carrying the section) notifies after a
successful PUT; every reader re-reads its own seam: ModelControl's
record read, the Agent section's slots/catalog, and chat.tsx's
configured state (clearing slots mid-session now surfaces the setup
CTA without a reload). Self-notifications re-read idempotently.
- MenuShell now announces lh:menu-open/lh:menu-close (still placement-
only); the settings dialog's content column locks (overflow-hidden,
count-based) while any menu is open — wheel-scrolling with a dropdown
down is dead; the re-place-on-scroll fallback stays for un-lockable
surfaces.
- The settings thinking menus' 'Default' noneRow is retired (owner:
the unset state can't re-enter once chunk 2's auto-populate lands; no
unpin is needed).
- Live-caught regression from the MenuShell migration fixed: the
composer's outside-click handler never learned the portaled menu body
isn't a container descendant — menu-row pointerdowns counted as
outside and ATE THE PICK (composer picks have been dead for pointer
users since 221c99d); rows now carve out via [data-lh-menu], the
settings sections' discipline.
- Item 4 (resolved-level display for legacy records) deferred to chunk 2
per owner ruling — the payload omits the level for legacy no-level
records and DEFAULT_THINKING_ORDER was server-only (now in the core
barrel via bd68486, exactly the seam chunk 2 needs).
- existing sessions: a send that lands the 503 models_not_configured envelope
renders as the SETUP FLOW, never the error banner — the record refetch drops
the refused orphan (the gate refuses before the write-ahead persist), the
draft goes home to the composer, and Save auto-replays it (owner ruling);
parseModelsRefusal reads the envelope from the transport's verbatim error
text (exported pure, tested)
- persistent 'Set up your models' CTA above the chat composer while
slotsConfigured is false (server-read seed via app/chat/[id]/page.tsx) —
legacy per-chat overrides may still send with the pill present; the server
gate stays the truth
- SetupDialog prefill + 'needed' marks (owner ruling): pinned tiers open with
their pick (matched in the federated catalog — lapsed keys don't prefill),
unpinned tiers carry the mark; Save still requires all three;
prefillFromSlots exported pure, tested
- refused jobs read as INFO: dispatch card shows a quiet 'needs model setup'
line + 'Set up models' deep-link (ChatActionsContext.openSetup); dock blobs'
activity reads 'needs model setup' instead of 'failed'; real failures keep
the red tone; isModelsRefusal added to the envelope's home module
- agent-section ModelsBody.slots type honest to the nullable contract
Chunk B (owner-designed, model-slots wave) on the merged engine surface
(main 1d66a17):
AGENT SECTION — SLOTS ONLY (owner ruling)
- The six per-role model rows are GONE; three slot rows instead:
Strong (Prober + Teacher), Cheap (five workhorse roles), Vision
(visual maker + PDF reader), roster + resolved-default in every hint.
- Slot dropdowns share the app's menu language over the FEDERATED
catalog (/api/models = keyed providers only, fresh per call — saving
a key in Keys immediately widens the picker); the vision slot
filters to image-capable models (Model.input); the menu's highlight
now matches provider+id (ids can collide across providers).
- Slots write the new models section (PUT /api/settings/models) through
the dialog's write seam; per-role overrides stay schema-only.
- ModelMenuList rows read 'provider · id' — federation makes the
provider bookkeeping-relevant. Usage stamps render provider-qualified
(prettyModel(qualified): 'Glm 5.3 Flash · openrouter') in the stats
bar's turn lines.
SEARCH ENGINE (search wave: no default, owner)
- A row in Tools: Tavily/Brave via the shared-menu look, Automatic =
the pin removed (auto-activation). The live reason line comes from
/api/search; no engine key at all grays the whole row + disables the
trigger (a keyless choice would only block auto-activation).
FIRST-RUN SETUP FLOW (owner design A: composer CTA + send interception)
- Chats cannot start until all three slots are picked — no defaults
for 33 providers (slotsConfigured: all three slot models required).
- The graph view's new-session dock (home) shows a 'Set up your models'
CTA while unconfigured; submitting a topic opens the SetupDialog WITH
THE DRAFT PRESERVED; Save stores the models section and the queued
message sends. Same interception on /chat/new sends.
- STAGED BY DEPENDENCY (owner: keys first, models after): no keyed
provider → stage 1 deep-links the settings dialog on Keys (open now
takes a section); a saved key reopens at stage 2 on the next submit.
- Stage 2: Strong → Cheap → Vision pickers (federated, vision-filtered,
all three required). Closable without configuring — browsing stays
possible; the gate is the send, not the dialog. Layered Esc: an open
slot menu eats Esc before the dialog.
- Two closure bugs caught live: onDone→start/send captured the stale
pre-save 'configured' (the flow now forces the replayed send); menu-Esc
used to close the whole dialog.
Settings-dialog provider: open(section?) lands on a section (stage 1
deep-link). Tests: composeSlotModel/composeSearchEngine pure, slot SSR
replacing role rows, slotsConfigured/setupStage first-run gates,
provider-qualified prettyModel. Gates: typecheck, lint, 792/792, build.