Commit Graph
813 Commits
Author SHA1 Message Date
Amos 05359301e2 Session picker: phase strips, status dots, live polling
- SessionRow (extracted, exported for tests): leftmost phase strip
  (bookmark tick, per-phase color: probe/plan/teach) + status dot left of
  the title — pulsing amber attention dot when an answer is owed, quiet
  breathing dot while the teacher works, nothing when it's the learner's
  turn. A pending answer outranks 'working'.
- globals.css: phase + attention color tokens (one system shared with the
  upcoming header chip and dividers) and the two dot animations, both
  disabled under prefers-reduced-motion.
- SessionsSidebar polls /api/chats every 2.5s while open (payload is tiny
  now that busy/phase/pendingQuestion come from the list) and pauses while
  the tab is hidden; fetch-on-navigation preserved.
2026-09-01 18:28:35 +03:00
Amos 42c112f8c5 Labels appear all at once: one global zoom gate, no per-node size gating
labelRenderedSizeThreshold gated each node's label by its own rendered
size, so big (taught) nodes showed names several zoom stops before
small (candidate) ones. Zero the per-node threshold and instead toggle
renderLabels from the camera ratio: every name appears at fit zoom or
closer and they all vanish together when zooming further out. Hover
still reveals any node's name regardless of zoom.
2026-09-01 18:25:04 +03:00
Amos 1c3d288d08 Three-phase session status: derived summaries for the picker
- core/phases.ts: Phase3 (probe/plan/teach) + pure derivations —
  phaseOfTree (point-in-time, from the stored plan tree: no nodes → probe,
  no occupied node → plan, occupied → teach) and phaseAt3 (monotonic
  transcript fold for the dividers: failed plan_init never marks a seam;
  phase only advances).
- contracts.ts: ChatSummary gains record-derived phase + pendingQuestion;
  ChatListItem adds liveness-merged busy.
- store/chats.ts listChats computes the record-derived fields (tree +
  pending question scan).
- api/chats route merges busy = turn running || running background job.

Lane crossing granted by master: contracts/store/api (schema arbitration).
Data-only inference: pre-plan planning reads as probe until the plan lands.
2026-09-01 18:24:13 +03:00
Amos 6bfec1555d Graph labels: wrapped names, dark hover pill, node-anchored detail card
- Custom sigma label drawers replace the defaults: every label renders
  the node name word-wrapped under the node (max 170px line, overlong
  words hard-broken, never truncated) instead of one long strip to the
  right of the node.
- Hover swaps sigma's hardcoded white box for a dark rounded pill using
  the app's popover tokens; hover always shows the name, even below the
  zoom threshold.
- labelRenderedSizeThreshold 12 -> 6 (names are short now) and labels
  use the app's body font at weight 500.
- Click card anchors just below the clicked node's viewport position
  (flipping above near the bottom edge, clamped horizontally, measured
  in a layout effect so the flip never flashes); any pan/zoom dismisses
  it via the camera 'updated' event.
2026-09-01 17:55:44 +03:00
Amos cf3225fb23 AGENTS.md: parallel-development workflow section; SCOPE.md untracked 2026-09-01 03:35:10 +03:00
Amos 2e4e43b8c4 Graph nodes gain a name; nodes are objective facts (no learner/session references)
Schema: GraphNode + ExtractedProposition carry 'name' — a short 2-6 word
header, the rendering handle. Node theory made explicit: a node is an
objective world-facing fact; the learner's relationship to it lives
entirely in the event ledger (state labels are derived, never stored).
Extractor + auditor instructions enforce it — misconceptions state the
wrong idea itself as a claim, never 'the learner believes Y'. Legacy
ledger lines without a name derive one from the statement on read.
graph-view: labels use the name; the detail panel shows name as header
and statement as body.
2026-09-01 03:34:47 +03:00
Amos 49a6a6d9a0 Match the pi learning system's tool definitions (quiz/ask)
Compared the port against the original pi setup
(vault/π/learn/.pi: quiz.ts, ask-user-question.ts, teach SKILL.md) —
the pi version never produced invalid correctAnswer values. Root cause
of the port's validation failures: the pi options schema's 'Give each
option a stable value; you reference the correct one by that value in
correctAnswer' sentence was dropped in the port. Without it the model
omits values and must copy a long natural-language label verbatim into
correctAnswer — and instead slugifies it ('cultural-diagnosis' for
'A cultural diagnosis') every turn.

