Implement fan-out subagent guard middleware#226
Open
KunjShah95 wants to merge 3 commits into
Open
Conversation
The small file-backed stores (Inbox, inbox routing, mention threads, channel subscriptions, unrouted/dead-letter, unattended flags, per-persona/per-session connection maps, and user risk overrides) each loaded their JSON file with an unguarded json.loads inside __init__ — and every one is constructed eagerly in SessionManager.__init__. So a single truncated or malformed file turned into an exception that aborted server startup entirely, recoverable only by finding and deleting the file by hand. Several of the same stores also wrote their files non-atomically (write_text in place), which is exactly what produces the corrupt file after a crash/kill/disk-full mid-write. This generalizes the resilient pattern already used inline by ChannelBuffer and WorkspaceTrustStore into two shared helpers (coworker/jsonstore.py): - read_json(): missing/corrupt file -> caller default, moving the bad file aside to <name>.corrupt so it is neither lost nor re-hit on the next boot. - write_json_atomic(): temp sibling + os.replace, atomic on POSIX and Windows. Every affected store now uses them. Adds tests covering the helpers and each store's construct-from-corrupt-file path plus a save round-trip. Related to the WakeStore-specific report in andrewyng#158 (that fix, andrewyng#159, covers only selfwake.py); this addresses the same failure mode across the rest of the persistence layer. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add GuardMiddleware layer in the engine's authorization flow to enforce safety policies for tool calls, limit concurrent subagents, and log all decisions for audit. - GuardRuleSet: load YAML rules, evaluate tool calls with command match patterns (contains/prefix/regex), argument matching, and fan-out limits - GuardLogger: dedicated audit log with timestamped ALLOW/DENY entries - GuardConfigLoader: load/cache YAML config with change detection for hot reload - GuardMiddleware: layered authorization combining PermissionEngine, GuardRuleSet, and GuardLogger; fail-safe on exceptions - Engine integration: inject guard_middleware parameter into TurnEngine, route tool authorization through middleware, track start/end for fan-out counting - Agent wiring: build GuardMiddleware for code agents with config path configurable via COWORKER_GUARD_CONFIG env var - Default config/guard.yaml with safety rules (block recursive deletes, limit explorer fanout to 3 concurrent) - 31 unit tests for all guard components (all passing)
… tests - track_start() now returns bool: checks the fanout limit BEFORE incrementing the counter, returns False if max_concurrent would be exceeded - GuardMiddleware.track_start() returns bool, logs denied decisions for audit - Engine concurrent/serial paths check track_start return value and deny tools that exceed the fanout limit (TOOL_FINISHED with status="denied") - Added is_fanout_block field to GuardDecision for robust fanout detection - New integration test: 3 concurrent tools, max_concurrent=2, threading.Barrier proves first two run concurrently while third is denied by fanout limit - New integration test: broken ruleset raises during evaluate(), middleware fails safe (needs_user=True), engine emits PERMISSION_REQUIRED - Fixed _BrokenRuleset in test_guard.py (duplicate methods, missing return) - Added fail-safe integration test for middleware exception path
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implement guard middleware for fan-out subagent tool calls to enforce safety policies, limit concurrent subagents, and log all decisions for audit. Implements the plan in
docs/superpowers/plans/2026-07-26-fanout-subagent-guardrails.md.Changes
New Files
is_fanout_blockfor robust detectionModified Files
Integration Tests (test_engine.py)
_engine()and_bare_engine()to acceptmode=andguard_middleware=kwargs_guard_engine()and_guard_bare_engine()delegate to base helpers (eliminated ~50% boilerplate)Architecture
The GuardMiddleware layers on top of the existing PermissionEngine:
Testing
All 31 unit tests + multiple engine integration tests pass covering: empty ruleset, deny/allow rules, command match patterns, argument matching, fan-out limits (block after max, concurrent Barrier test, serial tracking), YAML loading, log output, config caching, middleware integration, fail-safe on exceptions, config hot reload, and refactored engine helpers.