From 2835655515b8ccef88e71195760b3ad7a28f8a3a Mon Sep 17 00:00:00 2001 From: Carr1005 Date: Wed, 9 Sep 2026 12:20:41 -0700 Subject: [PATCH 01/14] Spec: on-ramp orientation block, heavy-option setup notes, model-currency updates, importable fixture filename MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Owner-approved texts from the #24 batch: blockquote gains 'what you're building' + fastest-path/completion sentences (R16/R18 density feedback; keyless finish blessed); D11/D12 course-default entries carry inline setup weight; D4 Options name providers not current model names, with gate-time recommendation deferred to the build agent; CTX-D gains the perishable request-parameter-behavior category (temperature rejection); §2 key wording acknowledges compatible endpoints; fixtures/llm-stubs.py -> llm_stubs.py. Co-Authored-By: Claude Fable 5 --- .../spec.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/courses/agent-memory-building-memory-aware-agents/spec.md b/courses/agent-memory-building-memory-aware-agents/spec.md index 1f02afe..b615ae3 100644 --- a/courses/agent-memory-building-memory-aware-agents/spec.md +++ b/courses/agent-memory-building-memory-aware-agents/spec.md @@ -1,5 +1,7 @@ # Spec: Memory-Aware Research Agent — Standalone Takeaway +> **What you're building:** a terminal chat assistant that remembers. What you discuss, what it looks up, and what it learns all persist in a local database — so you can quit, come back tomorrow, and ask "what was my first question?" and it answers from memory instead of starting over. **The fastest path:** answer the first question below with "recommended baseline build" and the agent builds it end-to-end; the build is complete when the offline test suite passes — no API key needed for that (a key only unlocks the live tests and actually chatting with the finished agent). +> > **This file is self-contained.** The embedded **Course Context Pack** (bottom of this file) replaces all external course references — nothing here requires access to the course platform, notebooks, or transcripts. `(CTX-X)` anchors mark course-derived knowledge. The **Decision Ledger** below holds every point where this build could diverge, each pinned to one course-derived default, so the spec builds as-is with zero intake. > > **Provenance:** generated from the *Agent Memory: Building Memory-Aware Agents* course notebooks (notebook dump incl. `helper.py` and `requirements.txt`) + lesson transcripts, on 2026-09-07. Generation guide version: repo commit `a7de8ef`. Hand-evolved since: §0 gate and Ledger option labels updated on 2026-09-08 to the guide's gate-semantics template (guide @ `bbe7189`); body otherwise unchanged. @@ -67,15 +69,15 @@ when you have reason to prefer another option. | D1 | learner | **Project** `[project]` — the Default is the course's *example realization* (a research-assistant scenario), expected to be swapped when the learner's own project differs; the invariants, not the example, must survive | — | A memory-aware **research-assistant chat agent** (terminal chat over per-thread sessions), re-expressing the course's example scenario shape ("an agentic research assistant that helps users investigate complex topics over multiple sessions", CTX-E L3) on the §5 synthetic fixture corpus. §3-precedence **branch 1**: the materials clearly afford a concrete example shape; all fixture facts are authored here — no course data is copied | Course-example shape (course default); the learner's own project (free text at the gate) | Swapping the project means re-seeding the KB fixtures, retitling, and re-reading D2/D3; the pattern and every invariant are unchanged | learner | | D2 | learner | **Data / inputs** `[data]` — Default is the course-shaped *example* seed, expected to be swapped for the learner's real corpus | — | The §5 **synthetic fixture corpus** (3 authored knowledge-base documents, 1 seeded 30-message conversation, fixture tools). Course provenance (what the course did, not advice): it streamed 100 records of public arXiv paper metadata plus on-demand fetched arXiv papers (CTX-C10, CTX-E L3/L4) | Fixture corpus; live arXiv ingestion (course default — the course demo path: keyless public API, network; ACs touching it are `live`); the learner's own documents | Real data adds network/latency and does not run the offline oracle; fixtures keep every AC runnable on day one. Changing embedded data after ingest → re-ingest (see Ask First AF2) | learner | | D3 | learner | **Goal** `[goal]` — what "working" means | — | **Cross-session continuity**: after the §5 scripted demo sequence, the agent answers a question about earlier turns (including turns already summarized away) using only persisted memory — the course's own closing demo shape ("what was my first question?", CTX-E L6) | Default continuity goal; a learner-defined retrieval/continuity goal (free text) | A different goal re-weights which memory types matter; ACs 1–25 pin the default goal only | learner | -| D4 | learner | **Model / provider** `[model]` | The model MUST be reachable through an OpenAI-style chat-completions interface and MUST support **native tool/function calling** | **OpenAI hosted API**: `gpt-5-mini` for the agent loop + `gpt-5` for background memory operations (summarization, tool-docstring augmentation, entity extraction) — the end-to-end app's configuration (loop default in the L5 app notebook; helper defaults for memory ops). The materials are split on naming: the L4 notebook's local token-limit map keys `gpt-5-mini` while `helper.py`'s keys `gpt-5` (same 256 000 value both sides) — both sources cited in CTX-C6. Keyed API ⇒ ACs needing it are marked `live` | OpenAI gpt-5 family **(course default)**; any OpenAI-compatible endpoint (hosted or locally served) meeting the invariant | Weaker models degrade summary fidelity, entity extraction, and tool selection; a different model changes the token-limit body default (§4 R4) | learner | +| D4 | learner | **Model / provider** `[model]` | The model MUST be reachable through an OpenAI-style chat-completions interface and MUST support **native tool/function calling** | **OpenAI hosted API**: `gpt-5-mini` for the agent loop + `gpt-5` for background memory operations (summarization, tool-docstring augmentation, entity extraction) — the end-to-end app's configuration (loop default in the L5 app notebook; helper defaults for memory ops). The materials are split on naming: the L4 notebook's local token-limit map keys `gpt-5-mini` while `helper.py`'s keys `gpt-5` (same 256 000 value both sides) — both sources cited in CTX-C6. Keyed API ⇒ ACs needing it are marked `live` | OpenAI gpt-5 family **(course default)**; any OpenAI-compatible endpoint meeting the invariant — hosted (e.g. Anthropic's or Google's OpenAI-compatibility APIs) or locally served. Model *names* age fast (CTX-D): expect the build agent to recommend current ones at the gate | Weaker models degrade summary fidelity, entity extraction, and tool selection; a different model changes the token-limit body default (§4 R4) | learner | | D5 | learner | **Environment** `[environment]` | **All memory must survive process restarts** — the course's core promise ("persists across sessions", CTX-E L6/L7) | Local single-machine run, Python ≥ 3.11 (this build's pin — *project hardening*: the materials state no Python version and install unpinned, see §2) | Local machine; container; always-on server | Environments without a durable filesystem break the invariant and force a hosted store (see D11 Options) | learner | | D6 | learner | **Scope boundary** `[scope-boundary]` — *gate instruction:* when asking this row, present the §1 "Not Included" list **verbatim inside the question** (§1 is not a Ledger row; this row is the only way the learner ever sees the exclusion list before being asked to amend it) | — | **Keep as-is**: every §1 exclusion stands; the full acceptance set is built | (a) Keep the boundary as-is (course default); (b) bring an excluded item back — free text names which; each §1 item carries a handling rule that determines the answer. A genuinely new exclusion also arrives via free text and MUST name the ACs it retires | Restoring a "named-but-never-built" item adds work the spec supplies no parameters or ACs for; restoring a row-owned item re-opens that row | learner | | D7 | design-argued | **Memory-core topology** | An external persistent store exists and a **single manager abstraction** mediates every memory read/write (the agent code never touches storage directly) | The full **seven-type segmented topology**: conversational, knowledge-base, workflow, toolbox, entity, summary, tool-log — one store each, unified behind the memory manager (CTX-A) | Seven-type topology (course default — the course's argued position); **conversational-memory-only** (the course's Lesson-2 baseline, demonstrated working for chat continuity and then deliberately built beyond — a legitimate reduced-scope choice); any subset in between | Lesson 2 argues the trade-off aloud: conversational-only gives continuity but "conversation windows are finite, user relationships are not", "not all valuable information is in a single conversation", and "agents need structured, queryable knowledge, not just chat logs" (CTX-E L2). Dropping a type removes its context segment, its persistence rules, and its ACs | course+learner | | D8 | design-structural | **Deterministic vs agent-triggered operation split** — who triggers each memory operation: the harness (code) or the model (tool call) | Context-assembly reads and the persistence writes continuity depends on run **deterministically every turn**, never at model discretion; judgment-requiring operations are exposed to the model **as tools** | The end-to-end app's split (§4 R3/R11–R13, R14): deterministic each turn — read conversational/KB/workflow/entity/summary-context at loop start, write user + assistant conversational rows, write workflow after tool-using runs, write a tool log after every call, extract entities from query and answer (non-fatally), and offload at the >80 % threshold; agent-triggered tools — `read_toolbox`, `arxiv_search_candidates`, `fetch_and_save_paper_to_kb_db`, `get_current_time`, `expand_summary`, `summarize_and_store`. Contradiction note (lower-precedence evidence, both sides cited in CTX-C4): the Lesson-3 classification table marks `read_summary_context` and `write_entity` agent-triggered-only, while the Lesson-6 app runs both deterministically — the app's behavior is the default. Summarization is deliberately **both** deterministic (threshold) and agent-callable — narrated in Lesson 6 | The app split (course default); moving individual operations between the two categories (e.g. agent-discretionary entity writes, per the Lesson-3 table) | Deterministic ops buy predictability, continuity, and "no forgotten saves" at token/latency cost; agent-triggered ops buy relevance and cost control but risk missed saves and the chicken-and-egg problem ("you need memory to know which memory you need") — argued in Lessons 3 and 6 (CTX-C4) | course+learner | | D9 | design-argued | **Context-window reduction strategy** | Context usage is monitored every turn and reduced before overflow; the current question is **never** summarized away | **Recoverable compaction** (the course's built mechanism): summarize the thread's unsummarized rows, persist the summary (id + description + summary + full source text, thread-scoped), mark the exact source rows with the `summary_id`, replace only the conversation segment with a stub + `[Summary ID: …]` reference, and expose `expand_summary` for on-demand recovery (§4 R7) | Recoverable compaction (course default); **pure lossy summarization** (summary replaces context, no back-link — presented by the course as a legitimate technique with an explicit warning that it "will always lose a little bit of information", Lesson 5); pure compaction (offload raw content under an ID + description, no summary) | Lesson 5 argues it aloud: summarization is inherently lossy; compaction preserves recoverability at the cost of storage and an extra retrieval hop (CTX-C3). Choosing lossy-only retires ACs 11–12's recovery assertions | course+learner | | D10 | design-argued | **Tool-description augmentation at registration** | Tool retrieval is **semantic**: keyed on the stored description text embedded at registration, not on exact tool names | **LLM augmentation ON by default** at registration (original docstring + function source → enriched description + 5 synthetic trigger queries, all folded into the embedding text), overridable per tool. The course registered a mix (most tools augmented; `arxiv_search_candidates` deliberately raw). Contradiction note (CTX-C7): `get_current_time` is registered `augment=True` in the Lesson-4 notebook cell but `augment=False` in the helper's common-tools registration used by the Lesson-6 app | Augmented (course default — the course's argued position); raw docstrings (course-demonstrated working: the unaugmented arXiv tool was still retrieved first in the Lesson-4 validation query) | Lesson 4 argues it aloud: augmentation buys higher separability and recall in the embedding space at the cost of LLM calls per registration (CTX-C5) | course+learner | -| D11 | realization | **Persistent memory store** — heavy-dependency substitution row | One durable store layer provides **both** (a) exact-key, time-ordered relational access (conversation rows by `thread_id`; tool logs) **and** (b) semantic-similarity retrieval with metadata filtering over embedded text for the five vector memory types. (Durability across restarts: owned by D5) | **SQLite via Python's stdlib `sqlite3`** — one database file, seven tables carrying the §4 canonical store names; embedding vectors stored per row; similarity computed **exactly, in process (brute-force cosine)** at the declared fixture scale. §3 dependency precedence **branch 1** (substitution): the course teaches a memory *pattern* that Oracle realizes; Oracle is not the taught subject — the course's own close is "take these patterns, adapt them to your own use case" (CTX-E L7). "Lightest" decided by tier: **tier 1** (ships with the standard distribution) satisfies the invariant at the declared default scale, because exact brute-force similarity meets the retrieval contract on fixture-sized data; no indexing tier is required at that scale — scaling realizations live in Options | Oracle AI Database 26ai + LangChain `OracleVS` + IVF vector index **(course default)**; SQLite stdlib; an embedded vector library (tier 2) once the corpus outgrows brute force; a locally served / hosted vector DB (tier 3) | Switching stores forces re-ingestion and re-implementation of the metadata filters; Oracle adds container + admin setup (Docker, admin credentials) but brings IVF/HNSW indexing and hybrid search at scale — the course's Lesson-3 rationale for indexing (CTX-C1, CTX-C2) | course+learner | -| D12 | realization | **Web-search tool** (course realization: Tavily) — keyed-tool row | If a web-search tool is enabled it MUST be toolbox-registered and follow the **search-and-store** pattern: results persisted to knowledge-base memory with title/url/score/query/timestamp metadata, never returned only ephemerally (§4 R17) | **Omitted.** §3 keyed-tool rule: Tavily needs an API key (heavy), and it is *not* the only carrier of the taught search-and-store behavior — the keyless arXiv tool `fetch_and_save_paper_to_kb_db` also persists fetched external content to the knowledge base — so the deterministic default is omission, with the keyless course tools carrying the behavior | Tavily via `TAVILY_API_KEY` **(course default)**; an honestly-labeled local stand-in (a fake `search_web_local` tool serving canned fixture results, whose registered description MUST say it is a fake standing in for a real web-search service); omit | Omission removes open-web reach (the agent is limited to arXiv + its own memory); the stand-in keeps the pattern exercisable offline but only answers from fixtures; Tavily restores the course demo exactly at the cost of a key | course+learner | +| D11 | realization | **Persistent memory store** — heavy-dependency substitution row | One durable store layer provides **both** (a) exact-key, time-ordered relational access (conversation rows by `thread_id`; tool logs) **and** (b) semantic-similarity retrieval with metadata filtering over embedded text for the five vector memory types. (Durability across restarts: owned by D5) | **SQLite via Python's stdlib `sqlite3`** — one database file, seven tables carrying the §4 canonical store names; embedding vectors stored per row; similarity computed **exactly, in process (brute-force cosine)** at the declared fixture scale. §3 dependency precedence **branch 1** (substitution): the course teaches a memory *pattern* that Oracle realizes; Oracle is not the taught subject — the course's own close is "take these patterns, adapt them to your own use case" (CTX-E L7). "Lightest" decided by tier: **tier 1** (ships with the standard distribution) satisfies the invariant at the declared default scale, because exact brute-force similarity meets the retrieval contract on fixture-sized data; no indexing tier is required at that scale — scaling realizations live in Options | Oracle AI Database 26ai + LangChain `OracleVS` + IVF vector index **(course default — heavy setup: Docker container, admin credentials, multi-GB image)**; SQLite stdlib; an embedded vector library (tier 2) once the corpus outgrows brute force; a locally served / hosted vector DB (tier 3) | Switching stores forces re-ingestion and re-implementation of the metadata filters; Oracle adds container + admin setup (Docker, admin credentials) but brings IVF/HNSW indexing and hybrid search at scale — the course's Lesson-3 rationale for indexing (CTX-C1, CTX-C2) | course+learner | +| D12 | realization | **Web-search tool** (course realization: Tavily) — keyed-tool row | If a web-search tool is enabled it MUST be toolbox-registered and follow the **search-and-store** pattern: results persisted to knowledge-base memory with title/url/score/query/timestamp metadata, never returned only ephemerally (§4 R17) | **Omitted.** §3 keyed-tool rule: Tavily needs an API key (heavy), and it is *not* the only carrier of the taught search-and-store behavior — the keyless arXiv tool `fetch_and_save_paper_to_kb_db` also persists fetched external content to the knowledge base — so the deterministic default is omission, with the keyless course tools carrying the behavior | Tavily via `TAVILY_API_KEY` **(course default — needs a paid API key)**; an honestly-labeled local stand-in (a fake `search_web_local` tool serving canned fixture results, whose registered description MUST say it is a fake standing in for a real web-search service); omit | Omission removes open-web reach (the agent is limited to arXiv + its own memory); the stand-in keeps the pattern exercisable offline but only answers from fixtures; Tavily restores the course demo exactly at the cost of a key | course+learner | **Contradicted group: empty.** Every mined course contradiction failed the §5.5 stakes test (each is a near-equivalent lever whose up-front choice changes no structure, semantics, or @@ -118,7 +120,7 @@ The agent must not add features beyond this boundary on its own initiative. |---|---|---| | Language | Python ≥ 3.11 | This build's pin (*project hardening* — see honesty note below). Environment: Ledger D5 | | Persistent store | SQLite (stdlib `sqlite3`) | Ledger row **D11** — learners change it there, not here | -| LLM | OpenAI API: `gpt-5-mini` (agent loop), `gpt-5` (memory ops) | Ledger row **D4**. Key via `OPENAI_API_KEY`; ACs needing it are `live` | +| LLM | OpenAI API: `gpt-5-mini` (agent loop), `gpt-5` (memory ops) | Ledger row **D4**. Key via `OPENAI_API_KEY` (or your compatible endpoint's equivalent — D4); ACs needing it are `live` | | Embeddings | `sentence-transformers` model `paraphrase-mpnet-base-v2` (768-dim) | Course-demonstrated in all lessons; keyless, runs locally (one-time model download). Post-build lever — changing it after ingest forces re-embedding (AF2) | | arXiv access | Keyless public arXiv API (metadata + PDF text extraction) | Keyless public API stays the default per the mere-network rule; ACs touching it are `live` | | Text chunking | Recursive character splitting, chunk_size 1500 / overlap 200 | Course-demonstrated values (deep-ingestion tool). Implementation library is the agent's choice | @@ -443,7 +445,7 @@ make a test pass. | `fixtures/conversation-seed.json` | A 30-message alternating user/assistant conversation (authored) in which a masters student plans a field study of low-power sensor networks. It deliberately contains one of each summary-heading category: technical facts (the Halvard array's 90-minute cadence; a CRC error on node 22), emotional context (the student says they are "nervous about the fieldwork window"), entities (Dr. Imara Voss; the Nordfell Institute; the LoRaWAN gateway "Kestrel-3"), and action items (email Dr. Voss by Friday; order 4 spare nodes). Long enough that its rendered form exceeds a test-scaled context threshold. **Failure-mode seed for F2/F3.** | | `fixtures/turns-reference.json` | Two scripted user turns on one thread: (1) "List the three projects in the knowledge base with their operators or maintainers." (2) "Book time to review the second one." Turn 2 is resolvable only via conversational memory. **Seed for F1.** | | `fixtures/tools.py` (fixture tools, registered at test start) | `lookup_reef_site(site_id)` → returns the coral-atlas facts for a site; `big_report()` → returns a deterministic 5 000-character report (**seed for F5**); `get_current_time(detailed)` → course utility tool; plus five distinct no-op tools with unrelated single-purpose descriptions (`convert_units`, `spell_check`, `roll_dice`, `hash_text`, `count_words`) so the registry (≥8 tools) exceeds k=5 (**seed for F4**); a duplicate re-registration of `lookup_reef_site` is attempted once (**seed for F8**). | -| `fixtures/llm-stubs.py` | [H] A scripted stand-in LLM client for offline ACs (the course used the live API; stubs are project hardening): `stub_empty_summarizer` (returns empty content twice, **seed for F6**), `stub_generic_labeler` (returns "Conversation summary", **seed for F10**), `stub_always_tool_caller` (always emits a `get_current_time` tool call, never a final answer, **seed for F7**), `stub_echo_answerer` (returns a fixed final answer with no tool calls). | +| `fixtures/llm_stubs.py` | [H] A scripted stand-in LLM client for offline ACs (the course used the live API; stubs are project hardening): `stub_empty_summarizer` (returns empty content twice, **seed for F6**), `stub_generic_labeler` (returns "Conversation summary", **seed for F10**), `stub_always_tool_caller` (always emits a `get_current_time` tool call, never a final answer, **seed for F7**), `stub_echo_answerer` (returns a fixed final answer with no tool calls). | Offline ACs run with stubs and no network (after the one-time embedding-model download). ACs tagged **`live`** need `OPENAI_API_KEY` (and network for AC24) and are excluded from the @@ -599,6 +601,7 @@ in CTX-A…C are durable; only these names are perishable. The course's installs - Packages/APIs: `langchain-oracledb` (`OracleVS`, `OracleVectorizerPreference`), `langchain_huggingface.HuggingFaceEmbeddings` (some lessons import the older `langchain_community.embeddings` path), `langchain_community` (`DistanceStrategy`, `ArxivRetriever`, `ArxivLoader`), `langchain_text_splitters.RecursiveCharacterTextSplitter`, `sentence-transformers`, `oracledb`, `openai` chat-completions with `tools`/`tool_choice="auto"` and `max_completion_tokens`, `tavily-python`, `datasets` (streaming), `pymupdf`, `dotenv`, `pydantic`. - Models: `gpt-5`, `gpt-5-mini`; embedding `sentence-transformers/paraphrase-mpnet-base-v2` (768-dim). +- Request-parameter behavior: some model families reject standard chat-completions parameters (e.g. reasoning-tier models rejecting `temperature`) — verify parameter compatibility against your provider's current docs rather than assuming; parameter support is as perishable as a model name. - Oracle-era artifacts (provenance only; not used by the default build): Oracle AI Database 26ai in Docker; admin user `system`; a `VECTOR` user with a lab password; DSN `127.0.0.1:1521/FREEPDB1`; connection `program` tag `devrel.deeplearning.course_1`; index names `*_vs_ivf`; hybrid preference `KB_VECTORIZER_PREF`; lab quirk: "Admin connection failed" on first run resolves by waiting and re-running. Never reuse lab credentials. - Demo-data names: HuggingFace dataset `nick007x/arxiv-papers` (100 streamed records); demo query paper "MemGPT"; assistant nickname "ArxivScout"; demo thread id `50000`; sample 30-message PhD-research conversation shipped in the helper (re-expressed here as an authored fixture, not copied). From 809dea8dc8b2ecde79c1fc706de2dda33ffac6e6 Mon Sep 17 00:00:00 2001 From: Carr1005 Date: Wed, 9 Sep 2026 12:21:33 -0700 Subject: [PATCH 02/14] Spec: D13 summary-memory-scope Ledger row (owner decision: admitted to canon) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First contradicted-category row: thread-scoped default (the app path) vs the Lesson-5 notebook's unscoped global pool, both course-demonstrated; stakes test passes because the choice changes what a summary read returns. Side edits: contradicted-group paragraph updated; R18 defers to D13 (no deciding twice); §5 coverage note and CTX-C11 cross-reference D13; provenance note records the batch. Co-Authored-By: Claude Fable 5 --- .../spec.md | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/courses/agent-memory-building-memory-aware-agents/spec.md b/courses/agent-memory-building-memory-aware-agents/spec.md index b615ae3..2fb7bde 100644 --- a/courses/agent-memory-building-memory-aware-agents/spec.md +++ b/courses/agent-memory-building-memory-aware-agents/spec.md @@ -4,7 +4,7 @@ > > **This file is self-contained.** The embedded **Course Context Pack** (bottom of this file) replaces all external course references — nothing here requires access to the course platform, notebooks, or transcripts. `(CTX-X)` anchors mark course-derived knowledge. The **Decision Ledger** below holds every point where this build could diverge, each pinned to one course-derived default, so the spec builds as-is with zero intake. > -> **Provenance:** generated from the *Agent Memory: Building Memory-Aware Agents* course notebooks (notebook dump incl. `helper.py` and `requirements.txt`) + lesson transcripts, on 2026-09-07. Generation guide version: repo commit `a7de8ef`. Hand-evolved since: §0 gate and Ledger option labels updated on 2026-09-08 to the guide's gate-semantics template (guide @ `bbe7189`); body otherwise unchanged. +> **Provenance:** generated from the *Agent Memory: Building Memory-Aware Agents* course notebooks (notebook dump incl. `helper.py` and `requirements.txt`) + lesson transcripts, on 2026-09-07. Generation guide version: repo commit `a7de8ef`. Hand-evolved since: §0 gate and Ledger option labels updated on 2026-09-08 to the guide's gate-semantics template (guide @ `bbe7189`); revisions batch on 2026-09-09 — orientation block, heavy-option setup notes, D4/CTX-D model-currency updates, fixture-module rename, and the D13 summary-scope row (matching guide rules land in the same PR); body otherwise unchanged. --- @@ -79,11 +79,13 @@ when you have reason to prefer another option. | D11 | realization | **Persistent memory store** — heavy-dependency substitution row | One durable store layer provides **both** (a) exact-key, time-ordered relational access (conversation rows by `thread_id`; tool logs) **and** (b) semantic-similarity retrieval with metadata filtering over embedded text for the five vector memory types. (Durability across restarts: owned by D5) | **SQLite via Python's stdlib `sqlite3`** — one database file, seven tables carrying the §4 canonical store names; embedding vectors stored per row; similarity computed **exactly, in process (brute-force cosine)** at the declared fixture scale. §3 dependency precedence **branch 1** (substitution): the course teaches a memory *pattern* that Oracle realizes; Oracle is not the taught subject — the course's own close is "take these patterns, adapt them to your own use case" (CTX-E L7). "Lightest" decided by tier: **tier 1** (ships with the standard distribution) satisfies the invariant at the declared default scale, because exact brute-force similarity meets the retrieval contract on fixture-sized data; no indexing tier is required at that scale — scaling realizations live in Options | Oracle AI Database 26ai + LangChain `OracleVS` + IVF vector index **(course default — heavy setup: Docker container, admin credentials, multi-GB image)**; SQLite stdlib; an embedded vector library (tier 2) once the corpus outgrows brute force; a locally served / hosted vector DB (tier 3) | Switching stores forces re-ingestion and re-implementation of the metadata filters; Oracle adds container + admin setup (Docker, admin credentials) but brings IVF/HNSW indexing and hybrid search at scale — the course's Lesson-3 rationale for indexing (CTX-C1, CTX-C2) | course+learner | | D12 | realization | **Web-search tool** (course realization: Tavily) — keyed-tool row | If a web-search tool is enabled it MUST be toolbox-registered and follow the **search-and-store** pattern: results persisted to knowledge-base memory with title/url/score/query/timestamp metadata, never returned only ephemerally (§4 R17) | **Omitted.** §3 keyed-tool rule: Tavily needs an API key (heavy), and it is *not* the only carrier of the taught search-and-store behavior — the keyless arXiv tool `fetch_and_save_paper_to_kb_db` also persists fetched external content to the knowledge base — so the deterministic default is omission, with the keyless course tools carrying the behavior | Tavily via `TAVILY_API_KEY` **(course default — needs a paid API key)**; an honestly-labeled local stand-in (a fake `search_web_local` tool serving canned fixture results, whose registered description MUST say it is a fake standing in for a real web-search service); omit | Omission removes open-web reach (the agent is limited to arXiv + its own memory); the stand-in keeps the pattern exercisable offline but only answers from fixtures; Tavily restores the course demo exactly at the cost of a key | course+learner | -**Contradicted group: empty.** Every mined course contradiction failed the §5.5 stakes test -(each is a near-equivalent lever whose up-front choice changes no structure, semantics, or -guarantee). Each is resolved as a **body default** carrying its in-place contradiction note, -its lever-value branch citation, and a *post-build lever* label — see §4 R4/R16 notes and the -"Post-build levers" list at the end of §4, with background in CTX-C6–C9. +| D13 | contradicted | **Summary memory scope** — does a stored summary belong to the thread that produced it, or to one global pool shared across threads | — (recoverability under either scope: owned by D9) | **Thread-scoped**: summaries are written with the originating `thread_id`, and summary-context reads filter to the active thread — the helper path the Lesson-6 app runs (§4 R18). *Contradiction note:* the Lesson-5 notebook's local summary writer stores summaries with **no thread id** (a global pool) while the helper version the app uses adds scoping — both sides cited in CTX-C11. Passes the §5.5 stakes test at initial choice: the choice changes what a summary read *returns*, so it is a row, not a lever | Thread-scoped **(course default** — the app path**)**; one global summary pool, unscoped (course-demonstrated: the Lesson-5 notebook's writer) | Thread scoping isolates conversations — no cross-thread context bleed, and per-thread recovery ("what was my first question?") stays deterministic. The global pool turns summaries into cross-conversation recall (useful for a single-user assistant) at the cost of one thread's compressed context surfacing in another's window — a hazard wherever threads are different users or contexts needing isolation. Cross-conversation knowledge still flows through the global stores (knowledge base, entities, workflows) under either choice | course+learner | + +**Contradicted group: one row (D13).** Every other mined course contradiction failed the §5.5 +stakes test (each is a near-equivalent lever whose up-front choice changes no structure, +semantics, or guarantee). Each of those is resolved as a **body default** carrying its in-place +contradiction note, its lever-value branch citation, and a *post-build lever* label — see §4 +R4/R16 notes and the "Post-build levers" list at the end of §4, with background in CTX-C6–C9. --- @@ -414,11 +416,12 @@ the contradiction is stated in place. `arxiv_id`, `entry_id`, `title`, `authors`, `published`, and abstract capped at 2 500 chars (discovery reads metadata only — cheap before expensive ingestion; retriever caps: 8 docs, 4 000 chars). → AC24 (`live`), AC25 (offline via fixture tool) -18. **R18 — Thread-scoped summary retrieval.** [C] Summary-context reads prefer/filter - summaries for the active thread when a thread_id is known; `expand_summary` accepts an - optional thread scope and reports "not found" per scope explicitly. (Source: helper - summary methods — the app path; the Lesson-5 notebook's local variant predates thread - scoping, CTX-C11.) → AC12 covers scope; AC11 asserts thread_id persisted. +18. **R18 — Thread-scoped summary retrieval (per D13's default).** [C] Summary-context reads + prefer/filter summaries for the active thread when a thread_id is known; `expand_summary` + accepts an optional thread scope and reports "not found" per scope explicitly. (Provenance: + helper summary methods — the app path; the Lesson-5 notebook's local variant is unscoped — + the decision between the two lives in Ledger D13, CTX-C11.) → AC12 covers scope; AC11 + asserts thread_id persisted. **Post-build levers (body defaults, not Ledger rows — each carries its §5.5 lever-value branch):** distance strategy = cosine (branch 1, R16); loop toolbox k = 5 (branch 2, R9); @@ -484,7 +487,8 @@ offline run. Coverage: every rule R1–R18 has ≥1 AC; every AC maps to a rule; every demonstrated failure mode F1–F10 (CTX-B) appears as a rule, a fixture seed, an AC, and a CTX-B entry. D-row invariants: D4→AC21, D5→AC2, D7→AC5, D8→AC5, D9→AC12, D10→AC15/AC16 (retrieval keyed on stored -descriptions), D11→AC1/AC2/AC23, D12→AC25. D1–D3/D6 invariants are empty (nothing to test). +descriptions), D11→AC1/AC2/AC23, D12→AC25. D1–D3/D6/D13 invariants are empty (nothing to +test; D13's recoverability is owned by D9→AC12, and its default is exercised by AC11/AC12). --- @@ -590,7 +594,7 @@ aware progression. The running example is a research assistant the notebooks nic 8. **Toolbox k** (→ R9): manager default 3; registered tool signature default 3 with a docstring claiming 5; app loop retrieves 5; lesson prose says "typically 3–5". Body default 5 by lever branch 2. 9. **Distance strategy** (→ R16): cosine in Lesson-3/4 store configs and Lesson-3 narration; Euclidean in Lesson-5/6 notebook configs and one Lesson-3 markdown key-components list. Body default cosine by lever branch 1 (narration of the introducing lesson). The clean-slate drop cells exist to guarantee strategy consistency across a lesson run — the consistency rule R16 keeps that guard without the wipe. 10. **Search-and-store** (→ D12, R17): the web-search tool doesn't just return results — it writes each result into the knowledge base with title/url/score/query/timestamp so the agent "learns from its searches"; the arXiv deep-ingest tool applies the same pattern to full papers (chunk 1500/200), keeping large payloads out of model context. -11. **Summary thread-scoping** (→ R18): the Lesson-5 notebook's local summary writer has no thread scope; the helper version used by the Lesson-6 app adds thread_id scoping and thread-filtered summary-context reads. The app path is the default. +11. **Summary thread-scoping** (→ Ledger D13, R18): the Lesson-5 notebook's local summary writer has no thread scope; the helper version used by the Lesson-6 app adds thread_id scoping and thread-filtered summary-context reads. The app path is D13's default; the unscoped pool is its course-demonstrated alternative. 12. **Instructor heuristics**: providers recommend exposing roughly 10–20 tools max for reliable selection; ~4 chars/token is a serviceable estimate (some models nearer 2); prompt wording determines summarization quality — vary it per problem; validate tool registration on a low-risk utility tool first; record workflows so the model doesn't "figure it out on the fly" each time. ### CTX-D. Perishable assumptions From 0cd5ff2f5170414fc6f7ddb9b280f56d848e29ca Mon Sep 17 00:00:00 2001 From: Carr1005 Date: Wed, 9 Sep 2026 12:23:38 -0700 Subject: [PATCH 03/14] =?UTF-8?q?Guide:=20six=20revisions-batch=20rules=20?= =?UTF-8?q?=E2=80=94=20orientation=20block=20+=20reader=20test,=20inline?= =?UTF-8?q?=20heavy-option=20weights,=20model-currency=20timing,=20id=20al?= =?UTF-8?q?phabets,=20importable=20fixture=20filenames,=20scoping-contradi?= =?UTF-8?q?ction=20example?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit G1 blockquote anatomy: orientation lead (goal-row default as a user-visible interaction, never a component inventory) + fastest-path/completion sentence. G2 §5.5 Options: heavy options state setup weight inline at choice time. G3 §6.0 + CTX-D anatomy: model currency is the canonical generation-vs-gate timing split; request-parameter-compatibility perishables. G4 Pass A: identifier formats are working parameters, alphabet included. G5 §5 anatomy: fixture module filenames must be importable identifiers. G6 §5.5 contradicted route: worked storage-scoping example that passes the stakes test (stabilizes the D13-class subject). §14: reference-style audit lines for G1/G2/G3/G5; G4 rides the existing Pass-A coverage line. Co-Authored-By: Claude Fable 5 --- .../references/spec-generation-guide.md | 40 +++++++++++++------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/.claude/skills/generate-spec/references/spec-generation-guide.md b/.claude/skills/generate-spec/references/spec-generation-guide.md index 8df9d62..da7528c 100644 --- a/.claude/skills/generate-spec/references/spec-generation-guide.md +++ b/.claude/skills/generate-spec/references/spec-generation-guide.md @@ -95,7 +95,7 @@ Either way — project, technique, or dependency — name **one** buildable targ Notebooks give you *what and how-configured*; transcripts give you *why and what-it-trades-off*. Mine both. **Pass A — Notebooks.** Extract: -- Every working parameter (chunk sizes, overlaps, k-values, temperatures, paths) **with its exact location**, distinguishing exploratory-notebook configs from the course's end-to-end *application* config. Parameters include **output-shape constraints the course's prompts impose** (length bands, required formats, reject-lists for generic outputs) — these are working parameters of the pipeline exactly as a numeric config is, and they vanish silently if mined only when numeric. When both exist, the app config is the recommended starting point (exception: a *contradicted near-equivalent lever* resolves by the §5.5 lever-value rule instead); record the rest as alternatives with their context. Never call a value "the default" if the materials contain more than one config. +- Every working parameter (chunk sizes, overlaps, k-values, temperatures, paths, identifier formats) **with its exact location**, distinguishing exploratory-notebook configs from the course's end-to-end *application* config. Identifier formats are parameters too, and their **alphabet is part of the value**, not just the length: an id the course generates as 8 hex characters is a different contract from "8 characters", and the alphabet is the half that silently drops when only the length is mined. Parameters include **output-shape constraints the course's prompts impose** (length bands, required formats, reject-lists for generic outputs) — these are working parameters of the pipeline exactly as a numeric config is, and they vanish silently if mined only when numeric. When both exist, the app config is the recommended starting point (exception: a *contradicted near-equivalent lever* resolves by the §5.5 lever-value rule instead); record the rest as alternatives with their context. Never call a value "the default" if the materials contain more than one config. - The API surface actually used: namespaces, class/function names, model names — and whether installs are **version-pinned**. State the era honestly (e.g. "installs unpinned, pre-1.0 namespace"). Never invent pins the notebook doesn't contain. - **Course-declared identifiers and constants** (store/table names, type enums, fixed labels the notebooks declare). Those that participate in a contract or schema stay **binding** in the spec; the rest land in **CTX as provenance** — a short name-map to the course's own terms — never as binding values. They exist so a learner can map the spec's concepts back to what the lessons show on screen; dropping them entirely severs that bridge. - Deliberately planted failure demonstrations (a duplicated input file, a query designed to fail) — these are curriculum, not accidents → they become rules. @@ -129,7 +129,7 @@ The **Decision Ledger** is the spec's second section (§6, right after the §0 p - **Category** — the §5.5 entry route that admitted the row (`design-argued | design-structural | realization | contradicted | learner`). Exactly one label, chosen by the precedence in the surfacing bar below; it determines the row's position in the fixed row order. - **Decision** — short name (e.g. "persistent store", "memory-core topology", "project"). - **Invariant** — what MUST hold to preserve the taught pattern. For learner-context rows it carries only the pattern's capability requirements on that dimension (§3) — typically empty for project/data/goal/scope-boundary, never invented. Write it precisely: this field doubles as the contract a future integration must satisfy. -- **Options** — realizations that satisfy the invariant, from course-faithful to tech-agnostic. The course's own technology always appears here even when it is not the default — and when it is not the default (a §3 branch-1 substitution), tag that entry **"(course default)"** in the cell text, so the §6.0 gate's labeling duty is deterministic: the build agent must never have to infer which option the course actually used. Never tag an Options entry with a bare "(default)": the Default column carries default-ness, and §6.0's two labels (provenance and recommendation) are the only sanctioned option labels. +- **Options** — realizations that satisfy the invariant, from course-faithful to tech-agnostic. The course's own technology always appears here even when it is not the default — and when it is not the default (a §3 branch-1 substitution), tag that entry **"(course default)"** in the cell text, so the §6.0 gate's labeling duty is deterministic: the build agent must never have to infer which option the course actually used. Never tag an Options entry with a bare "(default)": the Default column carries default-ness, and §6.0's two labels (provenance and recommendation) are the only sanctioned option labels. An option whose realization requires provisioning the learner may not have — a paid key, an admin-installed service, a large download — states that setup weight **inline in its own Options entry** (as prose inside or after its label, e.g. "(course default — heavy setup: …)"), not only in the Trade-off cell: the gate presents Options at choice time, and a weight buried in a cell the learner is not shown is a surprise deferred, not disclosed. - **Default (course-derived)** — **exactly one** buildable target (§3). For heavy-dependency rows, resolved by the §3 dependency precedence, with the branch stated. When the default is the course's *example realization* — a domain-specific instantiation (its demo data, its example tools) rather than the pattern itself — the Decision or Default cell must say so and mark it as expected to be swapped when the learner's project (the project row) differs: the invariant, not the example, is what must survive. - **Trade-off** — what switching costs (carries the course's *spoken* trade-offs: re-embedding, re-tuning, lateral-or-worse results). - **Owner** — a label only: `learner`, `course`, or `course+learner` (§9). Metadata; never gates behavior. @@ -142,7 +142,7 @@ The **Decision Ledger** is the spec's second section (§6, right after the §0 p - **`design-structural`** (pattern-structural) — choosing differently changes the system's *structure or semantics*: a pipeline stage added/removed/reordered, ownership moved between harness and model, a guarantee changed (reversibility, consistency), the data model or an interface reshaped. Litmus test: *redraw the architecture diagram after the change — did a box, arrow, owner, or guarantee change, or only a number inside a box?* Examples: store topology, the deterministic-vs-agent-triggered operation split, recoverable-compaction vs lossy-summarization. Counter-example: a retrieval `k` is load-bearing — set it absurdly and the pattern degrades — but no box, arrow, or guarantee changes; **sensitivity alone does NOT qualify** (such a value stays a body default unless another route admits it). - **`design-argued`** (course-argued) — the transcripts/slides explicitly present an alternative and argue a trade-off ("you could do X, but we do Y because Z"). Detection source: Pass B's mined spoken trade-offs (§4) — what the instructor argued about is what the course teaches *as a decision*. This route applies **even when the course's choice would otherwise be encoded as an invariant**: weaken the Invariant field to what must truly hold, make the argued position the Default, and carry the course's narrated argument — with its lesson citation — in the Trade-off cell. The default is always the course's argued position, so no-intake determinism (§3, §12.7/§12.8) is unaffected. **A row requires a genuinely two-sided argument.** When the mined "argument" is the course teaching X as best practice and demonstrating or warning against Y as the anti-pattern, do NOT emit a Ledger row offering X and Y as neutral options — a menu invites picking the warned-against side. Bake X in as a business rule (with an AC where testable); carry Y in the Trade-off/CTX narrative as the cited anti-pattern. Litmus: *did the course show Y working as an acceptable alternative, or only as the failure case?* Only the former earns a row. Judge "working" against how the course *frames* Y, not against the course's final goal: an alternative the course demonstrates working at reduced scope and then **moves beyond** (a baseline, a stepping stone, a simpler form it builds on) is NOT an anti-pattern — it remains a legitimate reduced-scope choice and keeps the subject a decision. Anti-pattern status requires the course to warn against Y as a *failure* (errors, degradation, broken behavior), not merely to outgrow it. And an "alternative" that is merely the taught approach's **degenerate or limiting case** — the behavior the taught mechanism itself produces when the workload is trivial (e.g. "with few items, retrieval trivially returns everything") — is NOT a second side: no row; the taught mechanism stays a business rule and the bound it enforces stays AC-tested. -- **`contradicted`** (course-contradicted) — the course itself set it inconsistently across lessons or between signature/docstring/call site (e.g. one distance metric in one lesson, another elsewhere; `k=3` in a signature but `5` in the call). A contradiction always elevates **provenance surfacing** — record it where the value is declared (in the default's own cell or body sentence, naming each side's source) and in CTX-C — but it earns a Ledger row only if it also passes the **stakes test, evaluated at initial choice time — before any build artifact exists**: would picking the other side *up front* change the system's structure, semantics, or a behavior guarantee (the `design-structural` litmus), or force rework? The cost of *changing the value later* (after data or artifacts exist) is §6 Ask First's business and is NEVER grounds for a row — nearly every value is expensive to change late, so late-change cost would promote everything. If yes → Ledger row. If no (near-equivalent, or a tunable knob) → a **body default**, not a row: pick the value by the **deterministic lever-value rule** — (1) the value the transcript narration states aloud in the lesson that introduces the concept; (2) if the narration names none or itself conflicts, the end-to-end application's config; (3) else the value used most often across the materials, earliest-taught on ties — cite the deciding branch next to the value, label it a **post-build lever**, and ask no build-start question about it. (For near-equivalent contradicted levers only, this rule takes precedence over Pass A's "app config is the recommended starting point.") Either way: any consistency invariant the contradiction threatens (e.g. one strategy across all stores, write and read) stays a business rule with an AC, and a lever whose change after data exists forces rework stays an Ask-First entry (§6). +- **`contradicted`** (course-contradicted) — the course itself set it inconsistently across lessons or between signature/docstring/call site (e.g. one distance metric in one lesson, another elsewhere; `k=3` in a signature but `5` in the call). A contradiction always elevates **provenance surfacing** — record it where the value is declared (in the default's own cell or body sentence, naming each side's source) and in CTX-C — but it earns a Ledger row only if it also passes the **stakes test, evaluated at initial choice time — before any build artifact exists**: would picking the other side *up front* change the system's structure, semantics, or a behavior guarantee (the `design-structural` litmus), or force rework? The cost of *changing the value later* (after data or artifacts exist) is §6 Ask First's business and is NEVER grounds for a row — nearly every value is expensive to change late, so late-change cost would promote everything. If yes → Ledger row. If no (near-equivalent, or a tunable knob) → a **body default**, not a row: pick the value by the **deterministic lever-value rule** — (1) the value the transcript narration states aloud in the lesson that introduces the concept; (2) if the narration names none or itself conflicts, the end-to-end application's config; (3) else the value used most often across the materials, earliest-taught on ties — cite the deciding branch next to the value, label it a **post-build lever**, and ask no build-start question about it. (For near-equivalent contradicted levers only, this rule takes precedence over Pass A's "app config is the recommended starting point.") Either way: any consistency invariant the contradiction threatens (e.g. one strategy across all stores, write and read) stays a business rule with an AC, and a lever whose change after data exists forces rework stays an Ask-First entry (§6). A worked example of a contradiction that **passes** the stakes test: a **storage-scoping choice** the course set both ways (records written with an owning-scope id in one lesson's code and without it in another's) — the two sides return different result sets to the same read, a semantics change at initial choice time, so it earns a row carrying both sides, rather than resolving as a lever. - **`realization`** — a heavy-dependency decision surfaced by the §3 dependency precedence (the row states which branch was taken and why). - **`learner`** — the learner-context dimensions (§3): project, data/inputs, goal, model/provider, environment, scope-boundary. @@ -175,12 +175,21 @@ The spec MUST contain these core sections, in this order (this is the seven-sect e.g. " chatbot" (precedence 1) or " app" (precedence 2). A learner retitles when they fill [project]. --> - + ## 0. Before you build — REQUIRED (do this first) @@ -297,10 +298,10 @@ A Decision Ledger that no one is shown is worthless. A build agent's default beh - **Imperative and build-agent-addressed.** "You are the build agent. Before writing ANY code, you MUST…" — not a passive "review before build" header (which reads as a note to a human skimming and gets skipped). - **First.** It is spec section `## 0`, ahead of everything including the Decision Ledger, visually isolated. -- **An express lane for the baseline builder, asked first — and named honestly.** The gate's FIRST question offers exactly two paths: the **recommended baseline build** (every Ledger row resolves to its Default), or **customize** the decisions row by row. The express path is named "recommended baseline build" — never "build as-is with the course's setup" or any phrasing presenting it as the course's own configuration — because on substitution rows (§3 branch 1) the Default deliberately departs from what the course ran, so a course-default framing misattributes those rows: the same provenance error the labeling rule below prevents at option level, recurring at path level. The baseline option's text must carry a one-line explanation stating both halves: most rows resolve to the course's own choices, and wherever the course's choice needs setup the learner may not have (a paid key, an admin-provisioned service), a lighter equivalent stands in — with the step-5 checklist marking exactly where. Choosing the baseline skips the per-row questions entirely — the agent prints the full resolved-decision checklist and begins building; choosing customize enters the one-question-per-row loop. This is NOT an escape clause (§12.10): the user gave an explicit answer that covers every row — the same "defaults are fine" reply the determinism argument already sanctions — and the printed checklist still makes completeness visible. What stays forbidden is proceeding on *silence*. +- **An express lane for the baseline builder, asked first — and named honestly.** The gate's FIRST question offers exactly two paths: the **recommended baseline build** (every Ledger row resolves to its Default), or **customize** the decisions row by row. The express path is named "recommended baseline build" — never "build as-is with the course's setup" or any phrasing presenting it as the course's own configuration — because on substitution rows (§3 branch 1) the Default deliberately departs from what the course ran, so a course-default framing misattributes those rows: the same provenance error the labeling rule below prevents at option level, recurring at path level. The baseline option's text must carry a one-line explanation stating both halves: most rows resolve to the course's own choices, and wherever the course's choice needs setup the learner may not have (a paid key, an admin-provisioned service), a lighter equivalent stands in — with the step-5 checklist marking exactly where. Choosing the baseline skips the per-row questions entirely — the agent prints and writes the full resolved-decision checklist and begins building; choosing customize enters the one-question-per-row loop. This is NOT an escape clause (§12.10): the user gave an explicit answer that covers every row — the same "defaults are fine" reply the determinism argument already sanctions — and the printed checklist still makes completeness visible. What stays forbidden is proceeding on *silence*. - **Presents EVERY Ledger row.** Do not have the gate re-filter or tier rows — the §5.5 surfacing bar already decided what became a row, so every row is by construction worth showing. (Never key the gate on row numbers; row identities are course-specific.) - **One question per row — do not depend on a channel's item cap.** Interactive question tools cap how many questions fit in one call (Claude Code's `AskUserQuestion` allows at most a few), and a Ledger can have many rows. If the gate says "present every row" without saying *how*, the agent improvises — batching inconsistently, or (worse) presenting a first batch, getting a reply, and treating the build as unblocked while the rest are never asked (§12.11). Fix the mechanism deterministically: **one row = one question**, looped until every row is asked. One-per-row has no batch boundary to mis-track and its completeness is a plain count (N rows ⇒ N questions). **Never hardcode a numeric cap** ("4") into the gate — that is one environment's limit; state the capacity-relative rule instead so it holds for an uncapped or plain-text channel too. -- **Checklist-echo gates the build, not "a reply happened".** Keying resumption on "the user replied" fails the moment many rows exist: a reply to *some* rows satisfies it, and the rest are buried. Require a **visible artifact** instead — before any code, the agent prints a checklist of every row with its resolved value. A completion-driven agent cannot produce that checklist without having resolved every row, and the user/reviewer can *see* completeness. A printed artifact beats an internal "did I ask N times?" count precisely because the same agent that skips rows is the one that would judge the count. The checklist also carries **deviation marks**: each row's line states whether its resolved value is the course's actual choice or departs from it — a substitution row resolved to its Default departs by construction; a customize answer may too — naming the course's choice on every departing row. And on the baseline path, the agent walks the learner through the departing rows alongside the checklist (each row's course choice, and the reason its Default substitutes — the row's own branch note carries it), so the express lane never hides where the baseline differs from the course. +- **Checklist-echo gates the build, not "a reply happened".** Keying resumption on "the user replied" fails the moment many rows exist: a reply to *some* rows satisfies it, and the rest are buried. Require a **visible artifact** instead — before any code, the agent prints a checklist of every row with its resolved value. A completion-driven agent cannot produce that checklist without having resolved every row, and the user/reviewer can *see* completeness. A printed artifact beats an internal "did I ask N times?" count precisely because the same agent that skips rows is the one that would judge the count. **Printed is not enough on its own, though** — screen output serves the human standing there at gate time and then is gone, so nothing downstream (an eval, a reviewer, the owner opening the delivered folder a week later) can tell a gated build from an ungated one. The checklist is therefore **printed AND written to a fixed-name file in the build folder** (`resolved-decisions.md`), which is what makes gate compliance mechanically checkable: file absent ⇒ the gate was skipped; rows short ⇒ rows were skipped. Fixed name, not the agent's choice — otherwise the record lands in a README, a build report, or nowhere, and no checker can find it. The file is a **record, not an input**: a later build — new or resumed — re-runs the gate from the spec and never reads a previous run's checklist (resuming from a stale record would silently build against decisions nobody re-confirmed), and each gate run overwrites it, so nothing accumulates. The checklist also carries **deviation marks**: each row's line states whether its resolved value is the course's actual choice or departs from it — a substitution row resolved to its Default departs by construction; a customize answer may too — naming the course's choice on every departing row. And on the baseline path, the agent walks the learner through the departing rows alongside the checklist (each row's course choice, and the reason its Default substitutes — the row's own branch note carries it), so the express lane never hides where the baseline differs from the course. - **Labels the course's actual choice "(course default)"; the build agent's own "(Recommended)" flag may coexist.** "(course default)" is factual provenance — what the course actually did — and must always mark the option the course actually used. On a **substitution row** (§3 dependency-precedence branch 1) that is the course-faithful **Options** entry, NOT the substituted Ledger Default: labeling the substitute "(course default)" misattributes it (observed in real learner feedback — a SQLite+Chroma default presented as the course's choice when the course ran Oracle). Separately, the build agent may mark an option "(Recommended)" per its own judgment or its question tool's convention (Claude Code's `AskUserQuestion` suggests exactly this) — that is advice about *this* learner's project, a different claim from provenance, and it is allowed. The two labels compose: when the agent's recommended option differs from the course's choice, both labels appear, each on its own option; when the recommended option IS the course's choice, use the single combined label **"(Recommended - course default)"**. A recommendation never removes or moves the "(course default)" label. The two labels also differ in **when their claims are made**: the Ledger's Default column is the *generator's* recommendation, fixed at generation time on zero-setup grounds (buildable as-is with nothing the learner must provision — the same grounds §3's precedence uses); "(Recommended)" is the *build agent's* judgment at gate time, made with the learner's context (their earlier gate answers; keys or services they actually hold). With no gate-time reason to depart, the agent recommends the row's Default; with one (say, the learner holds the key a keyed-tool row's course option needs), "(Recommended)" may land on any option — the label that never moves is "(course default)". **Model currency is the canonical case of this timing split**: the spec names providers, protocols, and the course's own models (provenance facts, which never stop being true), but never bakes in "current best" model names — those rot between generation and the learner's build; recommending what is current when the learner actually builds is the build agent's gate-time job, exercised through this flag. These two labels, plus their merged form, are the **only sanctioned option labels** anywhere the spec presents choices — gate questions and Ledger Options cells alike. Never label an option with a bare "(default)" or "(Default)": default-ness is carried by the Ledger's Default column, not by a label, and a third label re-opens the provenance-vs-advice confusion the two-label design exists to close (observed as direct owner confusion in review). - **A hard stop, with no escape clause.** Presenting the decisions must end the agent's turn: it stops and waits for the user's reply before writing any code or touching any file. Do **not** give it a "proceed if there's no response" release valve — that is the loophole that re-opens §12.10: the agent *always* trivially has "no response" the instant it finishes presenting, so it reads the valve as immediate permission and the ask becomes theater. A narrower "proceed if launched non-interactively" escape is the same hole, smaller — drop it too; a completion-driven agent steers into whatever escape exists. Determinism is **not** at risk from waiting: it comes from every row having one course-derived default (§3, Known-Trap #7), so a user who replies "defaults are fine" lands on the identical target. Waiting changes *when* the agent proceeds, never *what* it builds. (If a genuinely headless harness cannot answer, the absent reply stalls that build — the harness's constraint to resolve by pre-answering, not a hole the spec should pre-drill.) - **Structured tool required when available; still portable.** If the environment has an interactive structured-question tool (Claude Code's `AskUserQuestion` or an equivalent), the gate MUST require using it — it is the reliability lever, not a mere suggestion. Name specific tools only as *examples* of the class, never as a hard dependency: a non-Claude agent (Codex, Cursor, plain chat) with no such tool must still comply by listing the rows in its reply and asking. So the rule is "use a structured question tool **if you have one**, else ask in plain text" — mechanism-*required* where possible, mechanism-*agnostic* in fallback. @@ -347,14 +348,18 @@ not because they are the right choice for this person's project. an answer (a chosen option, an explicit "use the course default", or the step-2 baseline answer, which resolves every row at once). Answers to *some* rows do NOT release the build; "no reply yet" is not an answer — wait for the user. -5. **Before the first line of code, print a resolved-decision checklist** — every Ledger row - with its final value (the user's choice, or its Ledger default), each line carrying a - deviation mark: `= course choice`, or `≠ course choice (course used: