Skip to content

Close executor write-gate gaps and fix activity attribution - #576

Merged
thellert merged 4 commits into
mainfrom
fix/write-gates-and-activity-attribution
Aug 12, 2026
Merged

Close executor write-gate gaps and fix activity attribution#576
thellert merged 4 commits into
mainfrom
fix/write-gates-and-activity-attribution

Conversation

@thellert

Copy link
Copy Markdown
Collaborator

Summary

Two safety fixes and two cleanups around the agent-activity surfaces.

Fixes

  • Executor write gates (fix(executor)): execution_mode was an unvalidated free string. A value outside readonly/readwrite (e.g. "ReadWrite") satisfied neither write gate and ran write-pattern code even with control_system.writes_enabled=false; execute_file additionally had no deployment-level kill switch at all. Unknown modes are now rejected at the tool boundary and both tools share one deployment writes gate (_execution_gates.py).
  • Activity attribution (fix(artifacts)): the store-level activity listener reported every artifact delete as an agent action — including a person deleting from the gallery UI and the dispatch worker's retention sweep. Store mutations now carry a contextvar actor tag (default agent); the gallery route tags human, the retention sweep tags system, and the listener drops non-agent events.

Cleanups

  • Emit thread-hop dedup (refactor(mcp)): 16 emit sites hand-rolled anyio.to_thread.run_sync(functools.partial(notify_agent_activity, ...)); they now await a single notify_agent_activity_async helper. The off-the-event-loop property is pinned once at the helper's test. The authoring/draft emits keep their sync wrappers because they bundle a config read on the worker thread.
  • panel-manager.js split (refactor(web)): the rail's agent-attention surface (glow, badges, localStorage ack contract) moved to panel-agent-attention.js, the status-bar health readout to panel-status-bar.js, and the file-specific eslint max-lines override is gone — the module is back under the shared 450 cap.

Testing

  • New unit tests for the mode-boundary rejection, the execute_file kill switch, and actor-tagged deletes at the listener, gallery-route, and retention-sweep seams; the emit-site suite's uncanonical-mode test now asserts rejection instead of pass-through.
  • tests/mcp_server + tests/agent_runner + tests/hooks: 2251 passed locally; interfaces/dispatch suites green.
  • Front-end: eslint + tsc clean, 767 vitest tests, 13 Playwright agent-activity browser tests.

Reject execution_mode values outside {readonly, readwrite} at the tool
boundary: an unrecognized spelling satisfied neither write gate and ran
write-pattern code even with control_system.writes_enabled=false. Apply
the deployment-level kill switch in execute_file, which had no such
check, and share both gates between the two executor tools.
The activity listener emitted an agent-attributed frame for every store
delete, so a person deleting from the gallery UI and the dispatch
worker's retention sweep both appeared as agent actions in the web
terminal. Store mutations now carry an actor tag (default "agent");
the gallery route tags its deletes "human", the retention sweep tags
its deletes "system", and the activity listener drops non-agent
events.
Every async emit site hand-rolled
anyio.to_thread.run_sync(functools.partial(notify_agent_activity, ...)).
Add notify_agent_activity_async as the single thread-hop helper and call
it from all 16 sites. The off-the-event-loop property is pinned once at
the helper's own test instead of per tool; the authoring/draft emits
keep their sync wrappers because they bundle a config read on the
worker thread.
panel-manager.js had grown past the interface-JS max-lines cap and ran
under a file-specific eslint override. Move the rail's agent-attention
surface (transient glow, persistent badges, the localStorage ack
contract) to panel-agent-attention.js and the status-bar health readout
to panel-status-bar.js, and drop the override — the module is back
under the shared cap.

register_artifact_delete_listener(record_actor)
try:
assert client.delete(f"/api/artifacts/{entry.id}").status_code == 200
@thellert
thellert merged commit 8eb5c27 into main Aug 12, 2026
46 checks passed
@thellert
thellert deleted the fix/write-gates-and-activity-attribution branch August 12, 2026 20:16
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.

1 participant