4fa6e2105a12d6e17d930bc76d2297dfd19ac924
8
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2f39766528 |
free-recall explain tool: the quizzing-mechanic switch (owner 2026-09-27)
Sycophancy mitigation as a structural commitment: the model writes its full correctAnswer (plus explanation, in that order) BEFORE the learner sees the question; the commitment rides the persisted call args, is revealed on submit, and grades his free text in the model's next reply — the client never grades free recall. - tools.explain setting (segmented 'Quizzing — Multiple choice | Free recall' in Agent settings' Tools group): free arms explain and drops quiz for BOTH agents (prober variant carries no nodeId, same pattern as the probe quiz); mechanics keys never pass filterEnabledTools — quiz stays unwritable (the 2026-09-19 wedge ruling stands) - doctrine lives pi-natively in the tool descriptions (owner ruling — no conditional system-prompt chunks): explain carries the commitment rule; the MC option-building craft moved out of the prompt into the quiz description; shared/20-question-tools.md slims to the quiz-agnostic core - display seams: parseExplainInput (LaTeX-debris restoration included), dock text-mode with an 'I don't know' row (kbd row 0), AnsweredCard reveal (The answer / Why, no verdict chips), [Explain answer] format - phases/plan seams recognize both quizzing tools' nodeId tags; explain answers deliberately feed no ledger evidence (no client grading to key on — a future ruling can parse the model's grade) 1049/1049 tests, typecheck/lint/build green. |
||
|
|
b87a6925af |
Wave A: pi-native tool layer — delete LhTool/toPiTool, TypeBox everywhere, terminate on questions
- src/agent/lh-tool.ts deleted: the zod-wrapped LhTool + toPiTool bridge (z.toJSONSchema per call, clientQuestion flag, hand-rolled result mapping) is gone. Tools are pi-agent-core native AgentTools now. - New src/agent/tool-execute.ts owns what the wrapper added: the abort guard (runWithAbortGuard: run signal + 30s per-call timeout + 250ms grace, 'Operation aborted' / timeout wording preserved), ToolInputError (the retryable '- problem at path' format), result mapping (string verbatim / object JSON / image parts, details rides the record's tool part output), and questionResult() — placeholder + terminate:true (owner Option A: belt of terminate + slim makeQuestionStopper with the clean-question rule; maxTurns deleted from the teaching turn entirely per owner). - All 11 tool files + tools.ts registries converted to TypeBox parameters (typebox@1.3.7, pinned to pi-agent-core's copy). zod refinements (quizInputProblems, askOptionProblems, live-nodeId) moved from schemas into execute — the model sees the same retryable error shape. - question-core + contracts/probe TypeBox-authored (single source shared with the UI); tool-parts parse seam runs the boundary's full chain (Convert → Check → Clean → refinement), fixing the live-smoke lockout: pi's boundary Value.Converts correctAnswer: 17 → '17' but the record keeps raw args, so a parse without Convert rejected a posed quiz (invisible pending question, turn ended). Pinned with the real smoke emission shape. - Subagents converted: demo-builder/researcher AgentTool-native, visual-maker/pdf-reader declaration-only pi-ai Tools with local execute fns, extraction submit schemas re-authored in TypeBox (owner option 2: zod defaults → Type.Optional + fill at the submit seam). - Accepted delta, flagged: TypeBox-native schemas skip pi's JSON-Schema primitive coercion fast path... (boundary still Value.Converts), and prober-quiz unknown keys ride through (Value.Clean strips at display). - Tests: 988 passing (was 967 at the recon baseline); tool-abort and read-tool rewritten against the native seams; question-core/question- tools/tool-parts/demos/web-search/graph-tools/plan-tree/probe-done/ tool-toggles converted to Value.Check/execute pins. Live smoke (PORT=3004, real model): native ls turn streams end-to-end; a real quiz pose (first attempt missing correctAnswer → tool-input-error → model retry → posed) terminates the turn cleanly and persists pending. |
||
|
|
377d4b6548 |
fix: the handoff freeze + the free-form mermaid pass
Two user-reported problems from the muscle-protein-synthesis session, both diagnosed from the live data (chat_mtxeml9e_6118e59f). The freeze: after probe_done, the open session never showed the teacher's wake turn — only navigating away and back loaded it. Not an agent-switch problem: the server-side handoff worked perfectly (write-ahead persist, liveness registry, the sidebar's busy dot saw the wake the whole time). The gap was client attach: the only mid-session wake detector the open page has is the jobs poll, and probe-phase sessions have zero job traffic, so that poll is OFF; the post-turn adoption poll read turnInFlight=false at ~0ms (the wake fires at ~0.75-1.2s, after the pump's grace), adopted, and stopped. Fix: the post-turn adoption effect now keeps a short WATCH WINDOW after adopting (~4s, 8x500ms) — if turnInFlight flips true, it hands off to the existing wake-adoption poll (busy shimmer, adopt at finish). The mermaid failures: four failed plan_init calls, all caused by ONE hidden rule — underscores are forbidden in node ids (mps_translation, net_balance, per_meal). The id regex rejected '_' BEFORE the slug check, so the specific, actionable message never fired; the generic 'can't read the edge — expected a[title] --> b[title]' lied (the syntax was exactly that), and the teacher spent three attempts hunting punctuation it suspected (colons, commas, multi-word labels) — visible in its own reasoning trail. Per the user's principle — enforce only the slugs, free everything else: underscores are first-class slug chars; the id CANDIDATE class is broad so every bad id reaches the specific slug message; chained edges (a --> b --> c) expand to pairs; ==>, -.-> are edge synonyms; any direction header accepted (we layout ourselves); id["title"] quote pairs are stripped. The tool description, zod describe, and the teacher's planning prompt now state the one strict rule instead of the old subset liturgy. Verified: tsc, 305/305 (the exact muscle-protein mermaid is now a passing test), lint, build; real-browser puppeteer smoke on live GLM — the teacher's wake turn appeared in the open page ~50s after the probe concluded with ZERO navigation, verdict gate + first teach check + plan window all live in-page (the single console 404 is the app's missing favicon, environmental noise). |
||
|
|
5653c77a29 |
feat: probe/teach agent split — explicit phase markers, position-only plan map
Diagnosis (2026-09-11 user testing, minimal-effort GLM): sessions stuck on 'plan' forever. Two causes: (1) the three-phase view read the stored tree's currentNodeId, which nothing ever writes post author-once (2026-09-03) — a structural dead seam; (2) the teacher stochastically left quiz.nodeId untagged, and done-by-quiz-result semantics would have falsely completed nodes anyway. The redesign (docs/probe-teach-split.md) abolishes derived occupancy in favor of explicit events: - Two agents over one transcript: createProberAgent (quiz without a nodeId field, ask, probe_done) and createTeacherAgent (full toolset, plan_init onward). The chat-handler construction branch keys on a probe_done part in the transcript. MODELS.prober added. - probe_done: server-side seam tool — carries the learner assessment (strands + free-form edge read), refuses while a question is open or the probe is already concluded, and queues a handoff steer through the generalized wake pipeline (steers now dedupe by id, not jobId; job finalization only for job steers). The teacher auto-wakes and authors the plan; the learner sees the assessment via the probe-done card. - phases: probe ends at a concluded probe_done, plan ends at a successful plan_init — strictly sequential, no skipping, no legacy compatibility (data wiped at the pivot per doctrine). - Plan map = position, not completion: derivePlanStatuses reads only tagged quiz CALLS (state/outcome feed nothing); current = latest tagged node with consecutive duplicates collapsed, visited = earlier distinct stays (a revisit stays in both — current wins at render). PlanTree is structure-only; currentNodeId/doneNodeIds and the dead mutation helpers are gone. No fallback current. - Prompts split per agent (prompts/prober/, prompts/teacher/ over the trimmed shared base); graph-context injection removed entirely (shelved, docs/probe-teach-split.md §5); knowledge-graph context no longer reaches either agent. - Plan blocks are full-only snapshots at plan_init (neighborhood machinery retired; the persistent DAG window is the future UI wave); usage capture unchanged. Gates: tsc clean, 294/294 tests (phases/plan-tree/plan-blocks/question- tools rewritten to the new semantics; probe-done + handoff-wake tests added), lint, build. Live GLM smoke PASS: prober concluded with an honest assessment, teacher auto-woke, authored a 10-node plan with the block attached, then tagged checks on slope-instinct → secant derived current/ visited exactly as designed; grading honest both ways (driver off-by-one proved a wrong answer grades false). |
||
|
|
67fbc0575f |
Author-once plan protocol: single mermaid plan_init + derived statuses + live quiz nodeId validation
- plan_init is now the ONLY plan tool: a mermaid 'graph TD' block parsed by a constrained-subset parser (mermaid-plan.ts) with line-precise errors; the seven mutation/tracking tools are gone by design (2026-09-03 design pass). - Re-call plan_init = sanctioned wholesale rewrite; slugs are permanent. - derivePlanStatuses (contracts/plan.ts): done = passed check tagged with a quiz nodeId; current = open check's node else first topo-ready node — the tracking the teacher no longer declares by hand. - questionToolsFor(ctx) (tools.ts): the quiz schema chains a dynamic plan- nodeId validation against the LIVE record — an invalid nodeId is rejected at the call boundary and the teacher retries mid-turn, before any UI. Static questionTools stay validator-less for the default path. - Prompts rewritten for mermaid authoring + derived position. - Out-of-lane surgical touches (flagged for master review): src/core/question-core.ts (quizInputObject export + nodeId description), src/core/contracts/plan.ts (derivePlanStatuses + doc). |
||
|
|
59e1fee850 |
Turn services + agent DI: chat-handler becomes the composition root (stage 4)
The chat turn is now composed, not implemented, by chat-handler.ts:
- src/server/turn-service.ts: one named, error-contained seam per turn
responsibility — buildTeacherContext (W8 read channel), persistTurn
(canonical state write), spawnTurnJobs (async protocol kick-off),
foldTurnIntoGraph (session→graph consolidation; the extraction-pipeline
stage swaps this body behind the same seam). Each takes its deps
explicitly; failures log one [chat <id>] line and never touch the
response.
- chat-handler.ts wires: storage ports (stores), the job runner, the
teacher tools (ctx = chatId + injected ChatStore), and the services.
It no longer contains any logic beyond request validation.
Agent layer now receives dependencies instead of importing them — a new
lint zone enforces it (src/agent never imports src/server or src/store;
verified to fire both ways):
- runner.ts: createJobRunner({ chatStore, jobStore }) factory returning
the spawn hook + the three job runners; the model-override test seam
stays module-level. Bodies otherwise verbatim.
- plan tools: persist through the ctx-injected ChatStore
(TeacherToolContext = { chatId, chatStore }).
- teacher-agent's dependency-free default registry is now the true
client-side set: questionTools + jobTools (jobTools was ctx-parameter-
shaped but never used its ctx — now honestly context-free). Production
always passes createTeacherTools({ chatId, chatStore }); tests that
drive a bare mock model get the client-side set unchanged.
Zero behavior change: tsc clean, 190/190 tests, eslint clean, production
build clean. Tests inject the same fs ports the chat-handler does.
|
||
|
|
1167157c23 |
Storage ports: ChatStore/GraphStore/JobStore + adapters + core inversion (stage 3)
Per-entity semantic ports (src/core/ports.ts), one adapter family
(src/store/*, fs JSON/JSONL), exactly one composition point
(src/server/stores.ts). Dependency direction is now machine-checked:
adapters implement core interfaces; core never imports an adapter.
The inversion:
- core/graph.ts (emitChat/getGraph/graphSnapshot/foldStaleChats) is now
pure — it takes the storage ports as explicit parameters (GraphDeps).
No store imports; newGraphNodeId is local; node id format unchanged.
- core/graph-context.ts buildGraphContext() no longer defaults to the
live ledgers: the chat-handler hands in getGraph(graphStore) per turn.
- store adapters export exactly one object each implementing their port
(chatStore/graphStore/jobStore); bodies otherwise verbatim.
NodeMapping + SessionCheckpoint moved store/graph -> core/contracts
(they ARE contract shapes); CreateJobInit/JobPatch live in ports.
- titleFromPrompt moved store/chats -> core/contracts (pure helper,
planNodeStatus precedent). Store modules now contain persistence only.
- server/stores.ts is the composition root; server/{chats,graph,jobs}.ts
and chat-handler wire from it; agent (plan tools, sub-agent runner)
call the port objects. The shell surface (@/src/server exports) is
unchanged.
Lint law extended: the whole of src/core is now a purity zone (was a
file glob with a graph/graph-context exemption). Verified it fires on
core->store imports; tsc clean, 190/190 tests, eslint clean, prod build
clean (prerender 6/6). Tests pass deps explicitly — graph.test uses the
same fs-adapter pair stores.ts wires in production; graph-context.test
keeps its in-memory ledgers as a GraphStore implementation.
|
||
|
|
1e18d804fc |
W1: plan-tree tools — pure validated ops, eight server-side plan_* tools, tree persistence, teacher prompt fragment
- src/core/plan-tree.ts: pure tree ops (createTree/addNode/updateNode/removeNode/
addEdge/removeEdge/setCurrent/markDone), DAG invariants (unique ids, existing
endpoints, no self-loops/duplicates/cycles), cascade remove, version bumps on
state changes only (true no-ops for idempotent setCurrent/markDone).
- src/agent/tools/plan.ts: plan_init (refuses re-init), plan_add_node (optional
parents → requires edges), plan_update_node, plan_remove_node, plan_add_edge,
plan_remove_edge, plan_set_current, plan_mark_done. Execute = getChat → op →
saveTree → PlanToolResult with the full updated tree; PlanTreeError and missing
chats become {ok:false, error, tree}.
- src/agent/prompts/10-plan-tree.md: tree-authoring doctrine (when: post-probe;
grain: one reasoning step per node; discipline: set_current on start,
mark_done on demonstrated mastery; freedom: mid-session restructure).
- tests/plan-tree.test.ts: every validation error path, version semantics,
cascade remove, no-mutation, tool round-trips against the real chat store.
|