The Accept/Decline verdict gate is gone (owner ruling 2026-09-26): presenting the plan ends the teacher's turn; the learner's next message is a natural reply. Teaching begins at the first quiz call tagged with a nodeId (probe quizzes carry none) — both folds (view phaseAt3, native phaseAt3Lh) advance on the call itself, and the teach divider renders before that whole assistant block. No backwards compat: verdict markers, the verdict input event, optimistic appends, composer buttons, and projection all deleted; prompts and docs rewritten to the natural flow.
9.2 KiB
The probe→teach agent split & plan-movement semantics
Shared understanding reached 2026-09-11 (user + master session). This is the convergence record — the decided design, with reasoning. It is not a task list; implementation waves are planned against it.
1. Two agents, one conversation
Probing and teaching are structurally so different that they get separate agents: separate system prompts and separate tool sets, each fully fleshed-out with a major focus on its own phase. The split extracts more system-instruction alignment: neither agent carries the other phase's bulk (neither needs the specifics — each gets only a brief summary of the other's philosophy so they share a view of the whole process).
- The prober runs the probing phase: quiz + ask_user_question +
probe_done. Its quiz is a different tool (different schema: nonodeIdfield at all) under the same name, so the model is never shown a field it must not use. No plan tools, no jobs, no demos. - The teacher runs everything from planning onward: full tool set
(teach quiz with nodeId, plan_init, jobs, demos). Constructed the moment the
transcript carries a
probe_done. - The swap happens between probing and planning — the planning phase needs full knowledge of how teaching is done, so the teacher agent starts at planning.
- They share the message history. No fresh start: the history holds
subtleties an assessment summary would lose. History tool parts are
content, not live schemas — the teacher reads the prober's quiz calls as
past conversation; the one foreign tool (
probe_done) reads as a concluded past action, and even a hypothetical re-call degrades to a returned tool-error (the return-to-agent doctrine). - Invisible to the user: same chat, same record, same UI. The agents are constructed per-request by the same composition root; "which agent" is a construction branch, not a handoff.
2. probe_done — the seam tool
The prober concludes explicitly:
- Server-side tool. Input: the learner assessment. Output: ok/error.
- Refuses while a question is open ("resolve the open question first") — same doctrine as nodeId validation.
- Refuses a second conclusion (idempotent: the probe concludes once).
- The assessment is the tool call's input, so it streams to the learner live and is durable in the transcript by the tool result itself. A dedicated assessment card/viewer comes later (UI).
- Assessment shape (v1, refine later): a list of dependency strands + a free-form read of the learner's edge on each. The probe was already a binary search per strand the lesson depends on; the assessment formalizes exactly that. Optional overall summary.
- Side effect: it queues the handoff. When the prober's turn ends, the wake pipeline (the same machinery that delivers sub-agent results and abort steers) fires a server-side turn with a handoff steer — the teacher is constructed (probe_done is in the transcript) and authors the plan. The learner experiences assessment → plan as one continuous flow. If the wake is lost (crash window), the next client message still constructs the teacher — the transcript is the source of truth.
3. nodeId semantics — position, not completion
The old contract read a correct answer on a tagged check as "node done." That is the wrong signal and is abolished. The new contract:
nodeIdon a quiz means: "this question is about this node." Nothing else. It is a position signal, never a completion signal.
- Current node = the node referenced by the most recent tagged quiz call (the call, not the result — any state counts).
- Visited = every node that appeared in an earlier tagged quiz call.
Sticky, monotonic. There is no done ledger and no done bookkeeping of
any kind — everything is inferred from the session history. The stored
tree carries structure only (the author-once protocol taken to its
conclusion:
currentNodeId/doneNodeIdsare removed from the tree). - A node spans multiple turns and multiple checks naturally. Several questions may tag the same node — the map stays put. The teacher moving to a new node is the teacher's judgment that the previous one is finished, regardless of individual check outcomes (a failed check followed by a re-teach and a move-on is still a visited node).
- Failed checks move nothing. Results no longer feed derivation at all.
- Untagged checks are legal ad-hoc checks — they don't move the map (quick comprehension checks don't need nodes). Not every check needs a node. The prompt stresses tagging when teaching a map node: an untagged stream leaves the map frozen (lost signal).
- No fallback current. Before the first tagged check nothing is current. No derived-from-prereqs guess.
- Present-but-invalid ids are still rejected at the call boundary (existing validation, return-to-agent retry).
- Visualization: current node gets a special highlight; visited nodes render visible (not grayed); upcoming nodes gray out. "Where have we been, where are we."
4. Phases — explicit events, no occupancy derivation
The phase chain is event-driven:
probe (prober) — probe_done → planning (teacher) — the first
nodeId-tagged quiz call → teach (teacher)
- The broken derived-occupancy seam (phase-of-tree via
currentNodeIdnothing ever sets — the stuck-on-plan bug) is replaced, not patched. - The verdict gate is DEAD (owner ruling 2026-09-26 — simplified away):
presenting the plan ends the teacher's turn, and the learner's next
message is a natural reply — a change request (revise, re-present, end
the turn again) or the go-ahead. The plan→teach seam is inferred:
the first quiz call tagged with a
nodeId(a question ABOUT a plan node; probe quizzes carry no nodeId) is where teaching begins. The phase fold (phaseAt3) advances there, the teaching divider renders before that whole assistant block, and the live phase indicator may lag until the first node's question is posed (owner-accepted). Mid-lessonplan_initrewrites need no gate at all. - The session picker phase derives from the transcript the same way.
5. No global-graph context in sessions (for now)
Both agents run fully standalone — no knowledge-graph injection into either prompt. The graph-as-session-context idea (W8) is shelved until there's a considered design for what it should actually feed. The global knowledge graph remains what the extraction pipeline builds; nothing in lesson prompts reads it.
6. The plan UI: persistent DAG window (direction)
The injected-blocks approach is superseded: the plan should live in a persistent DAG window in the conversation view (like the background- processes dock): always present, zoomed on the current node, auto-moving as the lesson advances; click → full plan (the existing popup survives as the expanded view). Exact layout/design is refined in the UI session.
The window is LIVE (landed 2026-09-11, one day after the design): bottom-left,
2:1 zoomed viewBox centered on the current node, clamped to the layout, click
opens the popup. The full plan block also still renders in the flow at plan
authoring (the presentation moment; the block sits immediately after the
plan_init call) as a second popup entry point. The neighborhood-block
machinery (frozen fragments) is retired — snapshots can't represent
live-derived movement.
7. Deferred / later waves (designed, not now)
Accept-plan UI— landed 2026-09-11, then removed 2026-09-26 (the verdict death): the seam is inferred from the firstnodeId-tagged quiz call (see §4).- Assessment viewer — the strand assessments displayed to the learner as a proper card (data already durable in the transcript; the plain v1 probe-done card renders the assessment meanwhile).
- Model / thinking control — currently hardcoded per role in
config.tsMODELS. The split gives naturalMODELS.prober/MODELS.teacherentries; runtime control comes after. - Graph context, properly designed — what the global graph should feed into sessions (§5).
8. No backwards compatibility — ever
Legacy sessions are not supported. At major pivots the testing data is
wiped and re-grown from new testing. No compat shims, no legacy marker
fallbacks, no migration of old trees. probe_done is the only teacher
construction key; sessions without it are prober sessions (dead legacy
data is garbage to be wiped, not accommodated).
Diagnosis context (why this pivot)
The 2026-09-11 user testing round (Nietzsche + Lacan sessions, minimal-
effort GLM) exposed: (1) the teacher never tagged nodeId — every quiz in
both sessions was untagged, so nothing ever derived; (2) the phase system
structurally could not leave "plan" — phaseOfTree read currentNodeId,
which the author-once protocol never sets, so no session could reach
"teach"; (3) with result-derived "done," one passed check would have
falsely finished a node. Model-compliance issues (untagged checks,
free-text correctAnswer) are expected to shrink with higher thinking
budgets and runtime model control — but the semantics had to be right
first. Model mistakes are good test material: they exercise the
return-to-agent validation loops.