Agent Harness · Reference sheet
The 1-3-1 hand-over
When stuck: one problem, three options, one recommendation, then wait. The format, the triggers, four real cases and what the human does next.
The format
When stuck, the agent does not guess and does not keep trying. It sends exactly this, then waits:
PROBLEM <What is blocked, and why it matters. One or two sentences.>
OPTIONS 1. <Option> (<trade-off>)
2. <Option> (<trade-off>)
3. <Option> (<trade-off>)
RECOMMEND <Number>: <the reason, in one line>.
Waiting for your call.- 1 clearly defined problem, 3 options with trade-offs, 1 recommendation.
- It targets the expensive failure seen in real sessions: the agent rarely loops forever; it quietly decides something that was the human's call.
- The labels are one convention. What matters is one real problem, real options, a recommendation, and a stop.
When it triggers
| Stop and hand over when... | Example |
|---|---|
| A tool, SDK, test provider or environment blocks you | Only .NET 6/7 installed; Docker daemon down; EF InMemory cannot run ExecuteUpdateAsync |
| The fix would change an invariant, a requirement or the machine | Lower TargetFramework; rewrite the atomic UPDATE; install an SDK; start an application; drop a database |
| A retry has failed: the original attempt plus one retry | docs/ai/workflow.md: "after one failed retry, hand over". No third blind try, no polling loop |
The exact section to paste into AGENTS.md
## When you are stuck: 1-3-1
If a tool, SDK, test provider or environment blocks you, or a fix would change an invariant,
a requirement or the machine: STOP. Do not work around it. Reply with exactly
- **1 problem**: what is blocked and why it matters,
- **3 options**, with the trade-off of each,
- **1 recommendation**,
then wait for the human's decision. Never install, launch, downgrade or delete to get unstuck.Add it to the index in lesson 3's commit; where it sits in the skeleton.
Four real cases, as hand-overs
What the agent did
"the machine has .NET 6/7 installed but not .NET 8. I'm going to build the service against net7.0". The project required .NET 8; the agent kept going and nobody challenged it.
A real session, anonymised · Codex
The 1-3-1 instead
PROBLEM Only .NET 6/7 SDKs are installed; the brief requires 8+.
OPTIONS 1. Install the .NET 8 SDK (about 5 min), then build
2. Build inside the .NET 8 SDK Docker image
3. Target net7.0 and note the deviation
RECOMMEND 1: option 3 breaks a stated requirement.
Waiting for your call.Invariant in AGENTS.md: "Target framework is net8.0. Do not change it." The guard hook also denies net7.0.
What the agent did
ExecuteUpdateAsync "could not be translated (InMemory provider)", so the service was rewritten to load tracked entities, decrement in memory and save, and called "still correct against concurrent writes". A live test of 12 concurrent orders for 3 units oversold.
A real session, anonymised · Claude Code
The 1-3-1 instead
PROBLEM InMemory cannot translate ExecuteUpdateAsync, so 3 order tests fail. The atomic UPDATE ... WHERE qty >= n is what prevents overselling.
OPTIONS 1. Test orders against a real database (Testcontainers, or real SQLite)
2. Keep the atomic update; unit-test other logic on InMemory
3. Rewrite to read-then-write (loses atomicity unless we add locking or a rowversion check)
RECOMMEND 1: keep the correctness; fix the tests, not the service.
Waiting for your call.The human picks real SQLite and discards the InMemory work (docs/ai/testing.md: "never use the EF Core InMemory provider").
What the agent did
docker info failed. The agent ran Start-Process "Docker Desktop.exe" unasked, polled docker info again and again, read logs, and asked the human 35 minutes after the first failure.
A real session, anonymised · Claude Code
The 1-3-1 instead
PROBLEM The Docker daemon is not running; Docker verification is P0.
OPTIONS 1. You start Docker Desktop; I continue with docs meanwhile
2. I launch Docker Desktop myself and poll
3. Defer Docker to the end and ship without verifying
RECOMMEND 1.
Waiting for your call.Rule: "Never install, launch, downgrade or delete to get unstuck." Hook: starting software is denied. The hand-over arrives at the first failure, not after polling.
What the agent did
SQLite cannot ORDER BY a DateTimeOffset. The agent diagnosed it in under 3 minutes, then removed and regenerated the migrations as one InitialCreate, silently. Right here, because nothing had shipped.
A real session, anonymised · Claude Code
The 1-3-1 instead
PROBLEM The converter makes EF emit a lossy TEXT to INTEGER AlterColumn.
OPTIONS 1. Nothing has shipped: drop and regenerate InitialCreate
2. Hand-write a data-preserving migration
3. Store timestamps as DateTime (UTC) instead
RECOMMEND 1, because no database exists yet.
Waiting for your call.The diagnosis needs no hand-over. The decision to delete migrations does: on a real project it has data consequences and is the human's call.
The human's side
- Is the problem real? Re-read PROBLEM. Is it the thing that is actually blocked, or a symptom?
- Does any option touch an invariant or the brief? Those need your judgement. Rule out options that do unless there is a stated reason.
- Is it reversible? Prefer the option you can undo (a new commit, a branch) over one you cannot (deleting data or history).
- Decide, then say why in one line. Reply with the number and the reason; the agent does the rest.
- Record it in
ai-session/PLAN.md, then reference it in the commit body.
The swap test in lesson 11 checks whether the agent stopped and asked; your PLAN.md shows that you decided.
Record the decision
## Decisions made by the human
- 2026-09-30: only .NET 6/7 SDK installed; brief requires 8+ -> chose option 1 (install .NET 8 SDK myself) because option 3 breaks the brief (1-3-1)
- 2026-09-30: InMemory can't run ExecuteUpdateAsync -> chose option 1 (real SQLite in tests) because atomicity is the invariant (1-3-1)Format from the reference plan: <date>: <problem> → chose <option> because <reason> (1-3-1). Mention it in the commit body too: the commit skill allows "any 1-3-1 decision the human made".
Anti-patterns
| Anti-pattern | What it looks like | Instead |
|---|---|---|
| Polling | docker info again, "timed out, moved to background", again, read the logs: 35 minutes in one real session | One check, one retry, then hand over |
| Silently downgrading | "I'm going to build the service against net7.0"; rewrite to read-then-write so InMemory passes | Name the blocked requirement as the problem; put the downgrade in the options, never as the action |
| "I'll just..." | "I'll just install the SDK / start Docker / delete the migration and regenerate" | Install, launch, downgrade and delete are the human's verbs |
| Options that are one option | 3 options where 2 are strawmen, or all 3 are the same fix with different flags | Real trade-offs; include the option you would not pick and say why |
| Asking for approval and continuing | Presenting the 1-3-1, then starting the recommendation | Stop. Wait for the reply. Nothing runs until the human chooses |
| Burying it | The choice appears as one sentence inside a long status update | Its own message, with the three labels |
Taught in: Lesson 3: 1-3-1 · Lesson 4: Plan-gated workflow · Lesson 11: Capstone
Other sheets: AGENTS.md structure · The four guards · Commit skill · Glossary · Tool equivalents