# 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: **no `nodeId` field 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: > **`nodeId` on 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`/`doneNodeIds` are 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 `currentNodeId` nothing 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-lesson `plan_init` rewrites 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 first `nodeId`-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.ts` MODELS. The split gives natural `MODELS.prober` / `MODELS.teacher` entries; 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.