Lab 1 solution
This is the reference end state for Lab 1 — Your first delegation, and reading the session.
Session details on this page are illustrative. Your sessions will differ in wording and order. The reference document below is not: it's derived from the source, and you can check every line of it.
Files at the end of the lab
None. Lab 1 ends with the repository exactly as it started. You restored docs/api.md twice, once after the interactive session and once after the --auto low run, because T7 is a scoreboard task. A committed document would let T7 pass without Droid doing anything.
git status --short git diff --stat main
Both should print nothing.
Where this lives in harness-reference/: nowhere. Lab 1 builds a habit, not a file. The harness begins with AGENTS.md in Lab 2.
A correct T7 document, for checking your session
Use this to check the document your session produced, claim by claim, and then find the tool call each claim came from. Every line below comes from src/api/server.ts or src/transfers/ach.ts. Don't commit it.
## POST /transfers
Creates an ACH transfer from a Larkspur account. Idempotent: a retry with
the same `idempotencyKey`, amount and source account returns the original
transfer.
### Request body (JSON, max 64 KB)
| Field | Type | Notes |
| --- | --- | --- |
| `idempotencyKey` | string | Required, non-empty after trimming. |
| `fromAccountId` | string | Must be a known account id. |
| `toAccountNumber` | string | 4 to 17 digits. Never returned unmasked. |
| `toRoutingNumber` | string | Exactly 9 digits. |
| `amountCents` | integer | Positive, in cents. Must be a safe integer. |
### 201 Created
```json
{
"id": "tr_000001",
"idempotencyKey": "b7d2…",
"fromAccountId": "acc_retail_1",
"toAccount": "••••6789",
"toRoutingNumber": "021000021",
"amount": 125000,
"status": "pending",
"createdAt": "2026-09-29T14:03:11.000Z"
}
```
`toAccount` is masked with `maskAccount()`: only the last four digits.
### Errors
| Status | `error` | When |
| --- | --- | --- |
| 400 | `invalid_json` | Body is not JSON or is over 64 KB |
| 404 | `unknown_account` | `fromAccountId` is not a known account |
| 400 | `invalid_amount` | Amount is not a positive safe integer of cents |
| 400 | `missing_idempotency_key` | `idempotencyKey` missing or blank |
| 400 | `invalid_destination` | Routing number not 9 digits, or account number not 4–17 digits |
| 409 | `idempotency_conflict` | Key reused with a different amount or source account |
| 422 | `daily_limit_exceeded` | Retail daily ACH limit ($25,000.00) would be exceeded |
| 500 | `internal` | Anything else |
The error table is the part to check. server.ts only names idempotency_conflict and the route's own errors. missing_idempotency_key and invalid_destination come from the RejectionCode type in ach.ts. If your document has both and your trace shows a Read of ach.ts, the session did its homework. If it has both and the trace shows no such read, the session guessed well, and you should trust it no more than if it had guessed badly. If it lists insufficient_funds or anything else that isn't in RejectionCode, it guessed and got it wrong.
Two details also separate a careful document from a plausible one: toAccount is masked (invariant 4), and the daily limit applies only to retail accounts today. Business limits are T3.
What a good run looks like
The interactive session
At Autonomy Off, a typical T7 session asks for two or three approvals: the edit to docs/api.md, and often a command to start the demo server or run tests. A good lab run shows:
- at least one rejected approval (usually the demo server), with a reason you could give Theo;
- one approval of the edit, chosen once rather than "always allow";
- a claim-by-claim check of the document against the trace using
Ctrl+O, with at least one claim that has no tool call behind it.
Coming back to a session
droid --resume with no id opens a session picker (droid --resume --last skips it and reopens the most recent session in this folder). droid search "transfers" finds a session by what happened in it, and droid --resume <sessionId> opens that one. A good answer to Q4 (Ruth's "how do I find the session?") notices that today the chain breaks at the commit. Nothing yet ties a pull request back to a session id. The includeCoAuthoredByDroid trailer shows an agent was involved, but not which session. Lab 3 adds the plan link and Lab 9 adds CI artifacts.
droid exec
The read-only summary succeeds and changes nothing:
droid exec "summarize how fees are calculated" git status --short
A good summary mentions FEE_SCHEDULE in src/fees/schedule.ts, amounts as integer cents, the overdraft fee pro-rated on a 30/360 basis and rounded with roundHalfEven(), and possibly the display-only estimateCashback() that still uses Math.floor on a float. That last one is a good catch. It's the kind of code Lab 10's Mission retires.
The write without --auto fails fast:
droid exec "document POST /transfers in docs/api.md"; echo "exit $?" git status --short
Expect what Factory's docs describe for a request that exceeds the autonomy level: the run stops with a clear error, exits non-zero and makes no partial changes, so git status is empty. The exact wording of the error depends on your version. With --auto low the same command succeeds and exits 0, and git restore docs/api.md puts the file back.
Three common wrong turns
1. Keeping the documentation
The interactive session produced a good docs/api.md, so it feels wasteful to throw it away. But T7's check is "docs/api.md documents every field; no source files changed", and a committed document passes that check on every future run, with or without Droid.
How to spot it: git log --oneline -- docs/api.md shows a commit from you, or git diff main -- docs/api.md isn't empty. Fix: revert it on main. If you want to keep it, put it on a branch you never merge.
2. Choosing "always allow"
It's the fastest way through the prompts, and it quietly changes the session. Choosing an "always allow" option raises the session's autonomy level to whatever that prompt needed. After that, everything at that level runs without asking, not just the one action. Learners who do this in Step 2 often report that the demo server "just started".
How to spot it: later actions that should have prompted at Off didn't. The trace shows Execute calls with no approval before them. Fix: start a new session. Lab 4 is where you decide, in configuration, what runs without asking.
3. Judging the session by its summary
The last message in a session is a summary written by the same model that did the work. It's usually accurate about what was done, and it says nothing about what wasn't looked at. Learners who read only the summary and the diff report that T7 "worked", which it did, and miss that the error list came from a file Droid never opened.
How to spot it: you can't name, for any claim in the document, the tool call it came from. Fix: Ctrl+O, scroll to the top, and read the tool calls in order before you read the result.
Also watch for
droid login. It doesn't exist. Sign in through the browser when Droid first starts, or with/logininside a session. Headless runs useFACTORY_API_KEY.-r. Indroid,-ris--resume. Indroid exec, it's--reasoning-effort. A resume command pasted into an exec script fails in a confusing way.
Where to go next
Lab 2 writes the file every session reads first, including the lines that would have sent this session to ach.ts before it wrote anything.