Repository navigation
feat(server): contextmd serving module serves stored rules through harness hooks - #220
Conversation
…rness hooks Thin Claude Code, Codex, Grok Build and OpenCode hooks post each moment to POST /contextmd/v1/moment. The module matches it against ContextMD's stores (a TypeScript port of PR #23's matcher), resolves the running Chromeria run, decides what to serve or deny within the per-run 8,000 and 2,000-byte budgets, and reserves it until the hook settles. Must-know denies lift on an observed read (pointer rules) or a proven whole delivery (inline rules), cap at 3, and re-arm after compaction; a deny another hook's reason replaced stays armed. State and the serving log live in <stateDir>/contextmd.sqlite. Hooks authenticate as an ordinary Chromeria session with orchestration:read. Refs #217 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Thread transfer impact✅ Thread transfer remains within every enforced ceiling.
Baseline: Scenario and decoded snapshot size10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.
Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed. |
|
Independent review (Codex All 50 scoped tests passed, but the following blocking defects remain.
|
- A state file that cannot be opened disables serving instead of failing Chromeria startup. - A deny counts toward the cap only once shown (on settle, or on projection evidence); outstanding and dropped deliveries never spend it. - Projection evidence reads only the denied call's output and needs the whole delivered rule; each deny is adjudicated once. - The request body is capped while it is read, chunked bodies included. - A delivery id decided while another request was selecting gets the same reply. - A settle after the harness's deadline releases the reservation; OpenCode, which settles when the model reads the text, gets 120 s. Refs #217 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Independent review (Codex All 58 scoped tests passed. The startup, bounded-body, concurrent-replay, atomic-adjudication, and late-settlement fixes address their original failure mechanisms. Epoch-specific claim updates prevent late evidence from lifting a deny in a newer compaction epoch.
|
- Deny evidence reads each projected item type's result field (output for commands and tools, diffStr for file edits) and only once the item is finished; an unfinished item leaves the deny pending. - Settled denies awaiting evidence age out after 60 s on their own, including ones without a tool-use id, as `unverified` (counted toward the cap); the log shows `awaiting_evidence` until a verdict instead of `injected`. - Without a failure signal (Codex) a read counts only when the output shows the file's first non-empty line. Refs #217 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Independent review (Codex All 60 scoped tests pass. Fixes 2–5 address their reported mechanisms; fix 1 remains incomplete and introduces a regression.
|
A Codex read now counts per file: no output line may report an error for that file, and the output must hold one of the file's own lines (at least 12 characters, not shared with the other files the command named). Shared headings and other files' text no longer prove a read; partial reads (`sed -n`, `head`, `grep -n`) qualify through the lines they print. Refs #217 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Independent review (Codex All 60 scoped tests pass. The reported mixed-file,
No additional blocking findings in the rest of the module. |
…exit code Replaces the output-matching heuristic from rounds 2 and 3 (review round 4). Codex tells its hook nothing about failure, but the projection records each command's status and exit code. A read without a failure signal is now held pending and counts, lifting its pointer rules, only once the projected command item finished with exit code 0; a failed call discards it, and an unproven one is dropped after 60 s. Selection only names the files. Refs #217 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Independent review (Codex All 62 scoped tests pass. Read-only checks confirmed multi-file confirmation, original-epoch isolation after compaction, expiry of unproven reads after 60 seconds, and immediate lifting for successful Claude, Grok and OpenCode reads.
|
- Projected items are found by their exact native item id (`nativeItemRef.nativeId`, equal to the hook's tool-use id on every harness) for both deny and read evidence, never by a suffix pattern. - A read is proven only by a finished command item with an explicit exit code 0; a missing exit code proves nothing. - A read without a failure signal and without a tool-use id can never be checked and does not count. - A printed non-zero exit routes error keys and the failure seam, but no longer decides whether files were read: that stays the harness's report. Refs #217 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Independent review (Codex All 64 scoped tests pass. The four round-5 blocking findings are cleared; an additional cross-host check confirmed that printed
The stated Claude masked-exit trade-off remains a limitation: successful completion of the whole shell call does not prove every reader inside it succeeded. Under the accepted call-level read heuristic, not treated as a new blocker. |
…rness hooks (#220) * feat(server): contextmd serving module serves stored rules through harness hooks Thin Claude Code, Codex, Grok Build and OpenCode hooks post each moment to POST /contextmd/v1/moment. The module matches it against ContextMD's stores (a TypeScript port of PR #23's matcher), resolves the running Chromeria run, decides what to serve or deny within the per-run 8,000 and 2,000-byte budgets, and reserves it until the hook settles. Must-know denies lift on an observed read (pointer rules) or a proven whole delivery (inline rules), cap at 3, and re-arm after compaction; a deny another hook's reason replaced stays armed. State and the serving log live in <stateDir>/contextmd.sqlite. Hooks authenticate as an ordinary Chromeria session with orchestration:read. Refs #217 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(fork): register the contextmd serving feature Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(server): contextmd serving review round 1 fixes - A state file that cannot be opened disables serving instead of failing Chromeria startup. - A deny counts toward the cap only once shown (on settle, or on projection evidence); outstanding and dropped deliveries never spend it. - Projection evidence reads only the denied call's output and needs the whole delivered rule; each deny is adjudicated once. - The request body is capped while it is read, chunked bodies included. - A delivery id decided while another request was selecting gets the same reply. - A settle after the harness's deadline releases the reservation; OpenCode, which settles when the model reads the text, gets 120 s. Refs #217 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(server): contextmd serving review round 2 fixes - Deny evidence reads each projected item type's result field (output for commands and tools, diffStr for file edits) and only once the item is finished; an unfinished item leaves the deny pending. - Settled denies awaiting evidence age out after 60 s on their own, including ones without a tool-use id, as `unverified` (counted toward the cap); the log shows `awaiting_evidence` until a verdict instead of `injected`. - Without a failure signal (Codex) a read counts only when the output shows the file's first non-empty line. Refs #217 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(server): contextmd serving review round 3 fixes A Codex read now counts per file: no output line may report an error for that file, and the output must hold one of the file's own lines (at least 12 characters, not shared with the other files the command named). Shared headings and other files' text no longer prove a read; partial reads (`sed -n`, `head`, `grep -n`) qualify through the lines they print. Refs #217 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(server): contextmd serving confirms Codex reads by the projected exit code Replaces the output-matching heuristic from rounds 2 and 3 (review round 4). Codex tells its hook nothing about failure, but the projection records each command's status and exit code. A read without a failure signal is now held pending and counts, lifting its pointer rules, only once the projected command item finished with exit code 0; a failed call discards it, and an unproven one is dropped after 60 s. Selection only names the files. Refs #217 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(server): contextmd serving review round 5 fixes - Projected items are found by their exact native item id (`nativeItemRef.nativeId`, equal to the hook's tool-use id on every harness) for both deny and read evidence, never by a suffix pattern. - A read is proven only by a finished command item with an explicit exit code 0; a missing exit code proves nothing. - A read without a failure signal and without a tool-use id can never be checked and does not count. - A printed non-zero exit routes error keys and the failure seam, but no longer decides whether files were read: that stays the harness's report. Refs #217 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
What changed: A fork-owned
contextmdmodule in the server serves ContextMD's stored rules to agents in Chromeria threads: thin harness hooks post each moment toPOST /contextmd/v1/moment, and the module matches it, resolves the running run, decides what to serve or deny, and logs it.Why: Agents act without rules that already exist; serving state has to live where the turn is known, and the probe showed a Chromeria endpoint is faster than a file-based hook and joins every moment to its thread and run.
So what: Independent review, then merge under tonight's authority; then the planner installs the hooks and runs a live check. Closes #217.
How it works
ContextmdRoute.ts): authenticates with Chromeria's ownEnvironmentAuth(an ordinary bearer session withorchestration:read, issued byt3 auth session issueat install) and calls the service.Select.ts,Matcher.ts,Store.ts,Reads.ts,Harnesses.ts): a TypeScript port of ContextMD PR feat(web): configure Prism role preferences and view capacity #23's matcher. Entries stay Markdown data files in ContextMD's stores, named by~/.config/contextmd/serving.json(orCONTEXTMD_SERVING_CONFIG) and read on every moment, so knowledge edits need no release. Harness profiles hold the canary-proven seams per harness.ServingState.ts): its own<stateDir>/contextmd.sqlite, synchronous short transactions,busy_timeout100. Per-run 8,000 and 2,000-byte budgets; reserve, then settle; unsettled reservations release after 2 s asdelivery_unverified; repeated delivery ids replay. Must-know denies: pointer rules lift on an observed successful read of the target, inline rules on a proven whole delivery, cap of 3, re-armed by compaction. Grok carries. One log row per moment and per candidate, no prompt, command or result text.hidden, stays armed and does not count toward the cap.server.ts; one partial index on the run-attempts table, created at runtime after activation; a startup self-check disables the module (unmapped) if the attempt columns ornativeThreadIddisappear.Proof (head of this PR)
vp test run apps/server/src/contextmd/: 64 passed at head4ec3128b8b(state core: budgets, deny composition, lifts, cap, parallel waves, killed-hook release, lock holder, epochs, carries, evidence; route: run join across a steering restart,unjoined/ambiguous, auth, oversize, replay, bad event/config, store reload, self-check; matcher: PR feat(web): configure Prism role preferences and view capacity #23's segment and store cases, per-harness selection on captured event shapes, printed exits, reads).tsc --noEmit(no errors in the module),vp lint,vp fmt --check,knip --exportsforapps/server: clean for the changed scope. Repo-wide checks are CI's.tests/test_contract.py, PR feat(web): configure Prism role preferences and view capacity #23's hook-seam tests ported): 17/17 through the real hook against a dev server built from this head.~/.t3untouched), every served token quoted before the next action:cmd; echo "exit=$?")hidden, re-denied, then showncat)OpenCode obedience rerun (finding 3): the commit scenario 6/6 complied with rule text in the user message, against 4/6 in the obedience test; no distrust language in any transcript (small n; Chromeria instead of headless CLI).
Latency: 3 ms server time per moment sequentially; under a 48-call wave 110 ms p95 server, 188 ms p95 hook wall time (hook deadline 1 s; Grok fails open at 5 s).
Independent review: six rounds by Codex
gpt-6.1-sol(findings and verdicts in the comments below); round 6 PASS with two non-blocking notes (readers called by path such as/bin/cat; an abandoned OpenCode reply holds budget for up to 120 s), tracked as follow-ups.Review-driven changes since the first head: serving disables itself instead of failing startup; denies count toward the cap only once shown; deny evidence reads the projected item's result field, by exact native item id, only once finished; overdue evidence ages out as
unverified; the body is capped while read; concurrent same-id deliveries share one reply; late settles are refused; Codex reads count only after the projected command exited 0.Limits
cat RULE.md; echo "exit=$?"with a failingcatcounts as a read, because the harness reports the call as successful.Elon record
compactionitem dependency (harness hook events cover all four), the router client until test(brand): expect the Chromeria web icons in production builds #13.Built by Claude Opus 5.5 in Claude Code.
🤖 Generated with Claude Code