Skip to content

What the agent sees (and what it doesn't)

The worker is deliberately walled off from most of the harness's machinery. It sees only what it needs to do its job; the rest — audit logging, token accounting, resume state, the task-status machinery — is invisible. This is invariant 2 made concrete, and it's worth understanding before extending the harness.

The wall is softer than it once was, by design: the worker sees the feature overview and the whole plan as context, and — when it has worked this task before — the evaluator's prior verdicts on it. It still never sees the harness files, the queue it's part of, or the cost accounting.

The reviewing role is the evaluator.

Visibility table

Artifact Worker's view
Current task Sees its full description and acceptance criteria, injected as the user message. Not the file path or frontmatter — just the content.
Feature overview Sees the text of the feature's overview.md (capped at OVERVIEW_MAX_CHARS, 4 KB), so it understands the whole before building one slice.
Full feature plan Sees every task collapsed (id, title, status, description, AC), capped at FULL_PRD_MAX_CHARS (6 KB), framed as "context, not work to do" with the current task marked. It sees the plan, not the mutable status store.
Task ledger (ledger/<task_id>.jsonl) On a retry, sees its own task's ledger — the evaluator's prior verdicts (last 5). Empty on a first run; populated on later iterations and across resume. Not other tasks' ledgers or the file itself.
AGENTS.md / CLAUDE.md Sees the content (injected at task start; file list via TILTH_CONTEXT_FILES). Could also read_file it. Tilth never writes to these.
progress.txt Sees the last ~30 lines, injected. Doesn't write to it — the harness appends after task done/fail.
.tilth/<feature>/ files The content reaches it as the overview + plan + task injections. The directory is physically present in the worktree, so it could read_file them — the system prompt marks .tilth/ read-only, and the evaluator hard-rejects diffs that edit it.
task-status.json Never sees the file. Per-task status is harness-owned; the worker only sees each task's status rendered into the plan-as-context.
events.jsonl Never sees it. Pure harness audit log.
checkpoint.json Never sees it. Resume mechanics.
Token counts and caps Never sees them. Caps enforce silently between tasks.
Worktree branch Sees a workspace directory — doesn't know it's a worktree, its branch name, or that commits are made on its behalf.
The evaluator Runs in a separate fresh-context call the worker never sees. But it does see the evaluator's prior verdicts on its own current task (the ledger above), and each reject arrives as the structured submit_case tool_result. Never a generic cross-task reviewer or the session-wide rejection histogram.
Tool registry Sees the JSON schemas of all registered tools on every call (via tools=). The system prompt does not enumerate them — schemas are the canonical source.
Reasoning blocks Sees its own prior reasoning, echoed across iterations in the in-memory history (load-bearing for thinking-mode continuity — see client.assistant_history_message). The harness mirrors it into events.jsonl for the visualizer, but the agent never reads from there.

Why this separation is deliberate

Three failure modes — all seen in earlier hand-built loops — are prevented by hiding mechanics from the agent:

  1. Gaming the evaluator. The worker sees the evaluator's verdicts on its own task — by design, so it can fix what was flagged — but not the rejection categories aggregated across the session or the evaluation machinery itself. Keeping the wider machinery hidden stops the worker padding commits for the reviewer instead of writing working code.
  2. Budget shortcuts. If the agent knew the spend cap, it would cut corners ("I'll skip verifying to save money"). The only objective it's given is "do the task." Cost accounting is the harness's problem.
  3. Trying to manage state itself. If the agent saw the mutable status statetask-status.json, the queue machinery — it would try to mark its own task done, skip ahead, or rewrite the queue. The plan it sees is read-only prose; state management belongs in code, and the agent works on one task at a time and stops.

A corollary follows for AGENTS.md: it's for project conventions, not harness mechanics — the Memory channels page carries the belongs/doesn't-belong rule.

What the agent is explicitly told (in prompts/system.md)

  • Its role: "focused worker agent operating inside a long-running harness."
  • Each task starts a fresh conversation; no memory of prior tasks beyond the loaded context — the overview, the full plan, and (on a retry) the evaluator's prior verdicts on this task.
  • The overview and plan are context, not a worklist — build only the task under "Your task."
  • Prefer the narrowest tool that does the job (read_file over bash cat, grep over bash grep -r).
  • Verify before claiming done — if the project has a way to exercise the behaviour, run it via bash; "the code looks right" is not verification.
  • How it finishes: it's an advocate — when the work is done and verified it calls submit_case, mapping every acceptance criterion to the file:symbol that satisfies it (plus declared work-arounds and uncertainties). Going quiet is not "done."
  • .tilth/ is read-only context; no destructive commands, no force pushes, no out-of-workspace edits.

That's the entire visible contract. Everything else is the harness's job, in code.

When you'd break this rule

Two cases where exposing internals can be right:

  1. Debugging. During development, inject the iteration counter ("you have 3 iterations left") to see if behaviour changes. Useful as a probe; remove before production.
  2. Explicit budget signalling. If you want the agent to plan against a known budget ("you have 50K tokens for this task, plan accordingly"), that's a different pattern than the hidden cap — it reframes the agent as a planner and requires changing the system prompt. Tilth intentionally doesn't do this; the iteration cap is a hard stop, not a soft hint.