Agent consent: the pause contract + false-receipt kill (2 fixes, stricter only) #79

Open
tim.lingo wants to merge 3 commits from feat/agent-phase1-soul into main
Member

Rebased clean onto the regenerated main (zero conflicts). Two consent-flow fixes, both verified E2E on the clean test brain:

1. PAUSE CONTRACT — honor require_approval. The client has sent require_approval:true on every agentic request since Phase 1c, but needs_bridge never consulted it: builtin sub-escalate tools ran server-side unasked, making the app's Ask autonomy silently inert for that class. The flag is now persisted per session (so /approve resumes keep it) and ask_all bridges every tool turn. Absent/false = byte-identical legacy behavior. Proven: the probe that executed a write unasked now returns the pause envelope with nothing on disk; full in-app circle verified (card → crash → resurrection → late approve → fence re-fires).

2. BUG-6 — approved writes must land, and say where. Two compounding defects made every pause→approve write_file report success while writing nothing: (a) the naive json_get matched "content" inside tool_input, so approved builtin writes were treated as client-executed and skipped; (b) write_file returned ok unconditionally. Builtins now always dispatch server-side; write_file verifies fs_exists and returns the resolved path. Proven: approve-path write lands byte-exact; auto-run unchanged; deny executes nothing.

Also carried: per-session workspace-root isolation (BUG-LEAK) — sessions can no longer inherit another session's root via the shared state key; re-asserted on /approve and resume. Live proof pending a local rebuild (below).

Ask: local full-source rebuild now compiles all 49 modules (your elc OOM fix works — thank you) but links one symbol short: engram_get_node_by_label (+ friends) aren't in any el_runtime.c we have locally. Where does the current runtime C live? With it we can E2E the root-isolation fix locally too.

Deeper flag: the naive json scanner is a standing landmine (BUG-6 was one instance) — a nesting-aware reader would kill the class.

🤖 Generated with Claude Code

Rebased clean onto the regenerated main (zero conflicts). Two consent-flow fixes, both verified E2E on the clean test brain: **1. PAUSE CONTRACT — honor require_approval.** The client has sent require_approval:true on every agentic request since Phase 1c, but needs_bridge never consulted it: builtin sub-escalate tools ran server-side unasked, making the app's Ask autonomy silently inert for that class. The flag is now persisted per session (so /approve resumes keep it) and ask_all bridges every tool turn. Absent/false = byte-identical legacy behavior. Proven: the probe that executed a write unasked now returns the pause envelope with nothing on disk; full in-app circle verified (card → crash → resurrection → late approve → fence re-fires). **2. BUG-6 — approved writes must land, and say where.** Two compounding defects made every pause→approve write_file report success while writing nothing: (a) the naive json_get matched "content" inside tool_input, so approved builtin writes were treated as client-executed and skipped; (b) write_file returned ok unconditionally. Builtins now always dispatch server-side; write_file verifies fs_exists and returns the resolved path. Proven: approve-path write lands byte-exact; auto-run unchanged; deny executes nothing. **Also carried:** per-session workspace-root isolation (BUG-LEAK) — sessions can no longer inherit another session's root via the shared state key; re-asserted on /approve and resume. Live proof pending a local rebuild (below). **Ask:** local full-source rebuild now compiles all 49 modules (your elc OOM fix works — thank you) but links one symbol short: engram_get_node_by_label (+ friends) aren't in any el_runtime.c we have locally. Where does the current runtime C live? With it we can E2E the root-isolation fix locally too. **Deeper flag:** the naive json scanner is a standing landmine (BUG-6 was one instance) — a nesting-aware reader would kill the class. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
tim.lingo added 3 commits 2026-07-17 14:09:53 +00:00
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two consent-flow fixes, gates only get stricter:

1. PAUSE-CONTRACT: the client has sent require_approval:true on every
   agentic request since Phase 1c, but needs_bridge never consulted it —
   builtin sub-escalate tools ran server-side unasked, making the app's
   Ask autonomy silently inert for that whole class. Now the flag is
   persisted per session (set/reset every request, so /approve resumes
   keep it) and ask_all bridges EVERY tool turn. Absent/false = behavior
   byte-identical to before. E2E: the exact probe that executed a write
   unasked now returns the tool_pending envelope with nothing on disk;
   full in-app circle verified (card → crash → resurrection → late
   approve → fence re-fires on re-entry).

2. BUG-LEAK: agent_workspace_root lived in ONE shared state key — any
   request that omitted a root inherited the previous session's folder
   (proven: a rootless curl session wrote into another session's run
   folder). Root is now stored per session and every request re-asserts
   its own (possibly empty) root into the shared key the guards read;
   same re-assert on the /approve and resume paths. Env fallback intact.
   LIMITATION: assumes serialized handling; true per-call scoping means
   threading session_id through dispatch — flagged for review.

Runnable C-patch for the test brain: neuron-container-build/
soul-pause-contract-20260716.patch (pause-contract only; the leak fix
needs the #23 root-write which the running C predates — source carries
both for the regen).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fix(engine): BUG-6 — approved writes must land, and say where (false-receipt kill)
Neuron Soul CI / build (pull_request) Successful in 6m50s
Neuron Soul CI / deploy (pull_request) Has been skipped
4171aadfff
Two compounding defects made every pause->approve write_file report success
while writing NOTHING:

1. The naive json_get scanner matches "content" anywhere in the approve
   body — including INSIDE tool_input, which for write_file always carries
   a content field. The handler therefore treated every approved builtin
   write as already-client-executed, skipped dispatch entirely, and handed
   the model the file's own content as the 'tool result'. The model then
   narrated 'Done, created' — a false receipt with no file. Builtin tools
   now ALWAYS dispatch server-side; client content is only honored for
   non-builtin (MCP/client-executed) tools. Stricter only.

2. write_file returned {"ok":true} unconditionally — fs_write's outcome
   was never checked, so any failed write also reported success. The write
   now verifies the file landed (fs_exists) and returns the RESOLVED path
   in the ok payload; failures return a real error naming the destination.

E2E on the test brain (boot 38): approve-path write lands byte-exact and
the result carries the resolved path; auto-run writes unchanged; denied
writes execute nothing. BUG-5 (approve wire lacked tool_name) had been
masking this one — two stacked bugs on the same path.

NOTE for review: the deeper cure is a nesting-aware json reader; this fix
removes the dangerous consequence at the two spots that lie about disk.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
All checks were successful
Neuron Soul CI / build (pull_request) Successful in 6m50s
Neuron Soul CI / deploy (pull_request) Has been skipped
You are not authorized to merge this pull request.
This pull request can be merged automatically.
This branch is out-of-date with the base branch
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin feat/agent-phase1-soul:feat/agent-phase1-soul
git checkout feat/agent-phase1-soul
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: neuron-technologies/neuron#79