Start Task (SAM Task Execution Helper)
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 in P{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 with stale-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, first read the task without --attempt and inspect its ledger attempts count.
The direct route is available only when that count is zero. If any prior attempt exists, do not
move it directly: return the applicable refusal or blocked response and route the task to that
attempt's report-and-finish contract.
Before completing, compare the reuse result with this task's requested outcome:
- If required implementation depends on an
UNRESOLVED choice, follow dh:reuse-before-build's
task-local deferral/needs-input route. Preserve the decision in the existing task/report; do not
append a completion record or move that implementation task to complete.
- A research or review task may finish with
UNRESOLVED only when delivering that assessment fully
meets its own acceptance criteria. That result does not complete dependent implementation work.
With zero prior attempts, persist the direct route's evidence before moving state. This uses the
ordinary task-section carrier at attempt zero, not a runner-owned report or a fabricated attempt:
<sam_cli/> plan update \
--plan-address P{N} --task-id T{M} \
--append-section "Direct Completion Record" \
--section-content "TASK: {what completed}
FILES_CHANGED: {files or none}
COMMITS: {commits or none}
REUSE DECISION: {ADOPT|ADAPT|BUILD JUSTIFIED plus evidence; UNRESOLVED only for a completed assessment}
VERIFICATION: {each verification step and evidence, or none}
NOTES: {every material divergence disposition, or none}"
Read the task back and confirm the Direct Completion Record is present before moving its state:
<sam_cli/> plan read --address P{N}/T{M}
<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. The ledger
refuses a zero-attempt completion without this durable direct record, and it refuses a task with any
prior attempt unless that attempt owns both report sections. Do not create or claim a Completion Report or Verification Results for this direct path.
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 --attempt off only when your dispatch named no attempt number.
The result carries the task row — title, requirements, constraints, acceptance criteria,
verification steps, and the skills list 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 Report from an earlier attempt — when it carries a BRANCH: line, switch to
that branch before you start.
Use the address form P{N}/T{M} where N is the plan number and M is the task number from the --task argument.
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
--task provided, 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
2a. Load task-level skills (if present):
- Read
skills from the task row of the plan read result (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.
- Without
--attempt, skip runner-only steps 3a, 4a, and the attempt-owned branch of step 5. Do
not renew a lease or close an attempt. Step 4 still registers session context; then continue
with the direct branch of step 5, step 6, and step 7. A material direct divergence is the one
exception to the usual no-section rule: its direct branch writes an owned attempt-zero section,
never an attempt report or divergence count.
3a. With --attempt, your attempt is already open.
dispatch opened it when the orchestrator launched you, which is what set the task
in-progress and started the lease. There is nothing to claim, and nothing to write to the
status field by hand.
The CLI's plan claim command 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. Use plan 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-activity on 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-issue if the story issue number is not known; absence is None. It accepts
str | int — GitHub integer IDs (e.g., 42) and beads string IDs (e.g., "bd-a3f8") are both
valid.
4a. With --attempt, 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.
-
With --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
With --attempt, 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 current divergence_notes value plus one; read the current value
from the plan read result of step 1. Write the field name with an underscore —
divergence_notes is the ledger column, and a hyphenated name is refused.
The --append-section value supplies the ## Divergence Notes heading — {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.
Without --attempt, preserve each material divergence in the immediate caller response under
NOTES: using the same note body: plan artifact and quoted claim, actual implementation,
classification, and recorded timestamp. Also persist it before returning through the existing
task-section carrier at attempt zero:
<sam_cli/> plan update \
--plan-address P{N} --task-id T{M} \
--append-section "Direct Divergence Notes" \
--section-content "{same material-divergence note body}"
Do not create a divergence count, attempt, or attempt-owned report. This carrier is available
only while the task has zero prior attempts; otherwise return the direct-route refusal and use
that attempt's report-and-finish contract. If the caller explicitly uses the zero-attempt
--complete route above, put the same body in that route's owned Direct Completion Record
under NOTES: instead of a separate Direct Divergence Notes section.
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, or Resolves #N trailers.
These trailers trigger automatic GitHub issue closure. Issue closure is handled exclusively
by /complete-implementation in its final commit step, after all quality gates pass.
Including these trailers in task commits causes premature issue closure before verification
is complete.
-
When selecting an implementation approach that may duplicate an existing solution, load
/dh:reuse-before-build in runner mode. With --attempt, follow its bundled runner handoff
before research or attempt closure. Without --attempt, make and return the decision and any
material divergence in the immediate caller response, without its attempt-owned plan update,
plan read --attempt, or plan finish commands. The direct branch owns Direct Divergence Notes at attempt zero, and the explicit zero-attempt --complete route separately owns its
Direct Completion Record; neither creates an attempt-owned report.
Implement against the task acceptance
criteria and run its verification steps.
Close the Attempt
With an attempt, follow the runner contract's completion steps and return your response under
dh:subagent-contract after closure or a refusal that prevents it. Without an attempt, no
runner owns closure: return the implementation, reuse decision, and every material-divergence
NOTES: disposition. Leave ledger state unchanged unless the zero-attempt --complete route
above is explicitly requested and its Direct Completion Record was read back.