Commit Graph
151 Commits
Author SHA1 Message Date
amosblomqvist 0c9f82e027 prompts: Voice + Accuracy become shared fragments — one law across every agent (round 13 part A)
- shared/10-voice.md (agent-neutral) + shared/15-accuracy.md (research-tool
  truth contract) compose for teacher, prober, AND practice
- PRACTICE_SHARED_FRAGMENTS grows beyond 40-formatting.md
- practice 00-base.md drops its stale near-copy ("You have no research tool
  here" was false since round 10); the solve-and-shortcut-hunt line folds
  into the problem paragraph
- teaching system-prompt.md slims (Voice/Accuracy sections removed)
- pins updated: practice-agents composition, prompt-overrides segment mirror
2026-10-05 21:44:34 +03:00
amosblomqvist 4f3bbf61d0 practice: untagged problems — target optional, topic sessions work (owner round 12)
A session whose material is not in the map is a SUPPORTED mode now:
target is optional (absent = untagged problem, poses freely), an
explicit [] heals away at the seam (the record's canonical form is
tagged-or-absent), and when targets ARE present the round-9 law is
unchanged — whole-graph validation, names normalized to ids. The
rejection message teaches the exit ('omit target entirely'), so a
model inventing targets for an off-graph topic converges to omitting
on the retry; omitting always works, no brick is possible. Prompt
carries the clause: material in the map → ids; not → omit, problems
stand on their own.
2026-10-05 20:23:48 +03:00
amosblomqvist a1596556df practice: the rarely-needed set — files, research, pdf, map readers (round 10 slice 1)
The practice registry widens beyond problem+conclude: read/ls/find/grep
(the learner-filesystem set, always), research + pdf jobs (research
honestly absent when no engine is live; demo/visual stay teach-only),
and the prober's map readers behind the SAME settings gate as the
prober's (graphToolsEnabled at the composition root). Base prompt
carries the conditional doctrine: reach for them only when the
situation genuinely requires — no propositions → look at the map;
learner references a file → read it; load-bearing uncertainty →
research it. Not toggle-filtered: problem/conclude are the session,
these are exceptions.
2026-10-05 06:49:15 +03:00
amosblomqvist 83dad65481 practice: target-validation pins + the id convention in the base prompt (round 9 slice 3)
Four pins on the mastery-trace contract: a name target normalizes to
the canonical id (case-insensitive, whole-graph set), an id passes
through untouched, an unknown target rejects one-line AND does not arm
the gate (honest activation — the next valid pose goes through), and
the legacy node-source-less wiring stays unvalidated. The base prompt
now states the convention: target takes the proposition ids from the
session context — ids are how problems are traced.
2026-10-05 06:31:22 +03:00
amosblomqvist 0dfb1de246 practice: pose-time target validation — names normalize to ids, unknowns rejected (round 9 slice 2)
problemTool takes the whole-graph node source (chat-handler passes
graphStore.readNodes, read fresh per pose): prepareArguments silently
normalizes name targets to canonical ids so the RECORD carries ids
(the mastery trace groups on them); execute rejects unresolvable
targets with a one-line invalidInputResult — no re-listing, the [id]
proposition list already sits in the agent's context. Validation set
is the whole graph, not the picks: pickless sessions and future
graph-tools poses stay valid. Absent node source (tests) = no
validation.
2026-10-05 06:29:02 +03:00
amosblomqvist 0bd3236af0 practice: ONE send lane — the register is read from the text
Owner round 2 (2026-10-05), Slice H: the Nudge/Commit two-lane buttons
are gone. One Send; Enter sends, Shift+Enter is a newline. Whether a
submission claims an answer is apparent from the thinking itself — the
agent reads it (hint ladder for traces, moves+verdict for claims).

- The thinking input loses the commit field end-to-end: ChatInput,
  parseChatInput, newThinkingMessage (details and the model-facing label
  both collapse to the single 'Your thinking'), the optimistic marker.
- Legacy records' commit flags are deliberately unread.
- Doctrine: the two labeled channels become one read instruction; 'answer
  commits' section retitled 'Claimed answers' — a claim is not a
  progress signal.
- ThinkingCard: one label, collapsed arrival (the unified row).
- The dock placeholder no longer explains lanes that no longer exist.
2026-10-05 03:46:32 +03:00
amosblomqvist 09480979c4 practice: slim the tool schemas — the modelSolution IS the reveal
Owner round (2026-10-05): the problem pose only really needs statement,
mode, target, modelSolution; conclude needs no reveal field.

- problemInputObject drops decisionPoints, expectedWrongMoves, strategy,
  rationale. Halves the pose payload (the GLM incident's failing args
  were 3.3KB — a direct weak-model hardening).
- concludeInputObject drops solutionReveal: the reveal is the
  modelSolution committed at pose time, shown verbatim at conclude.
- Doctrine pass (00-base mechanics, 10-doctrine V.4/V.5): the commitment
  is the solution alone; the reveal instruction points at it.

Fixtures slimmed to the new shapes (flow, agents, heal). ConcludeCard's
render of legacy solutionReveal input stays until the card redesign
lands (Slice C).
2026-10-05 02:22:04 +03:00
amosblomqvist e8fe5da6b1 fix: honest activation + turn-scoped practice gates (the BKT stuck-slot incident)
The bkt-practice record showed ONE turn in which the model concluded
(clean ack) and then every pose died on 'a problem is already active'
until it gave up ('the problem slot is stuck — the conclude calls report
success'). Two flaws, both fixed:

1. GATES READ A FROZEN RECORD. problem/conclude gates re-read the stored
   record per execute — but the record freezes at the turn's write-ahead
   (persistTurn lands only at turn END), so a mid-turn conclude → pose
   pair could never see each other. practiceTools now builds ONE
   turn-scoped gate (createPracticeGate) seeded once from the record;
   the executes flip it. Fresh closure per turn (the agent is built per
   request) — no cross-turn leakage.

2. PHANTOM ACTIVATION. activeProblemOf ignored tool RESULTS, so the
   composer mounted from a pose the gate itself had refused (owner
   ruling: 'either it goes through or it doesn't'). Honest activation:
   a problem activates only on its clean ack; a conclude frees only on
   its clean ack; errored and dangling calls are skipped. Retroactively
   heals the BKT record (all its poses errored, all concludes clean →
   no active problem). pendingProblemDismissalOf follows the same law
   via the shared cleanToolCallIds helper.

Pinned: the mid-turn conclude→pose pair works in one toolset; the gate
re-arms after a successful pose; lone errored/dangling poses activate
nothing; a clean pose under a rejection stays active; an errored
conclude frees nothing.
2026-10-05 01:22:25 +03:00
amosblomqvist 85022ae143 practice hardening: weak-model argument heal + parked dismissals mid-turn
The GLM incident (2026-10-04): glm-5.3 sent mode as a JSON-string-encoded
enum ('"recall"') on 19 consecutive problem calls; TypeBox's error never
named the expected values, so the model re-emitted the identical payload
until the dev server was killed. And mid-turn the learner was trapped:
Esc's question/problem rungs only dismissed — the dismiss route 409'd
against the turn-owned record and nothing could stop the running turn.

- healEncodedArguments (practice.ts, the prepareArguments seam): a STRING
  where the schema expects a literal union / boolean / null / number /
  array is JSON.parse-d and kept when it satisfies the schema. Valid args
  pass through untouched (same reference); unhealable values stay for
  validation to reject honestly. Wired on BOTH problem and conclude.
- dismissal-park.ts: the dismiss route parks a mid-turn dismissal (202);
  the turn's settle path (onFinish, after persistTurn, before endTurn —
  the same ordering law as the record write itself) applies it against
  the persisted record, only if the dismissed thing really is pending.
- confirmAbort: question/problem discard while busy now ALSO aborts the
  in-flight turn; the dialog line says so (busy prop).

No error-result cap (owner ruling). 16 new pins (heal incl. pi-ai
validateToolArguments end-to-end; park registry, apply paths, the 202
route contract).
2026-10-04 23:57:00 +03:00
amosblomqvist edfac20bf3 prompts: the shared formatting guide composes for practice too
The practice agent wrote wrong LaTeX delimiters — 40-formatting.md (the
\(...\)/\[...\) delimiter law, markdown, mermaid) lived in shared/,
which practice deliberately excluded. Owner ruling: formatting applies
in every setting. Practice now composes its own directory plus a
CURATED shared allowlist (40-formatting.md only — the question/
confidence/file mechanics stay out, practice has no such tools); the
composition test pins the appendix and the order.
2026-10-04 23:07:13 +03:00
amosblomqvist 4bea0b46df practice agent: problem + conclude tools, doctrine prompt, handler branch
- tools/practice.ts: problem (terminate pose; commitment hidden in args)
  and conclude (non-terminating; outcome/verdict/reveal/rung/missClass/
  retest), gated by activeProblemOf — pose-while-active and conclude-
  with-nothing-active both return invalidInputResult.
- prompts/practice/00-base.md (voice + accuracy + tool mechanics) and
  10-doctrine.md (the owner's doctrine verbatim; one approved edit —
  §I material arrival generalized). Practice composes its own directory
  ALONE: no teaching base, no shared fragments.
- createPracticeAgent (teacher-agent.ts); chat-handler branch — kind
  'practice' builds the practitioner from turn 1 and skips extraction.
- problemResult/concludeResult acks in tool-execute (conclude text
  single-sourced: CONCLUDE_ACK_TEXT).
2026-10-04 21:47:23 +03:00
amosblomqvist 814c2ad412 delete the batch practice-problems module (superseded by practice sessions)
Owner ruling 2026-10-04: the module is a sunk cost, retrievable from
history if ever revisited. Removed: core practice contracts + pipeline
helpers, the PracticeStore port + adapter + data dir path, server
service + API routes (sets, clusters picker, practice aborts), the
practice pages + dialog/exam/list UI, chain-table's Generate handoff,
sidebar nav, activity-dock practice rows, and the partitioner/
problemGenerator/grader model roles (BACKGROUND_MODEL_ROLES 8 → 5).

Survivors, moved to core/chains.ts (pure + browser-safe): ReasoningChain,
chainsForSelection, formatChainLine — the chain table and graph
emission still consume them.
2026-10-04 21:34:24 +03:00
amosblomqvist a2c1287415 provider-events: raw provider stream recorder (LH_PROVIDER_EVENTS=1)
pi-ai 1.0's onProviderStreamEvent, at the layer our incident class lives
(GLM arg truncation, DeepSeek id resets) — env-gated (owner choice:
no app surface, off by default; raw events carry full prompts), one
JSONL per chat under data/debug/provider-events/ with a 20MB cap and a
run-start marker for record correlation.

Wired at every agent construction: teacher/prober turns (AgentBuildOptions.
chatId), jobs via LoopContext (researcher, demo-builder through
runSubagent; visual-maker + pdf-reader through their streamOnce), and
the extraction pipeline (extractor + auditor keyed by source id).
practice deliberately unwired (sunk-cost module).
2026-10-04 20:21:18 +03:00
amosblomqvist d2d159d8ae isError results replace thrown ToolInputError (pi 1.0 convention)
Authoring refinements (quizInputProblems, nodeId checks, ask/explain
refinements, submit-agent pipeline laws) now return invalidInputResult —
same model-facing failure and retry semantics pi minted from the throw,
but details keeps {tool, problems} as data instead of the throw path's
empty {}. ToolInputError deleted; wire shape unchanged (verified against
pi's own thrown-error conversion in agent-loop).
2026-10-04 20:21:10 +03:00
amosblomqvist 360d564a6c two questions per teaching unit: predict → teach → check (owner 2026-10-03)
The teaching cycle gains its motivation question. The teach quiz schema
now carries a REQUIRED type field — predict (posed before the teaching,
arrives open, the learner predicts the next step) or check (posed after,
arrives collapsed behind the reveal). The prober's quiz stays role-less.

- core: quizInputObject.type union + normalizeQuizType + the reveal law
  as pure quizNeedsReveal; QuestionData.questionType; note → thinking
  rename (legacy reads fold back); readingThinking channel on answers;
  compactAnswerForModel emits thinking / thinkingWhileReading
- tools: descriptions stripped to mechanics — QUIZ_OPTION_CRAFT and
  EXPLAIN_DOCTRINE deleted, doctrine moved to the prompt layer
  (reverses the 2026-09-27 placement, owner 2026-10-03); shared
  quizDescription/explainDescription builders differ only in deltas
- prompts: shared/20-question-tools.md rewritten (option craft,
  transcribed-speech leniency, the two thinking channels); new
  teacher/28-predictions.md (cycle, roles, skip rule, resolution law);
  20-teaching + 30-question-principles scoped to the new taxonomy
- UI: predictions render open with a Prediction eyebrow, verdict shows
  but the explanation tucks behind a toggle; the reading-thinking field
  mounts while a check waits (dictation claims it until reveal);
  answered card separates thinking vs thinking-while-reading blocks
2026-10-04 00:53:29 +03:00
amosblomqvist 3e5b857c63 quick-fixes round: quiz-only teach-check reveal, ESC-abort confirm popup + question dismissal, confidence prompt diet
- requiresReveal gates on pending.tool === 'quiz' (owner: only graded quizzes
  arrive collapsed; ask/explain always open)
- ESC-abort chain opens AbortTurnDialog everywhere (owner ruling); from a
  pending question abort IS dismissal — dismissedAnswer rides the answer
  path, the teacher acknowledges the skip in its next turn; popup-Esc is
  claimed via the state gate (two window listeners fire per Esc)
- 25-confidence.md: stripped the slider/JSON/calibration mechanics — pure
  reading doctrine remains
- toolQuestionKind named once in core (dock parity), dock switched to it
2026-10-03 01:03:29 +03:00
amosblomqvist d6450e260a push architecture slice 5: extraction/practice registries announce activity-changed; dock subscribes (debounced) — the adaptive poll and every chat-view dock nudge retire 2026-10-02 17:31:35 +03:00
amosblomqvist a85087528e tool-result diet: question answers return only learner-added truth (compact JSON for the model, full record in details); dead-code sweep 2026-10-02 04:04:04 +03:00
amosblomqvist 9b9f6372fa quiz answers carry a confidence slider — raw probability, snapped 0/30/50/70/90/100
Owner design round 2026-10-01: every multiple-choice quiz answer now
carries the learner's stated probability that his selected answer is
correct — calibration data for the prober and teacher, the flow itself
unchanged.

Definition (owner ruling, after weighing chance-normalization): RAW
probability. Sub-chance reports are diagnostic gold, not dead space —
'picked an option, believes it wrong' is a real state. The agents get
the sophistication: new shared/25-confidence.md composes for both
agents (chance level, sub-chance = distrusts the pick, correct+low =
fragile, wrong+high = committed misconception, IDK = genuine gap).

- question-core: AnswerRequest/QuestionAnswerData.confidence (quiz
  only, optional — old records valid), buildAnswer clamps and drops it
  on dontKnow, formatAnswerMessage states the line, CONFIDENCE_STOPS +
  DEFAULT_CONFIDENCE_INDEX exported.
- question-dock: snapped slider below 'I don't know', above 'Your
  thinking' — native range over the stop INDEX so ←/→ step the uneven
  grid cleanly, Tab chain rows → slider → thinking, Enter submits from
  the slider too. Rests at 50 and always sends (owner: 50 must mean a
  real coin flip, never 'untouched'). 'I don't know' GRAYS it out —
  layout holds, no value sent.

Deliberately untouched: explain free-recall card (quiz-only scope),
answered-card rendering of confidence, ask flow.
2026-10-02 00:24:05 +03:00
amosblomqvist d8cca61d1d partitioner fix: chain ids lead every rendered line
The partitioner was told to reference chain ids, but no rendered line
ever showed one — chainLine emitted bare 'premises → conclusion', so
the model re-typed whole rendered lines into submit_partition and every
call failed 'unknown chain ids' validation (owner live report,
2026-09-30 evening). The generator's YOUR TARGET block had the same
blindness.

- chainLine now leads with the chain's id: [arg_x] n_a, n_b → n_c;
  a bare proposition repeats its own id ([n_y] n_y (no premises)) —
  mechanically teaching that its chain id IS its proposition id.
- PARTITIONER_INSTRUCTIONS rewritten to chains-primary vocabulary
  (was still 'follows-edges, bijective re-encoding' — pre-chains
  framing) with the id-only doctrine and singular propositions
  first-class.
- partitionSchema/problemSchema gained field descriptions: ids
  VERBATIM, never re-type, never invent.
- Generator OUTPUT bullet + target-block header say the same.

Design ruling en route: partitioner stays an LLM partitioning chains
(deterministic partitioning and propositions-as-targets both
considered and declined by the owner).
2026-10-01 23:56:34 +03:00
amosblomqvist 91341a93da digest-aware serialized extraction + chain simplification replace the spine stage
Owner live-test verdict on the spine (2026-09-30): no perceptible gain —
the real problems (restatement nodes, shallow re-derivations, same
conclusion from many angles) are mint-time issues a post-hoc chain
minter cannot fix. This arc moves assembly INTO the extractor and adds
the deterministic dedupe the owner specified:

- Digest-aware extraction: each stretch's extractor prompt carries THE
  GRAPH SO FAR (its own source's merged propositions + chains, ids
  verbatim); build on existing ids instead of restating, lean on
  established conclusions as premises. Mixed-namespace chain endpoints
  (fresh p-ids OR existing n_-ids) validated at the submit seam.
- Serialized per source: a source's batches/chunks run strictly in
  order (each extract reads every earlier merge); cross-source runs
  stay parallel; source-wide abort stops the loop. The spine stage,
  role, phase, usage stage, cost row, and setup roster are gone.
- Core-ideas prompt: goal reframed as uncovering core ideas and showing
  how everything derives from the foundations; direction upward, prefer
  lean derivations, never re-derive what the graph already holds.
- Chain simplification (merger maintenance): simplifyChains drops
  provably redundant chains at every merge — prefer-fewest-premises +
  ancestry-cover ([A,B]⟹X with [X,C]⟹Y covers [A,B,C]⟹Y, transitive);
  same-key provenance lines drop together; rewriteLedgers under the
  audit lock; droppedChains counted separately in stats.

Chunk budget stays 8192 (owner: keep for now, maybe lower later).
2026-09-30 17:44:37 +03:00
amosblomqvist 52c02533d1 extraction: the spine stage — cross-stretch assembly between extract and audit
Per-batch pipeline is now extract → spine → audit → merge (owner order,
2026-09-30), the spine inside the serialized audit section so it reads
the graph exactly as the following audit will. Measured motive: 0 of 133
chains in the live corpus crossed a stretch boundary — no stage minted
cross-stretch chains.

- src/agent/subagents/spine.ts: SESSION/NOTES spine preambles + shared
  SPINE_CORE (span doctrine; extraction doctrines carried over), the
  submit_spine TypeBox schema, validateSpine (mechanical span law: every
  chain touches >=1 fresh endpoint AND >=1 existing digest endpoint;
  unknown ids, self-reference, and id collisions are loud), the digest
  prompt builder, and the runner (strong tier, retry-only).
- extraction.ts: 'assembling' phase, spine stage in the per-need gate,
  usage/billing stage, assemble() inside withAuditLock for both front
  doors; first stretch of a source skips the call (empty digest, no
  spend); the audit sees COMBINED propositions (chunk + connectives),
  never chains; merger receives the combined findings.
- core/graph.ts mergeFindings: chain endpoints resolve through the
  job-local map OR pass through verbatim as existing n_ ids (the spine's
  mixed namespace).
- EXTRACTION_CORE: steps-of-reasoning selection bar (examples, notation
  glosses, and restatements that no argument needs are not steps).
- Factory chunk budget 4096 -> 8192 (fewer seams, more local context).
- healStringifiedArrays moved to the shared submit seam (submit-agent.ts).
- Cost panel: Assembly row; usage records carry a spine stage (optional
  on legacy records).
- Tests +5: prompt pins, span-law matrix, digest rendering, seeded
  end-to-end flow (spanning chain + connective, verbatim n_ endpoint,
  spine billed as its own stage), non-spanning refused + retried.
2026-09-30 16:38:45 +03:00
amosblomqvist 5ed724c224 extractor: standalone/entailment/selection doctrines in EXTRACTION_CORE
Data analysis of the five-source corpus (490 nodes, 244 chains) found
three pathologies the goal-first core let through:

1. Narrative-adjacency chains — premises that occasioned rather than
   entailed conclusions (concentrated in the polemical sources).
2. Session-scaffolding minted as knowledge — the source's own axiom
   numbering (A1/F2/UT2), arc labels ('the crux') persisted as nodes.
3. Full-discourse volume — glossary and argument-arc material at equal
   rank with load-bearing ideas (~200 nodes per source).

Three doctrine lines, same terse register as the core:
- entailment is the only link that earns a chain; historical/narrative
  connections keep their pieces as bare propositions or drop them
- load-bearing ideas only (what a reference on the subject would teach)
- propositions must stand alone for a reader who never saw the source;
  no leaning on the source's own scaffolding

Substring pins for all three; shared by session + notes extractors by
construction.
2026-09-30 05:36:28 +03:00
amosblomqvist a2cb1447f7 docs: chains-primary reality in architecture.md + README; two stale comments fixed
- architecture.md section 6 rewritten: stored truth = propositions + chains
  (chains.jsonl); deriveView as the lossy image (implication nodes +
  untyped arrows, rebuilt per read); the bijection framing (independent
  arguments for one conclusion stay distinct — the corruption the edge
  store invited); new deletion-semantics paragraph (proposition death
  cascades to every referencing chain; arg_ death = one argument);
  supports retirement stated plainly; auditor's propositions-only scope;
  clusters stated as birth-session partition truth (no Louvain claim)
- section 7 practice chain model: stored-chains inclusion law replaces
  follows-edge grouping; chainsToGraph bijection claim deleted
- README: graph clauses (stored chains, derived view, chain dedupe),
  practice clause, ledger inventory, and two long-stale line-5 claims
  (ledger events, semantic top-K) corrected
- contracts/graph.ts GraphSnapshot comment: Louvain claim replaced with
  birth-session truth; extraction.ts comments no longer narrate the
  dead expandFindings/chainsToEdges seam
2026-09-30 03:27:10 +03:00
amosblomqvist 4985b6f9fa chains-primary: stored chains replace typed edges; edges become a derived view
Owner rulings 2026-09-30: reasoning chains are the stored truth; the graph
canvas renders a lossy derived image. No migration, no backcompat — old
graph data was wiped.

- contracts: GraphChain (arg_-minted id, premises, conclusion) replaces
  typed edges; GraphEdge survives only as the untyped derived arrow;
  GraphSnapshot carries stored chains; supports edges retired entirely
- store: chains.jsonl ledger (edges.jsonl purged at load alongside legacy
  events.jsonl); readChains/appendChain seam; merge dedupes by
  (sorted premises, conclusion), re-assertions append under the same arg_ id
- core: deriveView mints implication nodes (name ⟹, statement via
  formatChainLine) plus premise→imp→conclusion arrows; clusters stay
  proposition-only (birthSessionPartition); chainsForSelection now groups
  STORED chains (chain rides iff every member selected) — independent
  arguments for one conclusion never union; chainsToGraph/chainsToEdges die
- extraction: extractor submits chains natively (expandFindings deleted);
  auditor receives propositions only; supports fan-out gone
- deletion: full death of a proposition kills every referencing chain;
  arg_-prefixed node deletes kill that chain; chainsRemoved stats
- UI: every arrow renders the ⟹ double shaft; supports toggle + thickness
  slider and detail-card edge sections deleted (card keeps name +
  proposition); chain table + practice pipeline read stored chains
- tests: 1188 green across the new seams
2026-09-30 03:22:52 +03:00
amosblomqvist a3aabb3a57 extractor redesign: chains are the deliverable (owner-approved, goal-first) 2026-09-30 02:09:25 +03:00
amosblomqvist 06032769d2 practice: the learner's creation note — optional free-form input carried to partitioner + generators (owner, 2026-09-29)
- dialog: optional 'Anything else?' textarea (typing-only, 2000-char mirror)
- API: body gains note? — trimmed, whitespace-only omitted, hard 400 over 2000
- pipeline: note persisted on the set and threaded to both agent stages
- agents: THE LEARNER'S NOTE prompt block (present only when set) + the
  weighed-never-owed stance pinned in both doctrines
- fix en route: doubled THE PARAMETERS heading in both prompts since slice 3
  (renderParams owns the heading; the literal blocks removed)
2026-09-29 22:16:28 +03:00
amosblomqvist cf0ec4a2f0 practice UI round — top bars + sidebar reopen, one-attempt per-problem submit with instant reveal, score-only grading (owner 2026-09-29) 2026-09-29 21:54:32 +03:00
amosblomqvist ddce919f9b practice live-test fixes — scroll containers, session math delimiters, header guidance, sweep heartbeat (owner live test 2026-09-29) 2026-09-29 21:03:46 +03:00
amosblomqvist 88eb4b2ec0 practice restructure — per-problem jobs + ONE shared background-LLM pool (owner ruling 2026-09-29)
Rulings: each problem generation is its own visible dock job (the
extraction per-chunk pattern), and practice shares extraction's
semaphore — one process-wide 3-permit pool for ALL background LLM
jobs; total background LLM concurrency bounded at 3.

A. SHARED POOL: new src/agent/subagents/bg-llm-semaphore.ts —
   globalThis __lhLlmSlots, PERMIT_COUNT 3 (the ruling), the
   fresh-take/handed-off-permit deadlock discipline preserved for
   BOTH callers. extraction.ts's withExtractSlot is now a thin
   wrapper (job-row signal → withBgLlmSlot); __lhExtractSlots and
   __lhPracticeSlots both die. The partitioner now acquires a permit
   too (it's a background LLM job — it didn't before).

B. REGISTRY ROWS: the aggregate setId:generation / setId:grading rows
   are replaced by setId:partition (phase partitioning, label
   'Partitioning — title'), setId:gen:problemId (queued→generating,
   'Problem i/N — title'), setId:grade:problemId (queued→grading,
   'Grading i/N — title'). Each row carries its OWN abort controller
   and its OWN usage (one PracticeStepLog per row — the aggregate
   partitioner/generator/grader folding dies). Row key = feed id =
   abort key (the dock dup-key law).

C. ABORT SEMANTICS: abortPracticeRow(key) routes by shape — gen row:
   THAT problem only (error 'Aborted', siblings continue, set settles
   ready with one errored problem); partition row: the whole run;
   grade row: that grade drops (answer kept, re-submittable).
   abortPractice(setId) composes from row aborts + the synchronous
   persisted marks (a set-abort's verdict stands before the settles
   re-read it — the settle step keeps an existing error status).
   /api/activity/abort kind practice now takes the ROW key (charset
   admits ':').

D. FEED + DOCK: ActivityPractice/FeedPractice carry per-row identity
   (id = row key, problemIndex/problemTotal); dock blobs render per
   row, labels carry the i/N; the aggregate row is gone.

E. Boot sweep unchanged at set grain; the live-row check is now a
   key-prefix scan (any live row of the set protects it). The
   updatedAt heartbeat covers the new row scheme as-is.

Tests (20 in the pipeline suite): per-problem row shape/uniqueness/
i-N labels (partition row frozen behind a gated partitioner; per-row
usage ISOLATION pin — b's log never shows on c's row); abort-one-gen
(sibling completes, set ready with one errored problem); partition-
row abort; queued grade-row abort (dead-ticket path: answer kept,
re-submittable, sibling lands); shared-pool identity + contention
(practice queues behind extraction-style holders); registry/dock
feed mapping. Test isolation hardened: unique per-store set ids,
afterEach registry/lock/pool reset (a timed-out test can no longer
poison the rest).

architecture.md: jobs-pattern paragraph rewritten (per-problem rows,
shared pool, abort routing).

1149/1149, tsc 0, lint 0, build 0.
2026-09-29 20:42:33 +03:00
amosblomqvist 25cc2a238a practice — formatting doctrine for generator + grader (owner-approved 2026-09-29)
Prior state: zero formatting guidance anywhere in the agent module —
models wrote prose-only by default.

GENERATOR_DOCTRINE Part III gains the FORMATTING bullet (after the
THE PARAMETERS bullet, before PREVIOUS PROBLEMS): statement, solution,
and rubric render as full Markdown — math always LaTeX (inline $…$,
display $$…$$), code/pseudocode/formal systems in fenced blocks with
a language tag, identifiers inline as `code`, tables for parallel
structure, lists for enumerations; prose-only renderings of
mathematical or structured content are a defect.

GRADER_INSTRUCTIONS gains the feedback-formatting paragraph (before
the OUTPUT section): full Markdown, LaTeX math, fenced code, lists/
tables where they carry structure — teach the boundary in properly
formatted prose, not plain text.

PARTITIONER gets nothing (machine-shaped output).

Tests: substring pins on the exported doctrine constants — the exact
FORMATTING sentence + ordering (after presentation, before previous
problems), the exact grader paragraph + before-OUTPUT ordering.
Architecture.md §7 one clause (the formatting doctrine in the
generator's prompt inventory).

1144/1144, tsc 0, lint 0, build 0.
2026-09-29 20:21:12 +03:00
amosblomqvist 8d39f3717c practice bug fix — the boot sweep stamped a LIVE run (owner live test 2026-09-29)
Owner report: created a set, 'Interrupted (server restarted)' showed
on list + set views, generation proceeded and overwrote it. The
sweep's 'generating + no registry row' test had a false-positive
mode: it ran at EVERY pipeline instantiation, and an instance could
evaluate against a registry whose fresh row it couldn't observe.

Three guards, per ruling:

1. HEARTBEAT: ProblemSet.updatedAt? (contract + doc) — bumped on
   every store write; createSet stamps it = createdAt; saveSet
   re-stamps (validator tolerates absence for legacy rows).

2. ONCE PER PROCESS: the sweep guards on globalThis
   .__lhPracticeSweepDone — no instance re-evaluates after the
   process's first sweep.

3. GRACE WINDOW: only sets whose heartbeat (updatedAt ?? createdAt)
   is older than 120s are stamped. A live run saves at every phase/
   problem transition, so it is structurally unreachable; a true
   orphan is minutes stale at next boot. One [practice <id>] log
   line per stamped set (the extraction/jobs console.error idiom).

Tests: stale orphan stamped; FRESH generating set untouched (grace);
once-guard (second factory instantiation never re-sweeps); live-row
protection re-armed via resetSweep() test seam; store pins for
updatedAt birth-stamp + re-stamp + legacy tolerance. Architecture.md
sweep sentence updated (once-per-process + grace semantics).

1142/1142, tsc 0, lint 0, build 0.
2026-09-29 20:19:46 +03:00
amosblomqvist e92679254d practice — discretized parameter axes: named levels, never a number (owner-approved 2026-09-29)
Contracts: LadderRung 0-4, CueDistance 0-2, Presentation theory|
application; PracticeParams carries the three named levels. Doc
comments hold the three definition tables verbatim (rung names +
definitions from the ladder doctrine; cue levels; presentation
levels).

Core: LADDER_RUNGS/CUE_LEVELS/PRESENTATIONS tables in src/core/
practice.ts (definitions verbatim) — ONE source spoken by the agents'
prompts, the dialog's stepped controls, and the exam header; core
barrel re-exports practice.ts (pure domain, browser-safe).

Agents: Part I's ladder intro now 'five rungs, numbered; the request
arrives as a rung' with bullets numbered 0-4 (definitions untouched);
Part III's parameters block replaced with the named-level doctrine
(rung = exact class, no continuum; cue counts lesson examples AND
previous problems' situations; presentation dresses, never changes);
partitioner's RESPECT THE PARAMETERS replaced (low rungs = many small
assignments, high = larger; far cue makes confusables-together MORE
important; presentation does not shape the partition). renderParams
outputs the exact named-level block. Anchor/0..1 language swept dead.

Service + store: strict union validation — floats between levels,
out-of-range, wrong strings, legacy dial shapes all 400 at the API
and fail isProblemSet (legacy sets skipped from lists; no migration —
data/practice is disposable scratch, owner wipes before testing).

UI: dialog = 5-detent ladder slider + 3-detent cue slider (each shows
the level NAME + one-line definition) + Theory|Application segmented
toggle; defaults rung 2 / cue 1 / theory; Create posts the new shape.
Exam header gains the levels line ('Direct exercise · Adjacent
context · Theory').

Tests: renderParams exact-block pin; strict-validation pins (new
tests/practice-service.test.ts — bad unions 400, legacy shape 400);
store pins (legacy floats + 2.5 rung fail); render pins (detents +
names + definitions, defaults, detent-swap, toggle active segment,
levels line); every params fixture moved off floats. 1138/1138, tsc
0, lint 0, build 0.

Docs: architecture.md — discretization paragraph (compact tables,
named-level invocation, defaults, one-table source), UI mentions;
README axes sentence.
2026-09-29 20:06:35 +03:00
amosblomqvist f39e1b4294 practice fix — creation is graph-domain: cluster picker, nodeIds-only API (owner ruling 2026-09-29)
Owner ruling: 'problem sets always take in nodes — graph objects' —
practice is graph-domain, not chat-domain. No session-created sets.

API: POST /api/practice/sets takes {nodeIds, params, title?} ONLY —
the chatId branch, UnknownChatError, and the any-provenance filter are
deleted; the selector is nodeIds (readNodes filtered to the selection,
unknown ids drop, empty live intersection → 400 empty_selection);
title optional from the UI (the picker's cluster label), dated default
stays. chatStore leaves the pipeline deps entirely (labels resolve
store-side in the clusters service).

GET /api/graph/clusters (new): the birth-session groups the graph view
renders — graphSnapshot's clusters verbatim (same ordering, same
MIN_CLUSTER_SIZE presentation filter), mapped to {key, kind
chat|notes, label (null when the naming record is gone — honest,
never guessed), nodeIds, nodeCount}; corrupt/empty graph → empty
list. GraphClusterGroup contract in core/contracts/graph.ts (client-
safe — the dialog imports it).

practice-dialog: session picker → cluster picker (rows show label +
proposition count, notes clusters marked); Create posts the cluster's
nodeIds + label as title + params. 400 empty-selection copy reworded
for the vanished-nodes edge ('Nothing practicable is left in that
cluster — the graph changed.'); 503 deep link unchanged. Preselected
mode gone — the dialog is the only creation entry for now (the graph-
view cluster entry comes later with free-form selection).

Sidebar: session right-click 'Practice this session's propositions'
removed (item + state + dialog wiring); the /practice nav row stays.

Tests: pipeline — chatId/any-provenance tests die, nodeIds selection
pins added (unknown-id filtering, dated-default title, title hint
rides, empty/vanished → EmptySelectionError); render — cluster picker
pins (label+count rows, notes kind, unlabeled cluster muted, empty
graph line), preselected pins removed; 16 render + 13 pipeline.

Docs: architecture.md §7 API+UI paragraph rewritten (graph-domain
ruling, /api/graph/clusters, cluster picker as the only entry, no
404-unknown-chat); README creation-flow line updated.

1130/1130, tsc 0, lint 0, build 0.
2026-09-29 19:26:37 +03:00
amosblomqvist 27c5d9df19 practice slice 4 — pipeline, registry, API, feed
src/agent/subagents/practice-pipeline.ts (sibling of practice.ts —
orchestration vs calls): createPracticePipeline(graphStore+practiceStore
+chatStore+PracticeModels). Fire-and-forget front doors returning
{set, done}: createProblemSet (refuse → resolve selection (chatId =
ANY-provenance membership per brief; nodeIds raw) → chains → snapshot →
createSet partitioning → registry row → runPartitioner → stubs land
(generating) → ONE generator per problem behind the process-wide
3-permit abort-aware semaphore (__lhPracticeSlots, extraction idiom) →
per-problem failure isolation → ready (errored problems included);
abortPractice (idempotent: controllers + 'Aborted' marks; dropped
grades keep answers, re-submittable); submitAnswers (ready-only,
already-graded rejects, one grading job per answer under the same
semaphore); deleteProblemSet (abort-first). Per-set write mutex on
globalThis (__lhPracticeLocks, audit-lock idiom); registry
__lhPracticeRuns one row per set per kind; boot sweep marks orphaned
partitioning/generating sets 'Interrupted (server restarted)'. Typed
errors: EmptySelection/UnknownChat/SetNotFound/SetNotReady/BadAnswers.
Usage folds stage logs into the registry row (v1: completion-folded).

src/server/practice.ts: thin service — strict params parsing, typed
error → response mapping (503 envelope family, 400/404/409), list/get.
Routes: POST+GET /api/practice/sets (202 {id}), GET+DELETE
/api/practice/sets/[id], POST /api/practice/sets/[id]/answers (202).
Composition root: chat-handler wires createPracticePipeline (engineFor
fresh + providerKeyMissing gate) exported as ; barrel re-
exports. Feed: observability activityFeed() += practice rows
(ActivityPractice); dock blobs (process 'practice', GraduationCap
icon, set-keyed abort); /api/activity/abort kind 'practice'.

tests/practice-pipeline.test.ts — 13 tests: end-to-end (stubs mid-
flight via gated generators, coverage complete), failure isolation,
3-permit semaphore pin, abort mid-generation, registry shape pins,
refusal-before-create, chatId any-provenance selection, typed errors,
answers→grades land + graded rejects, boot sweep (orphan + live-row),
delete. 1114/1114, tsc 0, lint 0, build 0.
2026-09-29 18:31:53 +03:00
amosblomqvist da0cd558d0 practice slice 3B — the practice agents: partitioner, generator, grader
src/agent/subagents/practice.ts: createPracticeAgents(deps) with the
ExtractionModels mirror (per-stage engine overrides + engineFor +
providerKeyMissing + refuseIfStagesUnready, all three roles strong-tier).
Generator doctrine = the owner's problem-engineering framework verbatim
(minus Sources) + Part III wiring section per the green-lit amendments
(FULL SET input with minimal-use doctrine; degradation never in
meta-commentary). Partitioner: true-partition doctrine, validate via
chainCoverage + exactly-once + unknown-ids, error-back retries through
runSubmitAgent's new validate hook (ToolInputError → error tool result).
Generator: echo law (chains set === target). Grader: verdict union +
nonempty feedback (schema-enforced). renderChains/renderPropositions/
renderParams/renderPreviousProblems. BACKGROUND_MODEL_ROLES extended with
the practice trio (settings override symmetry). runSubmitAgent gains the
validate seam (null = accept). 16-test faux-engine suite incl. retry
paths, refusal gates, neverSubmitted.
2026-09-29 18:14:30 +03:00
amosblomqvist 00b3c23e06 practice slice 3A — runSubmitAgent extracted to shared module; practice model roles
runSubmitAgent moved verbatim from extraction.ts to src/agent/subagents/
submit-agent.ts (exported; extraction imports it — zero behavior change,
extraction suites green untouched). New optional prepareArguments seam:
extraction wires its healStringifiedArrays heal; default is identity
pass-through. ModelRole += partitioner | problemGenerator | grader;
ROLE_SLOT pins all three to strong (quality-critical trio, plan default).
Note: BACKGROUND_MODEL_ROLES (per-role settings overrides) deliberately
NOT extended — settings-surface decision, flagged for the planning
session.
2026-09-29 17:57:53 +03:00
amosblomqvist 5707672a09 auditor grounding — a prescribed, bounded exploration procedure + born-in find pointers
Observed live: the auditor looped find for a very long time, never touching
list_sessions/list_propositions. Root cause was the prompt: its only
procedure text was 'For each proposition, search the graph (its key words,
its synonyms, its phrasings)' — a per-proposition find loop with no
starting point, no sense of the space's extent, and no termination rule.
And find returned id+name+statement with no session pointer, so results
never composed with the listing tools (owner diagnosis, 2026-09-29).

- AUDITOR_INSTRUCTIONS now prescribes the process: (1) list_sessions ONCE —
  the graph is small and finite, this shows its entire extent; (2) read
  plausibly-related sessions with list_propositions (full text) — every
  realistic candidate seen in a few calls; (3) find as the targeted sweep;
  (4) SUBMIT once related sessions are read — never re-search seen ground
- find hits now carry 'born in: <session id>' — results point at sessions
  the auditor can read whole (legacy no-provenance nodes name none)
- identity doctrine untouched: equivalence bias, linkTo mechanics,
  verdicts-only deliverable
2026-09-29 04:10:38 +03:00
amosblomqvist 86ba2804d1 extractor prompt rewrite — reasoning skeleton, not lossless archive (owner, 2026-09-29)
Diagnosis on tonight's data (4 sources, 173 nodes, 216 edges): supports is
62-83% of edges, topology is hub-and-spoke (34/94 libertarianism nodes are
degree-1 leaves), hub edges read as essay-flow not logic, 33-41% of nodes
are degree<=1 across sources. The old doctrines asked for exactly this:
'every distinct proposition' + 'lonely nodes = failed extraction' +
'COMPLETENESS... omitting is the main way extraction fails'.

Owner rulings, prompt-only (data model untouched, both prompts):
- strict reasoning bar: a proposition earns its node by being a premise,
  step, or conclusion of an argument the source actually makes; isolated
  facts, name-drops, asides, trivia, example enumerations are dropped
- supports tightened: ONLY explicit grounding acts in the text, rare
  exception, genuinely argumentative relations are almost always follows
- completeness inverted: precision over recall, WHEN IN DOUBT OMIT,
  never connect merely-related propositions
- MUST-connect clause dropped; corrections of misconceptions stay in
  (argumentative by nature)

Identity doctrines untouched (established-terminology names, MERGE YOUR
OWN OUTPUT, aliases) — zero near-duplicate statements proves they work.
2026-09-29 03:47:07 +03:00
amosblomqvist 8355fbabdb delete the mappings ledger — per-job in-memory resolution + notes fingerprint guard
The persisted (chatId, sourceId)→node table was a shared namespace across
independent chunk/batch jobs: each chunk's extractor mints local ids fresh
(p1..pN), so later chunks collided with earlier ones — propositions silently
skipped as 'already resolved' (coverage checkpointed anyway: permanent silent
loss) and edges resolved onto the wrong nodes (the 'everything links to
everything' mush). Owner ruling 2026-09-29: mergeFindings resolves THIS job's
ids in memory, nothing persists between jobs; cross-job identity is the
auditor's linkTo verdicts alone.

- contracts: NodeMapping dies; NotesSource.contentFp — sha256 of the text at
  the last run, the re-import guard (unchanged re-import = TRUE no-op, no
  model calls, no usage; changed text re-runs; legacy unstamped sources
  re-run once)
- merger: job-local resolution map, in-submit duplicate ids skip once,
  edges resolve extractor-local ids only
- ports/store: appendMapping/readMappings, LEDGER_FILES.mappings,
  rewriteLedgers.mappings gone (stale data/graph/mappings.jsonl just stops
  being read)
- session-clusters: birthSessionOf is provenance-only — the legacy
  mapping-line fallback for provenance-empty notes nodes dies
- deletion: no mapping rows to strip; stats lose mappingsRemoved
- replay-layout batches nodes by first provenance point
- graph.test pins the new independence semantics (re-merge re-creates;
  duplicate ids within one submit resolve once); extraction tests pin
  per-chunk merges + the fingerprint guard
2026-09-29 02:25:32 +03:00
amosblomqvist 3d9d20794c Auditor submits links ONLY — the edges argument is gone (owner, 2026-09-28 evening)
Hypothesis under test by the owner: coherent extractions turn to mush
after merging because the auditor minted too many bridging edges of its
own — inference, not identity, and out of the auditor's scope. The graph
now connects across sources solely through identified equivalences;
relations between propositions come from the extractor alone.

- auditSchema drops 'edges' (submit_review = links only); the submit-seam
  required-field doctrine reworded per tool
- AUDITOR_INSTRUCTIONS: the BRIDGING EDGES section dies; deliverable is
  the identity verdicts only
- AuditVerdicts drops edges; ExtractedEdge endpoints are extractor-local
  ids only; mergeFindings stops consuming verdict edges and its resolve()
  drops the raw-node-id branch (dead — the extractor never sees graph ids)
- extractor untouched: submit_findings keeps propositions + follows/
  supports edges, required as before
- tests: 17 submit fixtures + 9 typed verdict literals slimmed to links
- docs: architecture.md division-of-trust + pipeline diagram updated
2026-09-29 01:16:32 +03:00
amosblomqvist d047e7b2dc Per-chunk jobs: parallel extracts under a 3-slot semaphore, serialized audits (owner 2026-09-28)
Every notes chunk and chat coverage batch is its own job — registry row,
usage record, failure domain. Extracts fan out process-wide behind a
3-permit extract semaphore on globalThis (new 'queued' phase); audits and
merges stay serialized behind the existing FIFO gate ('waiting'), so each
auditor explores the graph after every earlier job's merge.

- ExtractionPhase gains 'queued'; RunningExtraction gains part {i,n};
  the feed shows one row per job with a '· i/N' suffix (status route)
- abortExtraction is source-wide: every job of a fan-out dies together;
  merged jobs keep their coverage checkpoints
- failure isolation at the fan-out (Promise.allSettled + aggregation):
  a failed chunk no longer kills its siblings — the run returns what
  actually merged; job-level aborts log, pre-fan-out errors still throw
- usage ledgers: one record per job (source-keyed), failed jobs bill
  their partial spend
- runJob/aggregateJobs helpers; setPhase/recordStep/extract/audit/
  notesExtract key the registry by jobKey (src#i) — notesExtract's
  signal lookup was the one real bug the new tests caught (source-id
  lookup meant fan-out aborts never reached the model call)
- semaphore is handoff-correct (fresh takes increment; handed-off
  permits don't double-count — an inflated counter deadlocks at 3)
- tests: isolation pins rewritten (stage failure now resolves zeros),
  single-flight/registry tails updated, per-job usage counts, and a new
  describe pinning the fan-out: 3 extracting + 5 queued, parts 1..8,
  source-wide abort, per-job usage on failure, extract peak == 3,
  audit peak == 1
- docs/architecture.md: serialized-audit paragraph rewritten for
  per-chunk jobs; two-front-doors wording aligned
2026-09-29 00:34:21 +03:00
amosblomqvist 1f5e21a674 teacher-prose-only extraction view: coverage, fingerprints, tokens, and batches derive from the teacher's text blocks alone (owner 2026-09-28) 2026-09-28 22:37:18 +03:00
amosblomqvist c17405a3d7 chunk notes like chats; both submit tools require edges/links (owner 2026-09-28) 2026-09-28 21:28:39 +03:00
amosblomqvist f11b1664dc verdicts-only auditor: submit_review is {links, bridging edges} — the findings echo dies
Owner ruling 2026-09-28: the auditor has no control over the
extractor's output and never alters it, so re-emitting the full
findings per submit was pure payload cost — the retry collapse the
schema slim attacked, halved again. submit_review is now links
(local proposition id -> existing graph node; EQUIVALENCE BIAS
carried in the linkTo description) + bridging edges ONLY; the
merger joins extractor findings + verdicts.

mergeFindings(chatId, findings, verdicts, ...) — propositions come
from findings verbatim, links join by id, edges emit extractor's
then auditor's through one dedup-preserving emitter. The old echo
form hid an accidental drop channel: an auditor that omitted a
proposition from its echo silently dropped it from the merge.
Verdicts-only removes it structurally — extractor output always
fully merges (tests updated to full-merge expectations).

AuditResult/AuditedProposition -> IdentityLink/AuditVerdicts;
audit() logs links/bridges; batch view renders anchorless like
transcriptView; auditor prompt rewritten to the verdicts-only
deliverable; architecture.md division-of-trust updated.
2026-09-28 19:22:58 +03:00
amosblomqvist bd9694b0e4 slim the submit schemas: evidence and messages die; stringified-payload heal at the pre-validation seam
OpenRouter log diagnosis (owner, 2026-09-28): extraction time was
dominated by tool-validation retries. The failing call had the whole
propositions array emitted as ONE JSON-STRING ('propositions.0: must
be object'), a truncated placeholder artifact inside it, and
word-salad statements late in the array — payload-size collapse.
Field audit: 'evidence' was never persisted anywhere and its
faithfulness-checking consumer died with the fully-trusted ruling;
'messages' fed messageId granularity that NOTHING downstream reads,
and the model sent [] anyway (chat-level provenance in practice).
'aliases' stays (optional now): the auditor's find tool globs them.

Schema: propositions = {id, name, statement, aliases?}; edges =
{from, to, type}. Prompts lose the evidence/messages doctrine and
[id]-anchor guidance, gain a compact faithfulness line;
transcriptView renders anchorless. The auditor echo instruction
drops the evidence/messages mention, so its payload halves too.

Heal: healStringifiedArrays on the submit tool's prepareArguments
(pi-agent-core's sanctioned pre-TypeBox shim) parses a stringified
propositions/edges back into arrays — the LaTeX-restore doctrine:
heal weak-model wire behavior at the seam. Test drives the real
agent-loop seam with a stringified submit.
2026-09-28 19:04:39 +03:00
amosblomqvist 46e128d46c serialized audit: the audit+merge critical section is process-wide, one run at a time (owner 2026-09-28)
Extraction may run in parallel across sources (extractors are
source-local and graph-blind), but the auditor explores the LIVE
graph through its tools and stamps identity verdicts against what
it finds — two concurrent audits would each link into a graph the
other is about to change, the structural mint of duplicate nodes.

withAuditLock: a FIFO promise-chain mutex on globalThis (the same
module-instance idiom as the run registries — a module-level lock
would be per-route-instance and useless in dev). The check runs
before any LLM is invoked or the graph read; the lock spans audit
through merge inclusive (the merge is the graph mutation) and is
reacquired per batch, so interleaved multi-batch runs each audit
against the latest graph instead of starving each other. Applied at
both front doors — chat batches and notes imports.

New 'waiting' phase (extracting → waiting → auditing → merging):
visible in the status feed so a queued run never looks stalled in
extracting. Abort-aware on both sides of the queue: a run aborted
while waiting leaves without auditing (its chain link still
releases — successors never hang); a run aborted mid-audit releases
in finally.

Tests pin: a second run queues at the gate and its auditor is
invoked only after the first run's merge is in the ledgers (the
post-merge-graph invariant itself); a run aborted while waiting
never audits, checkpoints nothing, and leaves a healthy chain for a
third run. Gates: typecheck, 1061/1061, lint, build.
2026-09-28 18:00:56 +03:00
amosblomqvist fc8d91a043 first live-extraction findings: fix backwards edge-section labels; extractor naming/self-merge/completeness doctrines
Owner's first live test of the relinked pipeline surfaced three issues,
diagnosed against the ledgers:

1. Detail-card edge labels were backwards (UI only — data and canvas
   arrows were correct). Semantics pinned 2026-09-28: an edge A→B
   means A implies B (follows) or A supports B (supports). Outgoing
   edges now list under 'What follows from this' / 'What this
   supports', incoming under 'Follows from' / 'Supported by'.
   Verified against the ledger: Invariance --supports--> Twin
   paradox / Relativity of simultaneity; Argumentation ethics
   --follows--> Homesteading — arrows correct, only the words were
   swapped. docs/architecture.md wording fixed to match.

2. Session-local terminology leaked into node names ('Criterion C',
   'Action axiom (F1)'). Both extractor prompts gain a naming
   doctrine: names use established, discipline-canonical terminology
   (glossary-headword test); never source-local bookkeeping (letters,
   numbering, positional labels); local shorthand goes to aliases.

3. Near-duplicate propositions and lonely nodes (~40% of fresh-run
   nodes edge-less; 'Argumentation ethics' vs 'A priori of
   argumentation' duplicated despite the auditor having linked that
   phrase once). Root cause is upstream of the auditor: the extractor
   had no self-merge or edge-completeness duty. Both prompts gain:
   MERGE YOUR OWN OUTPUT (same claim at different precision = one
   proposition, phrasings to aliases; nothing downstream dedupes) and
   edge COMPLETENESS (a proposition that arose as evidence for,
   consequence of, or prerequisite of another must carry the edge —
   a graph of lonely nodes is a failed extraction).

Owner rulings on the open question: keep two edge types (no
'relates to' junk drawer); re-measure loneliness after these fixes.

Gates: typecheck, 1059/1059, lint, build.
2026-09-28 17:48:51 +03:00
amosblomqvist 1b50013551 extraction cleanup: events ledger dies, markdown source view, auditor-as-linker (2026-09-27 rulings)
The graph becomes raw reasoning chains only. Quiz outcomes and
understanding observations are no longer formalized; assessment
moves to the future practice module.

Events ledger removed end-to-end: LedgerEvent/LedgerEventKind/
UnderstandingLevel/ExtractionAttribution/AuditResult.dropped and
snapshot nodeObservations deleted from contracts; quizEvent builder
and the events step of mergeFindings gone; appendEvent/readEvents
dropped from the port and store; LEDGER_FILES loses events.jsonl
with a one-time purge of a stale file next to the nodes ledger;
graph-deletion and the delete dialog lose event purge + quiz-answer
impact counts; GraphSessionProposition becomes a pure birth list.

Whole-session markdown source view: new core/session-markdown.ts is
the shared formatting engine (id-anchored speaker blocks, quiz/
recall/question blocks, tool one-liners) — the extractor's prompt
view and the future user-facing export render from one engine.
Coverage fingerprints deliberately stay on the old line format
(content-hash semantics; re-formatting never re-opens covered
history). The whole session goes to the extractor — no phase cut.

Auditor refocused as the identity linker: never sees the source;
explores the graph via new auditor-specific tools in
agent/tools/graph.ts (find — glob over name/statement/aliases;
list_sessions; list_propositions with full statement text; no
timestamps) instead of an injected full-graph dump; equivalence
bias — paraphrases merge, only genuinely distinct propositions stay
apart; no drop channel, the extractor is fully trusted; may draw
bridging edges (typed follows/supports) between existing nodes and
the batch, resolved by the merger with chat-scoped message-less
provenance. Agentic loop, maxTurns 16.

UI: the node detail card gains an edges section above the sessions
list (follows-from / supported-by / what-follows / what-it-supports,
rendered only when present, click-to-select neighbors); the
understanding-note chips are gone.

Prober list_propositions reworded to what a session covered —
names only, no levels. docs/architecture.md + README updated to
the as-built pipeline. Gates: typecheck, 1059/1059, lint, build.
2026-09-28 16:54:44 +03:00
amosblomqvist 3160829eb6 explain refinements: no opt-out row, one committed field (owner 2026-09-27)
Two follow-up rulings on the explain tool:

1. No 'I don't know' row — not needed in free recall. If he doesn't
   know, he says so in his own words; his text is the only answer
   channel. Dock: text mode is rowless again (the composer IS the
   surface); doctrine and the shared prompt chunk updated; the format
   branch and card revert to quiz-only dontKnow.

2. explanation + correctAnswer collapse into ONE field — the separate
   explanation was quiz remnant (MC needs the why outside its options;
   a free-text commitment carries its own reasoning). Schema is now
   {question, correctAnswer, details?}; the reveal shows the single
   committed answer; no explanation snapshots anywhere on the explain
   path.

1047/1047 tests, typecheck/lint/build green.
2026-09-27 09:13:34 +03:00