Now matched to pi:
- quiz schema: pi's field order (question, details, options,
  multiSelect, correctAnswer, explanation) and descriptions — options
  carry the stable-value instruction, correctAnswer is phrased as a
  value reference ('the value field of the option you intend'), not a
  copy-exact-or-fail threat. nodeId (lh-only, graph) stays.
- ask schema: dropped the redundant 'mode' param (pi has none; the dock
  already infers from options presence), pi's descriptions.
- quiz tool description: pi's full text (ask contrast, note field,
  dontKnow semantics) minus terminal-only details, plus lh's turn
  protocol sentence.
- system-prompt: fixed the stale 'exactly two tools' line (plan/research/
  demo/visual tools have their own sections).

Not matched, deliberately: tool results stay JSON (pi sends curated
text; formatAnswerMessage exists if we want it later), shuffle param
(lh always shuffles seeded), pi's ask '(Recommended)' label hint.
2026-09-01 03:00:23 +03:00
Amos 7ab444ead3 Fix turn-liveness registry across Next dev module instances (globalThis)
In Next dev (Turbopack), each route compiles as its own entry — a
module-level Map in turn-liveness.ts was a DIFFERENT instance in the page
render, GET /api/chats/[id], and POST /api/chat. The chat route held the
live turn (hence the 409), but the page render read an empty registry:
initiallyInFlight=false → mount continuation fired a send → 409 → the
'Something went wrong' banner on returning mid-turn. The registry now
lives on globalThis (the standard Next singleton channel) — one Map per
process regardless of module instances. Semantics unchanged (TTL leak
guard, begin/end round trip).

Browser-verified end-to-end (headless tester): answer quiz → navigate
away → return mid-turn → graded card (no re-prompt), 'Thinking…' shimmer,
composer gated, no error banner; teacher response auto-delivered when the
background turn finished. Polling GET /api/chats/[id] observed working.
2026-09-01 02:25:54 +03:00
Amos a7ca33548b Sessions keep running across navigation: turn liveness + write-ahead persist
The bug: answering a quiz and navigating away mid-turn, then returning,
re-prompted the same question. Root causes: (1) the answer was durable only
at onFinish — every mid-turn view read the stale pre-answer record;
(2) the server had no notion of a turn in flight; (3) remount treated the
stale record as current, with no re-attach path. Worse, the stale dock let
the quiz be answered again, racing two turns into a last-writer-wins
saveMessages clobber.

Fix (four pieces):
- Write-ahead persist in chat-handler: the request body IS the client's
  canonical state — saved to disk BEFORE the stream starts. The answer is
  durable the instant the request arrives; crashes recover via the existing
  mount continuation.
- Turn liveness registry (src/server/turn-liveness.ts): in-memory chatId →
  in-flight map with a TTL leak guard; beginTurn rejects concurrent turns
  (409) so one turn ever owns a chat. endTurn runs in onFinish AFTER
  persistTurn, so pollers that see not-in-flight always read a finished
  record; a turn-start failure clears the registry (no lockout).
- GET /api/chats/[id] reports turnInFlight; the chat page passes
  initiallyInFlight to the client.
- Client re-attach (components/chat.tsx): while in flight, busy shimmer +
  composer gated + mount continuation suppressed; poll /api/chats/[id] until
  the turn finishes, then syncFromServer adopts the completed transcript.
  Stop stays local-stream only (a server turn has no client request to
  abort).

