Problem
#800 introduced phase-based file restrictions enforced via readonly mounts and commit-time gateway validation. These restrictions are phase-scoped — the implement phase blocks .egg-state/ directories but allows modification of any code file. This means an implement agent working on task A can accidentally modify files that belong to task B (or files not in the plan at all), creating unexpected cross-contamination when multiple agents work in parallel (Tier 3 dispatch).
Goal
Make the planner's per-task files list an enforced boundary, not just informational context. When the orchestrator spawns an implement agent container, the gateway should restrict that agent's commits to only the files listed in its assigned tasks.
Design Philosophy: Guide, Don't Cage
These restrictions exist to prevent accidental cross-contamination, not to micromanage the agent. The enforcement must be generous enough that an agent with a reasonable plan never gets stuck. An agent spinning because it can't touch a file it legitimately needs is worse than an agent that touches one extra file.
Principles:
- Warn before block: First violation logs a warning; repeated violations on the same file block. Give the agent a chance to self-correct.
- Generous matching: If the plan lists
src/auth/login.py, the agent should also be able to create src/auth/login_helpers.py in the same directory. Directory-level implicit allowance.
- Graceful fallback: Empty
files_affected = no per-file restriction. Missing or malformed entries are skipped, not fatal.
- Escape hatch: If the agent genuinely needs a file outside its allowlist, it can signal via
egg-contract (e.g., egg-contract request-file --path <file> --reason <why>). This logs the request and auto-approves (or queues a HITL decision for strict mode). The point is observability, not hard blocking.
- Planner responsibility: The planner should list files generously using globs. The enforcement catches mistakes, it doesn't compensate for a bad plan.
Current Architecture
The plumbing is mostly in place:
- Plan template (
docs/templates/plan.md) already requires files: per task in the YAML block
- Plan parser (
shared/egg_contracts/plan_parser.py) already extracts these into files_affected on the Task model
PhaseFileRestriction (gateway/phase_filter.py) already supports allowed_patterns with glob matching — it's just not populated for implement phase
- Gateway commit validation (
gateway/gateway.py:737-805) already calls check_phase_file_restrictions() on every push
- Post-agent commit (
gateway/post_agent_commit.py) already filters files by phase restrictions before auto-committing
What Needs to Change
1. Session model — add allowed_files field
Add an allowed_files: list[str] | None field to Session in gateway/session_manager.py. When set, these augment the phase's allowed_patterns during commit validation. When None (non-pipeline sessions, non-implement phases), behavior is unchanged.
Also accept allowed_files in register_session().
2. Container spawner — read task files, pass to session
In orchestrator/container_spawner.py, when spawning an implement agent:
- Collect
files_affected from all tasks assigned to this agent (union across tasks in the phase)
- Auto-expand entries: For each listed file
dir/foo.py, implicitly add dir/ as an allowed directory prefix so sibling files (new helpers, test files) are reachable
- Pass the combined list as
allowed_files to gateway.register_session()
3. Gateway commit/push validation — warn-then-block enforcement
In gateway/gateway.py, when validating a push for an implement-phase session that has allowed_files set:
- Build a
PhaseFileRestriction that combines the existing implement-phase blocked_patterns with allowed_patterns derived from the session's allowed_files
- Use glob/directory-prefix matching (not exact match) so
src/module.py in the plan covers the file even if nested or renamed slightly
- First violation per file: Log a structured warning (visible in agent logs and checkpoint), allow the push
- Second violation for the same file: Block with an actionable error explaining which task's
files_affected the agent should check, and how to request an exception
4. Post-agent commit — respect per-session restrictions
gateway/post_agent_commit.py needs access to the session's allowed_files to filter uncommitted changes the same way pushes are filtered. Files outside the allowlist are restored (not committed) but this should be logged clearly — silent drops cause confusion.
5. Plan template — document enforcement semantics
Update docs/templates/plan.md and planner prompt to clarify that files: is an enforcement boundary, not just a hint. The planner should:
- List files generously (include test files, config files the task will touch)
- Use glob patterns where appropriate (e.g.,
src/components/*.tsx, tests/)
- Prefer directory-level globs over individual files when the task touches multiple files in a directory
- Understand that the agent gets automatic directory-sibling access, so exact exhaustive listing isn't required
Edge Cases to Handle
- Empty
files_affected: If the planner omits files for a task (or it's empty), fall back to current behavior (no per-file restriction, only phase-level). Don't lock the agent out of everything.
- Glob patterns in file lists: Support
* and ** in files_affected entries so the planner can say tests/** instead of listing every test file.
- New files in allowed directories: Agent creates a file not explicitly listed but in a directory that contains listed files. This should be allowed — the directory-sibling expansion covers it.
- Shared files: Multiple tasks may list the same file. Each agent's session should include the union of files from its assigned tasks.
- Tester/reviewer agents: This restriction only applies to implement-phase coder agents. Tester and reviewer agents already have their own role-based restrictions.
- Config files: Common config files (
pyproject.toml, package.json, Makefile, etc.) should be implicitly allowed or the planner should be prompted to include them. An agent that can't update pyproject.toml after adding a dependency will spin.
Acceptance Criteria
Test Plan
gateway/tests/ — session file restriction enforcement on push and commit
gateway/tests/ — warn-then-block escalation behavior
gateway/tests/ — directory-sibling expansion
gateway/tests/ — post-agent commit filtering with allowed_files
orchestrator/tests/ — container spawner passes files_affected to session
shared/egg_contracts/tests/ — glob pattern support in files_affected
- End-to-end: Tier 3 pipeline where two agents have disjoint file lists, verify each can only commit their own files
Closes #805.
Problem
#800 introduced phase-based file restrictions enforced via readonly mounts and commit-time gateway validation. These restrictions are phase-scoped — the implement phase blocks
.egg-state/directories but allows modification of any code file. This means an implement agent working on task A can accidentally modify files that belong to task B (or files not in the plan at all), creating unexpected cross-contamination when multiple agents work in parallel (Tier 3 dispatch).Goal
Make the planner's per-task
fileslist an enforced boundary, not just informational context. When the orchestrator spawns an implement agent container, the gateway should restrict that agent's commits to only the files listed in its assigned tasks.Design Philosophy: Guide, Don't Cage
These restrictions exist to prevent accidental cross-contamination, not to micromanage the agent. The enforcement must be generous enough that an agent with a reasonable plan never gets stuck. An agent spinning because it can't touch a file it legitimately needs is worse than an agent that touches one extra file.
Principles:
src/auth/login.py, the agent should also be able to createsrc/auth/login_helpers.pyin the same directory. Directory-level implicit allowance.files_affected= no per-file restriction. Missing or malformed entries are skipped, not fatal.egg-contract(e.g.,egg-contract request-file --path <file> --reason <why>). This logs the request and auto-approves (or queues a HITL decision for strict mode). The point is observability, not hard blocking.Current Architecture
The plumbing is mostly in place:
docs/templates/plan.md) already requiresfiles:per task in the YAML blockshared/egg_contracts/plan_parser.py) already extracts these intofiles_affectedon the Task modelPhaseFileRestriction(gateway/phase_filter.py) already supportsallowed_patternswith glob matching — it's just not populated for implement phasegateway/gateway.py:737-805) already callscheck_phase_file_restrictions()on every pushgateway/post_agent_commit.py) already filters files by phase restrictions before auto-committingWhat Needs to Change
1. Session model — add
allowed_filesfieldAdd an
allowed_files: list[str] | Nonefield toSessioningateway/session_manager.py. When set, these augment the phase'sallowed_patternsduring commit validation. WhenNone(non-pipeline sessions, non-implement phases), behavior is unchanged.Also accept
allowed_filesinregister_session().2. Container spawner — read task files, pass to session
In
orchestrator/container_spawner.py, when spawning an implement agent:files_affectedfrom all tasks assigned to this agent (union across tasks in the phase)dir/foo.py, implicitly adddir/as an allowed directory prefix so sibling files (new helpers, test files) are reachableallowed_filestogateway.register_session()3. Gateway commit/push validation — warn-then-block enforcement
In
gateway/gateway.py, when validating a push for an implement-phase session that hasallowed_filesset:PhaseFileRestrictionthat combines the existing implement-phaseblocked_patternswithallowed_patternsderived from the session'sallowed_filessrc/module.pyin the plan covers the file even if nested or renamed slightlyfiles_affectedthe agent should check, and how to request an exception4. Post-agent commit — respect per-session restrictions
gateway/post_agent_commit.pyneeds access to the session'sallowed_filesto filter uncommitted changes the same way pushes are filtered. Files outside the allowlist are restored (not committed) but this should be logged clearly — silent drops cause confusion.5. Plan template — document enforcement semantics
Update
docs/templates/plan.mdand planner prompt to clarify thatfiles:is an enforcement boundary, not just a hint. The planner should:src/components/*.tsx,tests/)Edge Cases to Handle
files_affected: If the planner omits files for a task (or it's empty), fall back to current behavior (no per-file restriction, only phase-level). Don't lock the agent out of everything.*and**infiles_affectedentries so the planner can saytests/**instead of listing every test file.pyproject.toml,package.json,Makefile, etc.) should be implicitly allowed or the planner should be prompted to include them. An agent that can't updatepyproject.tomlafter adding a dependency will spin.Acceptance Criteria
files_affectedis empty for a task, no per-file restriction is applied (graceful fallback)files_affectedare supported (e.g.,src/components/*,tests/**)dir/foo.pyimplicitly allows other files indir/files:is enforced, with guidance on generous listingTest Plan
gateway/tests/— session file restriction enforcement on push and commitgateway/tests/— warn-then-block escalation behaviorgateway/tests/— directory-sibling expansiongateway/tests/— post-agent commit filtering with allowed_filesorchestrator/tests/— container spawner passes files_affected to sessionshared/egg_contracts/tests/— glob pattern support in files_affectedCloses #805.