You are implementing a specific task in a SAM plan, addressed as P{id}/T{id}. The backend resolves that address and returns the task — no path is involved.
Use the SAM CLI's plan group for the runner sequence. When your dispatch names an attempt,
read the runner contract in full before the first
ledger command; it owns attempt identity, leases, reports, outcome selection, closure and
refusals. Load dh:subagent-contract before returning your response; it owns the response
destination and the distinction between recorded closure, durable outcome and immediate status.
<task_input> $ARGUMENTS </task_input>
Load dh:dh-cli-usage before using <sam_cli/> or <dh_scripts/> below.
Tool availability: task state moves through <sam_cli/>, which needs no MCP server. The
artifact and backlog steps below use mcp__plugin_dh_backlog__* tools; if one is unavailable, load
dh:dh-cli-usage and follow its MCP connection check.
Parse Arguments
plan_address(required): plan address inP{hex}form, e.g.Pdec8934d--task <id>(optional): Task ID to start (defaults to first ready task)--attempt <n>(optional): the attempt number the orchestrator opened for this dispatch. Carry it on every ledger command below. It is the key that proves a command belongs to this dispatch: a command from a superseded attempt is refused withstale-attempt.--complete <id>(optional): Task ID to close
If --complete <task-id> Provided
With an attempt number, follow the runner contract's outcome selection, report prerequisites
and closure steps for that attempt. --complete names the task to close; select its result from
what happened rather than treating this argument as proof of success.
Without an attempt number, no runner is closing anything, so move the status directly and say why:
<sam_cli/> plan state \
--address P{N}/T{M} --new-status=complete --reason "{why this moved without a runner}"
--reason is required — the ledger records why a status moved with no runner behind it.
Starting a Task
-
Read the task via the SAM CLI, naming your attempt. This is your first command:
<sam_cli/> plan read --address P{N}/T{M} --attempt {n}Naming the attempt also pushes out your lease, so the orchestrator can tell a working runner from a stalled one. Leave
--attemptoff only when your dispatch named no attempt number.The result carries the task row — title, requirements, constraints, acceptance criteria, verification steps, and the
skillslist to load before implementing — and the sections recorded on the task. Two of those sections decide what you do first:Orchestrator Response— why a previous attempt was sent back. Act on it before anything else.Completion Reportfrom an earlier attempt — when it carries aBRANCH:line, switch to that branch before you start.
Use the address form
P{N}/T{M}whereNis the plan number andMis the task number from the--taskargument.
1a. Discover plan artifacts via manifest (when issue number is known):
If the task row carries a github_issue value or the plan carries an issue field, query the artifact manifest to discover available plan artifacts:
<sam_cli/> artifact list --item-id N
If the response contains artifacts (non-empty artifacts list), use artifact_read to fetch the architect spec and feature context content:
<sam_cli/> artifact read --item-id N --artifact-type architect
<sam_cli/> artifact read --item-id N --artifact-type feature-context
Use the returned content as context for implementation instead of reading filesystem paths directly. This is especially important for worktree-isolated agents that cannot access uncommitted plan files from the root worktree.
Fallback: If artifact_list returns an empty manifest (no artifacts entries) or an error, try artifact_read with types architect and feature-context directly. These artifact types are registered by the agents that produce them.
- Select the task:
- If
--taskprovided, use that ID - Else run
plan ready --plan-address P{N}and take the first task it lists — readiness is derived from status and dependencies, so the ledger answers this rather than you
- If
2a. Load task-level skills (if present):
- Read
skillsfrom the task row of theplan readresult (an array of skill names). - If absent or empty, skip.
- For each skill name, invoke:
Skill(skill="{skill-name}") - If a skill fails to load, log a warning and continue. Do not abort task execution.
- Task-level skills are additive to any skills already declared in the agent definition's frontmatter.
-
Your attempt is already open.
dispatchopened it when the orchestrator launched you, which is what set the taskin-progressand started the lease. There is nothing to claim, and nothing to write to the status field by hand.The CLI's
plan claimcommand does not reach the ledger. It writes to the content store, so a task claimed that way leaves the ledger row exactly where it was and the orchestrator watching a task that never moved. Useplan read --attempt {n}(step 1) as your first command instead.Handle refusals under the runner contract's code table and authority boundary. A superseded attempt is not yours to rejoin.
-
Register the active-task context via the SAM CLI (required for hook-driven updates):
<sam_cli/> active-task set \ --address P{N}/T{M} \ --parent-issue N \ --session-id "${CLAUDE_CODE_SESSION_ID}"This is session-scoped context for the PostToolUse hook, which stamps
last-activityon the task while you work. It is not task state and holds nothing the ledger holds.It is not how the SubagentStop hook finds you. That hook takes your address and attempt from your own launch prompt, because this record is keyed by
${CLAUDE_CODE_SESSION_ID}— the parent session's id inside a sub-agent, so a wave's workers all share one — and carries no attempt number.Omit
--parent-issueif the story issue number is not known; absence isNone. It acceptsstr | int— GitHub integer IDs (e.g.,42) and beads string IDs (e.g.,"bd-a3f8") are both valid.
4a. Renew the lease before work that may outrun it.
Your lease has a deadline. Before starting anything long — a full test suite, a build, a large refactor — push it out:
<sam_cli/> plan renew --address P{N}/T{M} --attempt {n}
renew prints renew_by: the instant the lease next expires. plan read and plan update
push the deadline out too whenever you pass --attempt, but renew is the command that tells
you where the new deadline sits. A lease left to expire lets the orchestrator take the task back
and hand it to another runner, and your commands then answer stale-attempt.
-
Record divergence observations during implementation.
While implementing, if you discover that the architect spec or feature-context describes something that does not match what you are implementing, record a divergence note on the task through the ledger.
When to record: Record a divergence note when ALL of these hold:
- You are implementing something that differs from what the architect spec or feature-context describes
- The difference is not a trivial implementation detail (e.g., different variable name, different import path)
- The difference affects the observable behavior, structure, or scope of the feature
Write the note and its running count in one command. Appending the section and setting the count are sub-operations of a single
update:<sam_cli/> plan update \ --plan-address P{N} --task-id T{M} --attempt {n} \ --append-section "Divergence Notes" --section-content "{note body}" \ --set divergence_notes={new_count}{new_count}is the task's currentdivergence_notesvalue plus one; read the current value from theplan readresult of step 1. Write the field name with an underscore —divergence_notesis the ledger column, and a hyphenated name is refused.The
--append-sectionvalue supplies the## Divergence Notesheading —{note body}carries no heading of its own:
### DN-1: {Brief title}
- Plan artifact: `artifact_read(item_id={N}, artifact_type="architect")`, section "{section name}"
- Plan claim: "{quoted text from plan artifact}"
- Actual implementation: "{what was actually done and why}"
- Classification: design-refinement | intent-divergence
- Recorded: {ISO timestamp}
Never record a divergence note by editing a file. The task is addressed logically; on a remote backend no task file exists to edit, and a file written in one worktree is unreadable from another, so a file-based note is silently lost.
For full artifact classification rules and divergence thresholds, see plan-artifact-lifecycle.md.
-
Commit message restriction — Fixes #N trailers are PROHIBITED in task-level commits.
Task-level commits must NEVER include
Fixes #N,Closes #N, orResolves #Ntrailers. These trailers trigger automatic GitHub issue closure. Issue closure is handled exclusively by/complete-implementationin its final commit step, after all quality gates pass. Including these trailers in task commits causes premature issue closure before verification is complete. -
Implement against the task acceptance criteria and run its verification steps.
Close the Attempt
Follow the runner contract's completion steps for the attempt named by your dispatch. Return
your response under dh:subagent-contract after closure or a refusal that prevents it.