Verified by scripted-model tests: concurrent turn 409 + registry lifecycle,
write-ahead durability mid-turn, failed-turn lockout guard.
2026-09-01 01:57:13 +03:00
Amos 59fc2ef863 Centralized model config (config.ts MODELS) + extraction seam logging
MODELS in config.ts is now THE single tuning table: model id + OpenRouter
reasoning effort for teacher/researcher/demoBuilder/visualMaker/extractor/
auditor. .env.local is secrets-only (LH_* model vars removed; all sub-agents
previously had NO reasoning setting -> provider-default unbounded thinking,
the extraction token burn). One factory (src/agent/models.ts modelFor) wires
all six call sites; test injection unchanged. EXTRACTION.everyNQuizAnswers
moved to a config literal too. Plus the §5 one-line-per-seam logging in the
extraction pipeline: pipeline start, extractor/auditor start + per-step,
audit kept, merged — previously silent until completion, which made live
runs look like they never fired.
2026-09-01 01:47:47 +03:00
Amos 5dab8d12d4 Quiz hardening: cross-field schema validation rejects ungradeable quizzes
quizInputSchema now enforces (via pure quizInputProblems + superRefine) that
correctAnswer resolves against option values, options are non-empty and
unique, and multi-answer requires multiSelect — schema-valid now implies
gradeable. The AI SDK already returns invalid tool input to the model as a
tool-error part (verified in ai@7 run loop), so the teacher retries in the
same turn. chat-handler onError passes tool-input validation messages
through to the client (masked 'An error occurred.' for everything else).
Covers the Gate 3 GLM failure: correctAnswer slug matching no option.
2026-09-01 01:47:47 +03:00
Amos 83e3ab6180 README final pass (stage 7): align with the as-built architecture
- Commands: add npm run lint (with the import-law note)
- Architecture: one-sentence orientation before the mermaid; split the
  turn composition (src/server: chat-handler) out of the teacher-agent
  subgraph — the diagram now matches the layers
- Layout: add the server route entry points + the server barrel
- Header: quiz outcomes reach the graph through the extraction pipeline
2026-08-31 22:47:05 +03:00
Amos c4bfdb4579 docs: architecture.md becomes the canonical target-architecture spec (stage 6)
Replaces the pre-pivot v0 scaffold doc (preserved in git history) with
the as-built spec: the five layers and their dependency directions, the
machine-enforced import law (four zones + the two documented-only
rules), ports/adapters and the two composition roots, the extraction
pipeline as sole graph writer (division of trust, merger invariants,
probe-attribution resolution, the v1 trigger seam), the pure read
channel, the turn seams, minimal observability, the testing strata, and
the deferred-with-links list. The doc states it describes the system,
not an intention — it changes with the commit that changes the system.

README reference updated (was: 'pre-pivot v0 scaffold — history only').
2026-08-31 22:46:32 +03:00
Amos af90fadcda Extraction pipeline: extractor → auditor → merger — the sole graph writer (stage 5)
The knowledge graph's write channel is now the pivot's target
architecture (vision §1 construction):

- src/agent/subagents/extraction.ts — the pipeline factory
  (createExtractionPipeline({ chatStore, graphStore, extractor?,
  auditor? }); models env LH_EXTRACT_MODEL ?? LH_MODEL):
  * transcriptView(): the compact view both LLM stages see — final plan
    tree with node ids, conversation verbatim, QUIZ lines with outcomes,
    mark_done lines; job tool parts are one-liners, plan ops fold into
    the tree line.
  * Extractor: transcript → SessionFindings (propositions incl.
    misconceptions and prose-only supports edges, attribution for
    UNTAGGED probes, all with verbatim evidence) via one zod-validated
    submit_findings call.
  * Auditor: transcript + findings → the verified subset + drops (the
    faithfulness net) via submit_review.
