Skip to content

feat(learning-tier): item 1 — the mentor writes (S1-0 receipt) - #41

Merged
NetDevAutomate merged 10 commits into
mainfrom
feat/learning-tier-fed
Sep 24, 2026
Merged

NetDevAutomate merged 10 commits into
mainfrom
feat/learning-tier-fed

Conversation

@NetDevAutomate

@NetDevAutomate NetDevAutomate commented Sep 23, 2026 •

Copy link
Copy Markdown
Owner

Part of #38 — lands S1-0, S1-RED, S1-GREEN and council review 9. Does not close #38: S1-SIM (owner-gated harness logins) and the noticing episode stay open there, and decision 3 defines the noticing episode as the first real session after S1-GREEN reaches main, so holding this reviewed, CI-green tree on a branch until then would block the very stage it gates. The 2026-09-23 branch census (~244 finished commits that never landed) is the reason not to wait.

What this delivers

The mentor writes to the learning tier. Before this branch it only read: no MCP teach-back writer existed, the Kiro persona routed "record progress" to a different tool, and no harness had a trigger table — zero writer calls in 143,973 historical messages. Plan: docs/architecture/learning-tier/plan-2026-09-19.md §3; owner decisions 1–5 in §7 (PR #36).

Branch cut from main @ c1a28de1 (decision 1). Item 2 is not here (#39).

Stages

  • S1-0 Capability lock — fd70817c. docs/architecture/learning-tier/receipts/s1-0-capability-lock.md: grant mechanism per harness from source with file:line; W_auto / W_srs frozen; the record_teachback contract pinned to the CLI validator. Rule this branch: W_auto named in every definition, prompt-per-call everywhere, no new pre-approval on any harness (decision 2).
  • S1-RED — f439ab84. 22 red, each for its stated reason: test_mcp_teachback.py (13, tool absent), test_adapter_parity.py (2 red + 4 guards pinning decision 2), test_docs_harness_tier_contract.py (7: protocol absent, persona.md:32 still names tutor-checkpoint).
  • S1-GREEN — 7354ea3a + 0a22aabe + 3c175f78.
    • record_teachback MCP tool validating through one shared implementation extracted from cli/_teachback.py (the CLI delegates to it; its messages are unchanged). ToolError on any rejection or an unavailable database; a repeated call is two rows.
    • agents/shared/recording-protocol.md: fenced YAML trigger table (trigger, when, writer, required_ids, consent) naming exactly the four writers; SRS mutators never fire from it. Referenced by an identical sentence in every mentor definition; hashed in agents/manifest.json (six touched files changed hash, nothing else).
    • Claude tools: line names the readers and the four writers — naming, not approval; no permissions block anywhere. Kiro persona no longer routes to tutor-checkpoint. Teach-back protocol's Recording section puts MCP first with the consent rule. docs/contributing.md tells a harness author what the parity tests pin.
    • test_writer_isolation.py: a child process with a decoy HOME/XDG runs all four writers; nothing lands outside the sandbox. Passed on first run, so it is the plan §5 guard, not a RED.
    • Inventory pins 32 → 33 in the four places that pin the count by design, each naming the new tool.
  • Council implementation review 9 — 5670f941. Seats: astra ACCEPT-WITH-CORRECTIONS, grok ACCEPT, qwen REJECT (not sustained: its two 🔴 are refuted against the tree). Nine findings landed one concern per commit (ad6d1f9d, 019624c3, 2a4d4d1d, 616fd1ed), four refuted with evidence, arbitration GATE ACCEPT at 616fd1ed. Coordinator's own finding, found while verifying a seat's mechanism: every live session is built from agents/shared/personas/<mode>.md, not the installed definitions — study.md named no writer and told the mentor to show the learner a studyloop topic command. Now named and pinned on the BUILT persona (test_the_built_live_persona_names_each_writer); receipt §5 records the correction.
  • S1-SIM — scripted learner per installed harness; evidence bundles; grok's ten-item list (arbitration, gate decision) is the checklist, with the refusal path, Claude tools:-line reachability and installed-path resolution of agents/shared/… deciding {passed}. Owner-gated: harness logins.
  • Noticing episode (observation, not gate; decision 3: first real study session after S1-GREEN reaches main).

Finding N1 (corrected from the RED as first written)

The RED assumed the live study session id would land in teach_back_scores.session_id. The ownership layer forbids it: records.bind treats session_id as a native harness session that must exist in sessions and be visible in scope, and links study sessions only for parked_topics and study_notes — the write failed and the tool reported "not recorded". So the row is owned by scope exactly as the CLI's rows are, and the study session id travels in the tool's reply. Stated in the test and the GREEN commit, not erased.

Verification

Scoped: RED files + install contracts + isolation + CLI 84/84; MCP suites 100/100; inventory suites 180/180; stdio smoke 2/2. ruff, format, pyright clean on every touched file; mkdocs strict clean; .secrets.baseline updated by a whole-repo scan (only agents/manifest.json entries changed, 72 → 72 files).

Full pytest (unit, 15:43): 7340 passed, 35 failed, 14 errors. Failing ids minus the committed environmental set (full-suite-control-item4-2026-09-18.md) = the four inventory pins, fixed in 3c175f78, plus test_concatenated_remote_dump_with_existing_archive — the pre-existing sqlite3 -bail 60 s timeout in agent-session-tools recorded on #35, which fails identically on clean main.

Claim rule

The final PR body will state exactly "pipe open on {passed}; plumbing proven for all six definitions; noticing observed once", listing which harnesses passed and the skip reason for each of the rest. No skip is counted as a pass.

Gates before merge

CI green on the reviewed tree; the S1-SIM stage above.

…s from source, writer sets frozen

Item 1 (#38) stage S1-0. Records, with file:line on main c1a28de, how each of the six
harnesses grants tool use to the mentor today and freezes W_auto and W_srs. Written to the
owner decisions on PR #36: W_auto is named in every definition and prompt-per-call
everywhere, no new pre-approval on any harness; kiro keeps its log_topic entry as the one
recorded asymmetry; Claude names none of the studyloop MCP tools today so naming is what
makes the writers reachable; opencode wildcard recorded as known and out of scope.
Also pins the record_teachback MCP contract to the CLI validator it must mirror.
…erywhere, one recording protocol

Item 1 (#38) stage S1-RED, per plan §5 and the S1-0 receipt. 22 red, each for its stated
reason on main c1a28de:

- test_mcp_teachback.py (new, 13 red): the tool is absent from the MCP registry. Pins the
  contract: validates exactly as cli/_teachback.py (five ints 1-4, review_type enum), lands
  one row through the real migrated schema (CHECK present), no row on failure, session_id
  bound from session state and refused from the caller, a repeated call is two rows, a
  missing connection is a ToolError.
- test_adapter_parity.py (2 red, 4 guards green): no definition names the four W_auto
  writers; Claude tools: line names none. Guards pin decision 2 - kiro allowedTools keeps
  exactly log_topic, Claude settings.json has no permissions block, opencode block as recorded.
- test_docs_harness_tier_contract.py (7 red): agents/shared/recording-protocol.md absent,
  so no trigger table, no references, no manifest hash; persona.md:32 still routes
  "record progress" to tutor-checkpoint.

test_writer_isolation.py is deferred to GREEN (it needs record_teachback to exist to run
each W_auto writer; plan §5 says it is a guard, not RED, if it passes).
…ares

Item 1 (#38) S1-GREEN, part 1. The mentor had no MCP writer for a teach-back score;
the only writer was the CLI. record_teachback now validates through one shared
implementation (history.teachback.coerce_scores / coerce_review_type, extracted from
cli/_teachback.py with the CLI messages kept verbatim; the CLI delegates to it), lands
one row through history.record_teachback, raises ToolError on any rejection or an
unavailable database, and treats a repeated call as a second row.

Finding N1, corrected from the RED as first written: the RED assumed the live study
session id would land in teach_back_scores.session_id. The ownership layer forbids it -
records.bind treats session_id as a native harness session that must exist in
`sessions` and be visible in scope, and links study sessions only for parked_topics and
study_notes - so the write failed and the tool reported "not recorded". The row is
therefore owned by scope exactly as the CLI rows are, and the study session id travels
in the reply. The two RED expectations are rewritten to that, with the finding stated
in the test. 13/13 in test_mcp_teachback; CLI, history and the other MCP suites 100/100.
…o persona stops routing to tutor-checkpoint

Item 1 (#38) S1-GREEN, part 2. agents/shared/recording-protocol.md is the one
instruction that says WHEN the mentor writes: a fenced YAML trigger table (trigger,
when, writer, required_ids, consent) naming exactly the four W_auto writers, one line
of prose per trigger, and the rule that the SRS mutators never fire from it.

Every mentor definition now references it with an identical sentence (codex and pi
AGENTS.md, opencode and claude mentors; grok reads the codex file). Claude tools: line
names the studyloop readers and the four writers - naming, not approval: no permissions
block is added, per owner decision 2. Kiro persona.md no longer routes "record progress"
to uv run tutor-checkpoint (a different tool, left to its own skill); it names the MCP
writers and keeps the CLI as the human path. teach-back-protocol.md Recording section
puts the MCP tool first with the consent rule; contributing.md tells a harness author
what the parity tests will pin.

agents/manifest.json regenerated: hashes change for exactly the six touched files.
test_writer_isolation.py added as the plan §5 guard (passes first run, so not RED): a
child process with decoy HOME/XDG runs all four writers and nothing lands outside the
sandbox. S1-RED f439ab8 flips: 84/84 across the RED files, install contracts,
isolation and CLI.
Four deliberate exact-count pins guard the MCP registry against an accidental or
duplicate registration: test_mcp_plan_tools, test_mcp_stdio_smoke, the verify script
and its test, plus the MCP README table and its count sentence (docs contract). Each
names the new tool in its arithmetic comment rather than just bumping the number.
Found by the full-suite set comparison against the committed environmental ids.
…ilt from personas/*.md, not the installed definitions

Council review 9, coordinator finding while verifying grok Y2: studyloop study renders
agents/shared/personas/<mode>.md through build_canonical_persona and hands THAT to every
adapter (Kiro prompt, Claude flag file, session-dir AGENTS.md for Codex/pi/Grok). The six
installed definitions GREEN named the writers in are what a learner gets when opening the
harness directly. study.md told the mentor to show the learner a studyloop topic command and
never to write - the defect in its live form - and named no writer.

study.md gains a Recording section (four triggers, the protocol, the unsuccessful-write
line) and lists the writers under Progress & Review; co-study.md gets the same four with its
student-drives rule. test_adapter_parity pins the BUILT persona for study and co-study, not
a file; the docs contract adds both live personas to DEFINITIONS. Also landed here, same
files: grok Y2 (pin that Grok projects the Codex definition and that agents/grok/ does not
exist), astra Y2 (kiro guard also refuses wildcard grants; opencode block parsed and compared
whole, not substring-matched). S1-0 receipt §5 records both corrections as an addendum.
…failure honestly

Council review 9. grok Y5: NOT NULL does not stop "", so a blank concept or topic became a
row nothing can find again - refused at the boundary before any write, three parametrised
tests. grok Y1 (partially accepted): the writer returns False for no-database, lock/timeout
and refused-row alike and does not say which, so the ToolError now states the three rather
than claiming "unavailable". Splitting them properly needs the writer to distinguish - the
same collapse its CLI caller inherited - and is not done here. grok claimed ScopeError was
also swallowed; verified false: record_teachback catches only IntegrityError and
OperationalError, a ScopeError propagates.
…licit unsuccessful-write line

Council review 9. qwen R2 (partially accepted, not as a blocker): the teach_back_agreed
when-clause now names what is observable - five scores proposed in one sentence, the
learner next reply accepts them - instead of "the learner agreed them". astra Y5: a
section says what to do when the write does not happen (refusal, declined prompt, database
unavailable): one line, continue, never retry silently, never claim a record that did not
land. Manifest hash and secrets baseline regenerated for the one changed file.
Council review 9, astra Y4: "each writer landed" was asserted by a file existing. Now the
sandbox sessions.db is opened and teach_back_scores and parked_topics are counted (one row
each), and the topics file must carry the logged concept.
…ipts, arbitration (GATE ACCEPT at 616fd1e)

Three seats (openai.gpt-6-astra ACCEPT-WITH-CORRECTIONS, grok-4.6 ACCEPT, qwen3-coder REJECT)
on the item-1 tree 3c175f7. Every finding verified against the tree before action; nine
landed across ad6d1f9, 019624c, 2a4d4d1, 616fd1e; four refuted with evidence (qwen R1,
qwen Y, astra Y1, grok Y4 mechanism); qwen REJECT not sustained. The coordinator own finding
- the live persona is built from personas/*.md and named no writer - is recorded as a gap
none of the seats saw. Seat copies: git diff -w against the originals is empty; originals
sha256 prefixes recorded in the arbitration. grok (e) ten-item list adopted as the S1-SIM
checklist.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Critical contract and database-error-handling findings remain unresolved, along with persona and test-contract corrections.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 3 High severity · 4 Medium severity

Open (7)
What changed in this PR

Adds the learning-tier record_teachback MCP writer, shared validation, and mentor recording guidance across supported harnesses.

Changes:

  • Adds MCP persistence, validation reuse, and writer isolation coverage.
  • Updates recording protocols, personas, tool inventories, and manifests.
  • Adds capability-lock and council-review documentation.
File Summary
scripts/​verify/​plan_integration.py Updates the MCP inventory pin; the canonical OpenSpec inventory remains stale (nit).
scripts/​update-agent-manifest.py Tracks the shared recording protocol.
packages/​studyloop/​tests/​test_writer_isolation.py Adds sandbox isolation coverage for writers.
packages/​studyloop/​tests/​test_verify_plan_integration_script.py Updates inventory verification assertions.
packages/​studyloop/​tests/​test_mcp_teachback.py Tests teach-back behavior; transport-level coverage and shared database fixture reuse remain needed (moderate, critical).
packages/​studyloop/​tests/​test_mcp_stdio_smoke.py Updates stdio inventory coverage.
packages/​studyloop/​tests/​test_mcp_plan_tools.py Updates tool inventory assertions.
packages/​studyloop/​tests/​test_docs_harness_tier_contract.py Validates protocol contracts; the required when key remains unenforced (moderate).
packages/​studyloop/​tests/​test_adapter_parity.py Pins writer names and harness grants.
packages/​studyloop/​src/​studyloop/​mcp/​tools.py Implements record_teachback; operational database failures can still leak raw SQLite errors (critical).
packages/​studyloop/​src/​studyloop/​history/​teachback.py Provides shared teach-back validation.
packages/​studyloop/​src/​studyloop/​cli/​_teachback.py Reuses shared validation for the CLI.
docs/​contributing.md Documents harness parity requirements.
docs/​architecture/​learning-tier/​receipts/​s1-0-capability-lock.md Records capability-lock decisions.
docs/​architecture/​learning-tier/​council/​review9/​seat-qwen3-coder.md Records council review evidence.
docs/​architecture/​learning-tier/​council/​review9/​seat-openai.gpt-6-astra.md Records council review evidence.
docs/​architecture/​learning-tier/​council/​review9/​seat-grok-4.6.md Records council review evidence.
docs/​architecture/​learning-tier/​council/​review9/​manifest.json Records council run metadata.
docs/​architecture/​learning-tier/​council/​review-9-arbitration-2026-09-23.md Records arbitration and gate decisions.
docs/​architecture/​learning-tier/​council/​brief-review9-2026-09-23.md Records the review brief and scope.
agents/​shared/​teach-back-protocol.md Routes teach-back recording through MCP.
agents/​shared/​recording-protocol.md Defines writer triggers; its struggle condition conflicts with the prose requirement (moderate).
agents/​shared/​personas/​study.md Adds study-mode recording guidance; an unconditional CLI instruction conflicts with MCP writing (moderate).
agents/​shared/​personas/​co-study.md Adds co-study recording guidance; existing CLI instructions conflict with MCP writing (moderate).
agents/​pi/​AGENTS.md References the recording protocol.
agents/​opencode/​study-mentor.md References the recording protocol.
agents/​mcp/​README.md Documents the 33-tool inventory; the active OpenSpec contract still requires 32 tools (critical).
agents/​manifest.json Updates agent manifest hashes.
agents/​kiro/​study-mentor/​persona.md Replaces obsolete progress routing.
agents/​codex/​AGENTS.md References the recording protocol.
agents/​claude/​socratic-mentor.md Exposes the relevant readers and writers.
.secrets.baseline Refreshes generated secret-scan metadata.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread agents/mcp/README.md
## studyloop-mcp (Study tools)

The `studyloop-mcp` server exposes 32 MCP tools: courses and review cards, the study backlog and
The `studyloop-mcp` server exposes 33 MCP tools: courses and review cards, the study backlog and
Comment on lines +916 to +931
recorded = write_teachback(
concept=concept,
topic=topic,
scores=five,
review_type=kind,
angle=angle or None,
notes=notes or None,
)
if not recorded:
# The writer returns False for three different failures -- no database,
# a lock/timeout, a refused row -- and does not say which (its CLI caller
# inherited the same collapse). Say that, rather than claim one cause.
raise ToolError(
"teach-back not recorded: the sessions database could not take the write "
"(unavailable, locked, or it refused the row) -- run `studyloop doctor`"
)
Comment on lines +68 to +73
@pytest.fixture()
def scratch_db(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
"""A per-test sessions.db that the real connection resolver migrates."""
db = tmp_path / "sessions.db"
monkeypatch.setenv("STUDYLOOP_DB", str(db))
return db
Comment on lines +34 to +38
The same four triggers, when the student confirms rather than runs the command themselves, are yours to write
through the `studyloop-mcp` tools — `log_topic` at wind-down, `log_struggle` after two stuck rounds,
`record_teachback` only for scores the student agreed, `record_plan_learning` only for a plan they are working
against. `agents/shared/recording-protocol.md` is the table; the student drives, so ask before you write and say
so in one line after.
Comment on lines 36 to +53
**When logging, show the command inline.** Example: "That's a win — run: `studyloop topic "Decorators" --status win --note "grasped wrapping pattern"`"

## Recording — what you write yourself

`agents/shared/recording-protocol.md` is the one table that says when *you* write to the
learning tier, through the `studyloop-mcp` tools (they may prompt the learner to approve the call;
that is the harness's setting, not yours to change). Four writers, four triggers:

- a teach-back has ended, you proposed five rubric scores, and the learner **agreed** them →
`record_teachback` (concept, topic, the five scores in order, the review type). Not agreed, not recorded.
- two rounds stuck on one point → `log_struggle` (the question)
- wind-down, one call per concept touched, with the status the learner confirms → `log_topic`
- the session ran against an active plan and the learner agrees one line for its record → `record_plan_learning`

A turn that matches none of these writes nothing. After a write, one short line — *"Recorded: window frame,
structured, 15/20."* — then the next question. If the tool refuses or the learner declines the prompt, say so in
one line and continue; never retry silently. The `studyloop topic` commands above remain the learner's own,
visible path to the same rows.
Comment on lines +191 to +193
REQUIRED_KEYS: ClassVar[frozenset[str]] = frozenset(
{"trigger", "writer", "required_ids", "consent"}
)
Comment on lines +46 to +51
def _get_tool(name: str):
tools = mcp._tool_manager._tools
if name not in tools:
raise KeyError(f"Tool {name!r} not found. Available: {sorted(tools)}")
return tools[name].fn

@NetDevAutomate
NetDevAutomate merged commit 6b6a35b into main Sep 24, 2026
31 of 32 checks passed
@NetDevAutomate
NetDevAutomate deleted the feat/learning-tier-fed branch September 24, 2026 07:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Learning tier item 1: the mentor writes (record_teachback tool, trigger table on all six harnesses, scripted-learner proof)

2 participants