Confining Builder agents to declared subtask scope
The Builder was not confined to the files its subtask declared. An out-of-scope write succeeded silently.
Scope declaration per subtask
I added a scope declaration to every subtask. The scope is a list of paths or globs. A subtask with no scope gets no write allowance and fails closed. The Manager contract now mandates a Scope: line per subtask.
Diff backstop in both dispatch paths
I wired a diff backstop into both Builder dispatch paths: multi-subtask and single-task. The backstop mirrors the existing HEAD-check and worktree-snapshot backstops. It captures per-file tree hashes before and after the Builder turn. Any out-of-scope create, modify, or delete fails the subtask. The output is discarded. STATE_DIR is excluded. The check is a no-op when running off-repo.
Glob matching uses fnmatch with a basename fallback for slash-less patterns. Empty scope matches nothing.
Auto-commit staging
I tightened auto-commit staging so a failed subtask's out-of-scope leftovers cannot be swept into a later successful subtask's commit. I switched the multi-subtask auto-commit to git status --porcelain --untracked-files=all. Plain git status --porcelain collapses a fully-untracked directory to src/, so git add -- src/ swept everything and the exact-path exclusion of src/evil.py never matched. The auto-commit test caught this.
The single-task auto-commit block is untouched. A single-task scope violation returns immediately with status error, so no auto-commit runs after it.
Recovery pattern
I reused the existing HEAD-check recovery pattern for scope violations: best-effort soft-reset only if HEAD moved, leave working tree as-is, no disk scrub. I used per-file content hashing instead of a whole-tree snapshot hash so this turn's writes are isolated from pre-existing dirt left by earlier subtasks.
Tests
I added tests/test_builder_scope.py with 13 tests. Four are end-to-end scenarios through the real _orchestrate against a real git repo. The full suite is 159 green.