- src/core/graph.ts — emitChat/foldStaleChats/replayFinalTree retire;
  mergeFindings() replaces them as the pipeline's deterministic apply
  stage: exact/alias identity resolution (one mapping line per (chat,
  graph node)), edge dedupe, and event rebuild FROM THE TRANSCRIPT's tool
  parts — kinds/timestamps/refs/notes deterministic, the LLM's only event
  say being WHICH proposition an untagged probe tested (retroactive —
  the vision's open question #3, resolved structurally: tagged events
  resolve deterministically; attribution rescues untagged and
  tagged-but-unmapped ones; unattributable land null + question text).
  pendingQuizAnswers() is the trigger helper.
- Trigger (turn-service.ts triggerExtraction, replacing the old
  foldTurnIntoGraph body): once a chat accumulates
  EXTRACTION.everyNQuizAnswers (LH_EXTRACT_N, default 3) new answered
  quizzes, fire the pipeline async — single-flight per chat, failure
  checkpoints nothing and re-runs on the next trigger. Deliberately a v1
  placeholder isolated in ONE seam, trigger-agnostic pipeline.
- GET /api/graph is now a pure read (foldStaleChats gone).
- NodeMapping.treeNodeId → sourceId (the mapping key is now the writer's
  local proposition id — tree node when tree-derived); store strict-read
  schema updated. Local ledger data regenerates through the pipeline.
- Tests: graph.test.ts rewritten around mergeFindings (identity,
  attribution paths incl. ghost-rescue, idempotency, cross-session
  provenance, trigger counting), graph-context.test.ts merge section
  converted to findings-driven, NEW extraction.test.ts — transcriptView
  units + the full pipeline with mock models (audit drops, failure
  checkpoints nothing, single-flight). 195/195, tsc/eslint/build clean.

Probe attribution (vision open question): probes attach retroactively by
the extractor against the whole transcript; a pre-tree probe maps to a
proposition the tree only articulates later. Fuzzy/embedding merge
adjudication stays deferred (conservative exact/alias only).
2026-08-31 22:36:41 +03:00
Amos 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.
2026-08-31 22:16:03 +03:00
Amos 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.
2026-08-31 20:00:12 +03:00
Amos e92140f993 Shell/core import law: public API barrels + ESLint enforcement (stage 2)
The shell now imports through exactly two barrels, enforced by
import/no-restricted-paths (npm run lint):

  components/**  ->  "@/src/core"      (pure core API, browser-safe)
  app/**         ->  "@/src/core" + "@/src/server" (server entries)

Zone violations are lint errors with architecture-pointer messages; the
pure-core modules (contracts, question-core, plan-tree, phases, pending,
math-delims, ui-message) may never import agent/server/store.

Supporting moves (direction: IO layers -> core):
- core/index.ts: the core public API barrel
- core/ui-message.ts: moved from src/agent (it was shell-facing all along)
- quiz/ask input zod schemas moved agent/tools -> core/question-core
  (the question contract, alongside the grading logic)
- ChatRecord/ChatSummary moved store/chats -> core/contracts (single schema)
- GraphSnapshot/NodeStateClass moved core/graph -> core/contracts (the
  /api/graph payload IS a contract; the pure renderer needs only these)
- src/server/{chats,graph,jobs}.ts + index.ts: thin server entries for the
  transport layer; GET /api/graph's fold side-effect now lives behind
  graphView() instead of inline in the route

Gates: tsc clean, 190/190 tests, eslint clean (law verified to fire on
store/agent/subpath violations), production build clean.

(Repo repair en route: a git object (app/chat/[id] tree) was missing from
the object store — reconstructed byte-exactly via mktree from its blob;
the broken node_modules that stalled the build was cleanly reinstalled.)
2026-08-31 19:51:17 +03:00
Amos bfb31568fc Purge legacy pre-pivot scaffold (decided: extractor/merger-only architecture)
Delete src/extract/ (pi-session normalize/extract/merge stubs), src/ledger.ts,
src/schema.ts, src/derive.ts, src/queries/, src/cli.ts (npm run ingest/graph/
what-next), data/legacy-graph/, data/agent/ (pi agentDir vestige), and their
config.ts/package.json references.

The retroactive-bootstrap goal is dropped: the live chat store (AI SDK
UIMessages) is the only session format now, and the old pi JSONLs in
data/sessions/ stay on disk, inert. contracts.ts is the single schema.
2026-08-31 19:35:20 +03:00
Amos 8dd14b5235 Housekeeping: next-env.d.ts regeneration + architecture.md model-name tweak 2026-08-31 19:34:37 +03:00
Amos 117c0dba83 README: rewrite for the post-pivot architecture (Next.js + AI SDK)
The old README documented the pre-pivot pi/Fastify/Vite scaffold (npm run
teach, doctor scripts, Fastify+WS server, extensions/) — all removed in the
pivot to standalone Next.js + Vercel AI SDK. New README covers the current
commands, env vars, embedded architecture diagram, key mechanisms
(client-side question tools, plan tree, async sub-agent protocol, live
graph emission), the dormant legacy scaffold, testing, and lineage.

Also commits the pending next-env.d.ts regeneration.
2026-08-31 19:34:37 +03:00
Amos 153aa2e0b8 Add Next.js 16 AI-agent rules (AGENTS.md + CLAUDE.md pointer) 2026-08-29 17:38:13 +03:00
Amos 663778d9a1 Gate 3 results — tree panel + graph-personalized probing PASSED; plan fully executed 2026-08-22 11:48:29 +03:00
Amos 806e1405d5 W8: graph-as-context injection + exact-match merge 2026-08-22 11:41:50 +03:00
Amos 53be8a2834 W7: persistent right-panel plan tree 2026-08-22 11:37:28 +03:00
Amos 8d300b71e0 Roadmap: items 2, 3, 6 marked done (Wave 2) 2026-08-22 11:27:01 +03:00
Amos a097ece4e9 Gate 2 results — full journey PASSED 2026-08-22 11:26:08 +03:00
Amos db833a3537 Wave 2 integration: deps manifest (sigma, graphology, puppeteer-core), test-run emission guard 2026-08-22 10:32:37 +03:00
Amos 8728573253 W4: graph store + emission + home graph view 2026-08-22 10:30:00 +03:00
Amos 9a99b01159 W5: probe/teach phases — divider + collapsed teach checks 2026-08-22 10:26:51 +03:00
Amos 6f1b35b2c2 W6: demo + visual artifacts, verifying builders 2026-08-22 10:24:02 +03:00
Amos ded163aa3f Repoint legacy V0 scaffold to data/legacy-graph — the live store owns data/graph exclusively 2026-08-22 10:11:58 +03:00
Amos 42c553cfac Gate 1 results + roadmap status (plan tree, researcher, presentation all live-verified) 2026-08-22 10:04:11 +03:00
Amos 6a68fcfba1 Wave 2 prep: LedgerEvent.ref, demo bridge protocol, artifact stubs, sendText in ChatActionsContext 2026-08-22 10:03:20 +03:00
Amos 34dee919bb Fix job continuation: setMessages never fires sendAutomaticallyWhen
Gate 1 found the teacher staying silent after a research brief landed.
The SDK only evaluates sendAutomaticallyWhen inside addToolOutput — an
external setMessages (our syncFromServer) updates the UI but never
triggers the continuation. Fix: syncFromServer checks
lastAssistantMessageIsCompleteWithToolCalls and calls sendMessage()
(itself a valid continuation trigger); plus a mount-continuation effect
for the reload case (job fulfilled while no client was open).
2026-08-22 09:58:41 +03:00
Amos c27f7f2f76 Base prompt: mermaid label-quoting rule (live session hit the lexer error) 2026-08-22 09:49:18 +03:00
Amos 2c522116c0 Gate 1 integration: shared streamdown config in reasoning, teaserText flattens \(...\) 2026-08-22 09:42:39 +03:00
Amos ef488224bc W3: presentation fixes — delimiters, line breaks, mermaid
- src/core/math-delims.ts: pure preprocessing for the \(...\)/\[...\] math
  contract — balanced delimiters rewrite to the two-dollar forms remark-math
  parses ($ is never math). Skips code fences/spans, escaped delimiters, and
  leaves unbalanced (mid-stream) delimiters untouched. Display math renders
  as a block: mid-paragraph gets blank-line padding; indented (list items)
  degrades to inline so list continuations don't half-parse.
- message.tsx: transform in the render path (children-memoized); math plugin
  now singleDollarTextMath: false — the currency hazard dies structurally.
  remark-breaks via the cjk remarkPluginsAfter slot (the bare remarkPlugins
  prop would replace the [remark-gfm, codeMeta] defaults).
- mermaid: dark theme + sans font via Streamdown's mermaid prop; dark-styled
  error fallback (components/mermaid-error.tsx) with error, source in
  <details>, and retry.
- globals.css (appended): @source for streamdown dist — its internal UI
  chrome (code blocks, mermaid containers, tables) was entirely unstyled
  because Tailwind v4 never scans node_modules; --color-sidebar → card; wide
  mermaid SVGs capped to the column; .katex-display scrolls.
- tests/render-markdown.test.tsx: 26 tests (transform unit + render). The
  math describe block in render-cards.test.tsx is updated to the new
  contract ($...$ now renders as literal text — pinned).
2026-08-22 09:38:54 +03:00
Amos 866352cb65 Base prompt: accuracy rule now points at the research tool for material facts 2026-08-22 09:37:54 +03:00
Amos df16750560 W2: async job infra + researcher sub-agent
- src/store/jobs.ts: per-job JSON store in data/jobs (create/get/update/list,
  dedup by toolCallId)
- src/agent/tools/jobs.ts: research tool — client-side (no execute), turn ends
  at the call; input per contracts ResearchToolInput
- src/agent/tools/web.ts: server-side web_search (Google CSE) + web_fetch
  (Readability/Turndown/linkedom, Jina fallback) — errors return as strings,
  never thrown into the agent loop. Deps: @mozilla/readability, linkedom,
  turndown (+@types/turndown dev)
- src/agent/subagents/researcher.ts: isolated ToolLoopAgent (web tools only,
  12-step cap), researcher prompt ported from pi
- src/agent/subagents/runner.ts: spawnPendingJobs hook + runResearchJob —
  fire-and-forget, activity line per tool call, fulfills the part in the
  LATEST chat record (output-available) before flipping job status; failure
  still fulfills the part so the teacher learns the outcome
- src/server/chat-handler.ts: onFinish spawn hook after saveMessages
- app/api/jobs/route.ts: GET ?chatId= polling endpoint
- components/artifacts/job-card.tsx + registry entry: polling activity card
  (2s) → syncFromServer on finish; research-brief card (markdown + sources)
- src/agent/prompts/20-jobs.md: research doctrine for the teacher
- tests/jobs.test.ts: store CRUD, spawn hook via scripted mock teacher,
  runner completion/failure with fake model + stubbed fetch
2026-08-22 09:36:15 +03:00
Amos 92fb8e56d8 Base prompt Phase 2: dependency map is built with plan_init, not mermaid 2026-08-22 09:28:25 +03:00
Amos 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.
2026-08-22 09:27:27 +03:00
Amos 6d9ca54d6b Gate sending while a background job part is pending (protocol §5) 2026-08-22 09:16:02 +03:00
Amos 856eb67da1 Base prompt: \(...\)/\[...\] math delimiters, $ literal currency, mermaid encouraged 2026-08-22 09:15:29 +03:00
Amos c55bae0de6 Wave 0: shared contracts + extension points
- src/core/contracts.ts: plan tree / graph / ledger / job protocol / artifact
  registry types — the single source of truth for all workstreams
- tools: per-request factory createTeacherTools({chatId}) composing
  questionTools + planTools (stub) + jobTools (stub); quiz gains nodeId
- prompt composition: system-prompt.md + sorted src/agent/prompts/*.md
- ChatRecord.tree + saveTree; pending gate scoped to quiz/ask (+findPendingJobCall)
- chat.tsx: artifact renderer extension point + ChatActionsContext.syncFromServer
2026-08-22 09:06:24 +03:00
Amos c338f468f8 docs: build plan v1 — waves, gates, worker execution model 2026-08-22 08:41:07 +03:00
Amos 64ea13f0a7 docs: graph-as-session-context, sub-agent architecture, not-a-chat deprioritized
- Every session starts with the learner's knowledge graph as base
  context (precision probing); selection problem noted: pedagogical
  relevance != semantic closeness (structural neighborhood, diagnostic
  hotspots, frontier strip); strategies to tune on data.
- Sub-agent architecture: async background workers with live activity
  indicators; demo-builder TESTS code headlessly; visual-maker verifies
  by looking (port of vault svg-maker); mermaid needs no sub-agent;
  researcher tool for fact verification (port of pi web-search/web-fetch
  + interactive-subagents researcher prompt).
- Not-a-chat presentation principle marked LOW PRIORITY per user.
2026-08-22 08:23:42 +03:00
Amos 588897fba6 docs: refine not-a-chat — spine + interjection cards, anchoring as overlay
Margin model flaws (user feedback): fragment placement needs unreliable
routing; scattering kills the receipt moment and chronology; session
value is in the moment, revision is rare, HTML export deprioritized.
Evolved model: one chronological spine; learner blocks are intact typed
'interjection' cards; teacher replies as woven Q&A interludes
(quote-then-answer); anchoring is a link overlay (backlinks), margins
demoted to a computed view. Chat-ness dies by type asymmetry, not
relocation.
2026-08-22 08:05:02 +03:00
Amos c46e8561c8 docs: presentation principle — it is not a chat
Asymmetry of artifact: the teacher writes the document (whiteboard), the
learner writes in the margins (voice/floor). Bubbles die; the unit is the
block; chronology yields to tree structure; Q&A becomes document
furniture; the composer becomes a response field. Presentation-layer
change only — the interaction model stays conversational.
2026-08-22 07:56:33 +03:00
Amos 3c3deef045 docs: demos as artifacts, dictation/notes direction, structural-identity ideation
- Interactive demos: one format (self-contained HTML/JS in sandboxed
  iframe), demo tool contract (resize bridge, error->fix loop, versions),
  persistence via tool parts; Pyodide deferred, server Python rejected.
- Notes as first-class reasoning channel; dictation requirements
  (AudioWorklet PCM, Deepgram token mint, never-lose-a-dictation) ported
  from the pi dictate extension's proven recipe.
- Structural identity ideation: sectioned lesson-document transcript,
  gated progression with learner-chosen branches, typed blocks, session
  state strip, retrieval-first session opens, durable compiled artifact,
  visible tree edits. Gamification explicitly avoided.
- Roadmap: demo tool and structural candidates slotted; dictation later.
2026-08-22 07:46:11 +03:00
Amos 6e7f118190 docs: graph rendering decision — sigma.js v3 + graphology + forceatlas2
Research verdict: ~40KB gz, synchronous run-N-ticks-then-freeze layout,
labelRenderedSizeThreshold = Obsidian's text fade threshold, near-zero
idle CPU. Obsidian internals confirmed: PixiJS WebGL + d3-force-style
custom sim. Rejected: vis-network, cytoscape, react-force-graph-2d,
cosmos.gl. Pre-impl validation: 2k-node synthetic benchmark.
2026-08-22 07:36:00 +03:00