H1 harness spike: design (for approval before any code)¶
Status: approved (#67, atul, 5 Oct 2026): option B (structured output; MMDEP raises the item; agents get no write
path into the hub), the caps as proposed, Sonnet. Built per this design; results in h1-harness-notes.md. Spec 0.10 §6 ("Harness: start, pause, resume, hand over"), §12 (relay steps and
waiting time), §18 (H1 right after story D). No code is written until the approve item for this document is approved.
1. What H1 proves¶
An agent run is a resumable session, not a one-shot call. MMDEP starts Claude Code in a task's worktree. The agent
works until it needs a person, then asks one question and stops. The task parks on a specify item. When Atul answers,
MMDEP resumes the same session with the answer, and the task carries on to the usual approve item. This runs on
two synthetic projects in parallel, one slot each, and the ledger records what the relay cost.
2. The flow¶
Ready ──dispatch──▶ Running: claude -p … --session-id <uuid> (uuid stored on the task first)
│ structured result
┌───────────┴─────────────┐
status = "question" status = "done"
│ │
MMDEP raises a specify item the existing path: validate → commit → Test Runner → approve item
(raised_by = the agent's name;
the question is untrusted text)
task ─▶ AwaitingHuman (step = resume)
│ Atul answers the item
next dispatcher pass: resume_from_item ─▶ Ready (step = resume)
│
Running: claude -p … --resume <session_id> (prompt = the answer, framed as Atul's reply to item #N)
└──▶ back to "question" or "done"
- Session id. MMDEP generates a UUID and passes
--session-id <uuid>. It stores the UUID on the task before the launch (a newtasks.session_idcolumn, added in place like story F's). A crash mid-run still leaves the id on the task. MMDEP still checks it againstsession_idin the JSON result and logs any difference. - Same working folder. Claude Code keeps sessions per working folder, so the resume runs in the same worktree. The worktree stays until the task is Done or Cancelled, as it does today.
- One question per stop. At most 3 question/resume cycles per task. A 4th question parks the task with a
reviewitem ("the agent keeps asking"), the same way 3 failed attempts do today. - Reject on the specify item cancels the task, as
resume_from_itemalready does. An answer of "stop" is just an answer; the agent decides.
How the question reaches the inbox: a choice for Atul¶
Spec §6 says "the agent raises a specify item and stops". There are two ways to build that:
- (B, recommended) Structured output; MMDEP raises the item. The run gets
--json-schemawith{"status": "done" | "question", "question": string, "summary": string}. When the status isquestion, MMDEP raises thespecifyitem itself, raised by the agent's registered name. The question goes in a marked block of untrusted text, as for Drive requests (#61). - The agent's process has no MCP server and no write access to the hub. That's
--strict-mcp-configwith no--mcp-config. - It also avoids the MCP import problem found on 5 Oct (section 8).
- (A) The agent calls the
raise_itemMCP tool itself. This needs an MCP config for the run, a registered agent identity, and allowingmcp__mmdep__raise_item. It's closer to the spec's wording but gives the agent a write path into the hub. MMDEP would then have to find "its" item afterwards.
3. Exact command lines (checked against claude --help, Claude Code 2.1.289, 5 Oct 2026)¶
Start (prompt on stdin; never on the command line):
claude -p
--output-format json
--json-schema <schema JSON> (option B)
--session-id <uuid>
--model sonnet
--restricted
--tools Read,Edit,Write,Glob,Grep
--allowedTools Read,Edit,Write,Glob,Grep
--permission-mode acceptEdits
--permission-prompts none
--strict-mcp-config
--max-budget-usd <per-call cap>
Resume: the same flags, except --resume <session_id> in place of --session-id <uuid>. The prompt is the answer.
What each flag is for, per the help text:
| Flag | Why |
|---|---|
-p / --output-format json |
non-interactive; one JSON result (with session_id, total_cost_usd, num_turns, duration_ms, is_error, result; the field names are checked on the spike's first run) |
--json-schema |
the done/question result is checked by the CLI; option B only |
--session-id / --resume |
MMDEP owns the id; the resume continues the same conversation |
--restricted |
removes Bash, PowerShell, REPL and the other code-running tools, and WebFetch; ignores user, project and local settings files (so Atul's own allow rules and hooks don't apply); confines the file tools to the working folder; refuses bypassPermissions |
--tools … |
only the file tools exist in the session |
--allowedTools … |
the same set, pre-approved (spec §6: an explicit allowlist) |
--permission-mode acceptEdits |
edits inside the worktree go ahead without a prompt |
--permission-prompts none |
anything that would still prompt is denied, not waited on: nothing ever blocks on a person mid-run |
--strict-mcp-config |
no MCP servers from any config; with option B there's no --mcp-config either, so none at all |
--max-budget-usd |
a hard per-call spend cap |
Never used: --dangerously-skip-permissions, --allow-dangerously-skip-permissions,
--permission-mode bypassPermissions (and --restricted refuses it anyway), --no-session-persistence
(it would make --resume impossible), --bg, --cloud, --chrome.
4. Who it runs as, and the agent-account lock¶
- The claude process runs as Atul (spec §6: H1 is the CLI agent's opt-in, synthetic data only). The
claudeagent staysrun_as_agent_account: false. CLI login under the agent account is backlog, before any real project. - Containment for the session comes from
--restricted(file tools only, confined to the worktree) and the tool allowlist, not from Windows access lists. - The Test Runner step after a
doneruns the project'stest_commandas mmdep-agent, under #64's per-task grant and its hub-wide lock. - Two projects in parallel: two
mmdep runprocesses, one per project (one slot each). - The two claude sessions run at the same time; they don't take the lock.
- Only their Test Runner steps serialize. The second waits for the first, polls the kill switch, and fails as a normal attempt only if it waits past its test timeout.
- H1 measures that wait.
account_lockwill log the seconds it waited, which is a small addition to #64's code, so the cost of "one launch at a time" (#64's accepted trade-off) shows up in numbers before the agent account pool (backlog) is built.
5. Caps¶
| Cap | Value proposed | Where |
|---|---|---|
| Per call | --max-budget-usd 0.50 |
the command line |
| Per spike | stop at $10.00 total total_cost_usd (the sum in the ledger) |
checked by the dispatcher before each call; over it, the task parks with a manual item |
| Calls per day | max_calls_per_day: 20 for claude (today 0, item #2) |
Atul's change to the hub's agents.yaml; I'll give the exact lines in a manual item after approval |
| Per call, time | the agent's timeout_s (default) |
existing deadline; kills the tree |
| Questions per task | 3 | section 2 |
total_cost_usd is the CLI's own estimate. On a subscription login it is an estimate, not a bill. It's still
the number H1 reports.
6. Data class: synthetic only¶
- Two new projects,
h1-aandh1-b, each a tiny repository made for H1. Each has fake content: one small Python module with a unittest suite (for example "sum line items in a fake invoice", with names likeACME Test Ltdand amounts like 123.45) and atest_commandrunningunittest. - No PAN, Aadhaar, policy or folio numbers, no client names, nothing copied from a real project.
- Each task description says the data is synthetic.
- Registering the two projects (
mmdep project add) and adding tasks (mmdep task add) are operator commands I'm allowed to run (CLAUDE.md). The repositories live underC:\Projects\ai-sandbox\h1-aand\h1-b. - Tasks are written to make a question likely, for example "round to 2 places or to the nearest rupee? ask".
7. What the ledger measures¶
| Entry | Fields |
|---|---|
harness.start / harness.resume |
task, session_id, kind (start or resume), item answered (for resume) |
harness.result |
session_id, status (question or done), num_turns, duration_ms, total_cost_usd, is_error |
harness.question |
task, the specify item number, cycle number (1–3) |
| relay steps per task | the count of question/resume cycles (from the entries above) |
| waiting time per relay | specify item created → responded_at (already on the item), logged on resume as waited_s |
account.lock_wait |
the seconds the Test Runner waited for the agent-account lock, with project and task |
| per task, end to end | Ready → approve item raised; the number of person touches (answers + the approval) |
The report goes in docs/research/h1-harness-notes.md: a table per task, the totals, and what the Agent SDK
(interrupts, approval callbacks) would remove from our subprocess adapter (spec §6, golden rule 2).
8. Found on 5 Oct, relevant here¶
.mcp.json starts the MMDEP MCP server as mmdep_runtime\Scripts\python.exe -m mmdep.mcp_server, with the repository
as its working folder. python -m puts that folder first on the import path, so the server runs the dev checkout's
code (whatever branch is checked out when it starts), not the runtime's. That includes the guard that refuses
respond to agents.
- The fix is
-P(Python 3.11+: don't prepend the working folder) in.mcp.json's args. It goes on its own branch and approve item. - With option B, H1 doesn't start an MCP server at all.
9. Stop condition¶
The spike stops at the first of these:
- Success: each of
h1-aandh1-bhas finished at least one task that went through at least one question → answer → resume cycle, to an approve item, with the two projects' runs overlapping in time. That should be at most 3 tasks per project. - Budget: $10.00 total
total_cost_usd, or 20 calls in a day. - Safety: any isolation violation, any write outside the task's worktree, a permission prompt that blocked, or a resume that lost the conversation (the agent doesn't know its own earlier question). Stop at once and report.
- Time: 3 working days after the code is approved and released.
Then: the notes (section 7), the H1 tasks cancelled and their worktrees cleaned up, and claude's cap back to 0
(Atul's change) unless he decides otherwise.
10. Code it will need (after approval)¶
ClaudeCliAgent: aharnessmode with the command lines above, JSON parsing, session handling.- tasks: the
session_idcolumn, and aresumestep. - dispatcher: question → specify item → park; answer → resume; the 3-question limit; the spike budget check.
account_lock: the wait time logged.- The ledger entries in section 7.
- Tests against a fake
claudeexecutable that returns question/done JSON. One opt-in live test, skipped by default, that runs the real CLI once against a scratch folder. - Risk: high. It starts an autonomous coding agent from the dispatcher for the first time.