Related: real enforcement of invariant 2 (worker physically can’t reach harness state, even with bash) is opt-in process isolation, planned in #13. This issue is the default-mode drift surface; complementary, both worth doing.
What
CLAUDE.md invariant 2 says:
The agent doesn't see harness mechanics. No prd.json structure, no events.jsonl, no summary.json, no token counts, no judge, no checkpoints.
prd.json lives in the workspace (it has to — that's where loop._load_prd reads it from), so the worker can read_file it freely. In the 5/1 demo session the worker did exactly that on T-002 iter 2:
$ jq -c 'select(.payload.task_id=="T-002" and .payload.iter==2 and .type=="tool_call")' events.jsonl
{...,"tool":"read_file","args":{"path":"tests/test_t002_package.py"}}
{...,"tool":"read_file","args":{"path":"prd.json"}}
No scope-creep happened in this run, but reading prd.json exposes every future task to the worker — and the model could legitimately decide to pre-implement T-003 while "doing" T-002. The harness has no defense.
Why memory.py doesn't already fix this
memory.build_user_prompt() correctly never injects prd into the user prompt — only the current task is rendered. The leak is purely via tool access to the file on disk.
Proposed directions (pick one)
- Block reads to
prd.json in pre_tool. Simplest. Cost: an explicit error message in the agent's view, which is itself a small leak of "there's a prd.json you can't see."
- Move prd.json out of the worktree. Store it under
sessions/<id>/prd.json (or <source>/.tilth/prd.json), and have _load_prd read from there. Worker has no way to see it. Cost: breaks the demo's "prd is a seed file in your repo" affordance; the demo workspace's PRD becomes a setup step rather than just git clone.
- Make prd a
.tilth/ directory inside the workspace and have pre_tool block .tilth/. Middle ground.
Option 2 feels closest to the invariant's intent — it's the same separation that already keeps events.jsonl / summary.json / checkpoint.json outside the worktree.
Related
tilth/loop.py:_load_prd, tilth/loop.py:_save_prd
tilth/tools/files.py:read
tilth/hooks/pre_tool.py
CLAUDE.md (invariant 2)
What
CLAUDE.mdinvariant 2 says:prd.jsonlives in the workspace (it has to — that's whereloop._load_prdreads it from), so the worker canread_fileit freely. In the 5/1 demo session the worker did exactly that on T-002 iter 2:No scope-creep happened in this run, but reading
prd.jsonexposes every future task to the worker — and the model could legitimately decide to pre-implement T-003 while "doing" T-002. The harness has no defense.Why memory.py doesn't already fix this
memory.build_user_prompt()correctly never injects prd into the user prompt — only the current task is rendered. The leak is purely via tool access to the file on disk.Proposed directions (pick one)
prd.jsoninpre_tool. Simplest. Cost: an explicit error message in the agent's view, which is itself a small leak of "there's a prd.json you can't see."sessions/<id>/prd.json(or<source>/.tilth/prd.json), and have_load_prdread from there. Worker has no way to see it. Cost: breaks the demo's "prd is a seed file in your repo" affordance; the demo workspace's PRD becomes a setup step rather than justgit clone..tilth/directory inside the workspace and havepre_toolblock.tilth/. Middle ground.Option 2 feels closest to the invariant's intent — it's the same separation that already keeps
events.jsonl/summary.json/checkpoint.jsonoutside the worktree.Related
tilth/loop.py:_load_prd,tilth/loop.py:_save_prdtilth/tools/files.py:readtilth/hooks/pre_tool.pyCLAUDE.md(invariant 2)