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, 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 --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.
-
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. 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 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.
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.
-
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.