Skip to content

Enforce per-task file restrictions from planner in implement phase #805

@jwbron

Description

@jwbron

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:

  1. Plan template (docs/templates/plan.md) already requires files: per task in the YAML block
  2. Plan parser (shared/egg_contracts/plan_parser.py) already extracts these into files_affected on the Task model
  3. PhaseFileRestriction (gateway/phase_filter.py) already supports allowed_patterns with glob matching — it's just not populated for implement phase
  4. Gateway commit validation (gateway/gateway.py:737-805) already calls check_phase_file_restrictions() on every push
  5. 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

  • Gateway warns on first out-of-scope file per session, blocks on repeated violations
  • Post-agent auto-commit filters out files not in the task's allowlist (restores them instead of committing) with clear logging
  • When files_affected is empty for a task, no per-file restriction is applied (graceful fallback)
  • Glob patterns in files_affected are supported (e.g., src/components/*, tests/**)
  • Directory-sibling expansion: listing dir/foo.py implicitly allows other files in dir/
  • Plan template documents that files: is enforced, with guidance on generous listing
  • Existing tests pass; new tests cover per-session file restriction enforcement
  • Tier 3 (parallel agent) dispatch correctly scopes each agent to its phase's file union
  • Agent never hard-blocks on first attempt to modify an out-of-scope file

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions