Shipwright Harness

Task Store API

The task store is the central queue that drives autonomous task execution. Agents query it to pick up work, track progress, and record outcomes. This page is the primary reference for operators and agents interacting with /tasks directly.

See also: PRs API — the companion surface for PR review and patch tracking.

Authentication

All requests require a Bearer token in the Authorization header:

Authorization: Bearer $SHIPWRIGHT_TASK_STORE_TOKEN
Env varDescription
SHIPWRIGHT_TASK_STORE_URLBase URL of the task store service (provisioned at deploy time)
SHIPWRIGHT_TASK_STORE_TOKENBearer token for this agent or admin

Token scoping

Tokens come in two varieties:

  • Agent tokens (agentId set) — all reads are automatically scoped to tasks assigned to that agent. Writes are blocked on tasks belonging to other agents (403). Task creation forces assignee to the token’s agent ID.
  • Admin tokens (agentId null) — no restrictions. Can read and write any task. Required for HITL execution and cross-agent inspection.

Repo-scoped agent tokens additionally include unassigned pool tasks whose repo is in the token’s repos list — allowing agents to pick up shared work without requiring an explicit assignee.

Verify connectivity before starting work:

curl -sf -H "Authorization: Bearer $SHIPWRIGHT_TASK_STORE_TOKEN" \
  "$SHIPWRIGHT_TASK_STORE_URL/tasks?ready=true" | jq '.total'

Task Status Lifecycle

Tasks move through a defined set of statuses from creation to completion:

pending → in_progress → pr_open → approved → merged → deploying → deployed

Branch statuses that can occur at any point:

blocked    — dependency not met, or manually paused (HITL)
cancelled  — permanently abandoned
done       — completed without a PR (direct task, no deploy path)

Terminal statuses (dependency resolution treats these as satisfied): merged, done, deploying, deployed, cancelled

StatusMeaning
pendingNot yet started; eligible for ?ready=true pickup if dependencies are met
in_progressClaimed by an agent; work is underway
pr_openPR has been opened on GitHub; pr and prCreatedAt must be set
approvedPR passed review; ciFixAttempts must be set
mergedPR merged to main; mergedAt should be set
deployingDeploy pipeline is running; deployingAt should be set
deployedSuccessfully deployed to production; deployedAt should be set
blockedExplicitly paused — waiting on human action or unresolvable dependency
doneTask complete without a PR (e.g. research or HITL tasks)
cancelledPermanently abandoned

Endpoints

GET /tasks

List tasks. Returns { tasks, total } for ready/blocked queries; { tasks, total, limit, offset } for all other queries.

Query paramDescription
ready=trueReturn only tasks that are ready to pick up (see ?ready=true semantics)
state=readyAlias for ?ready=true
state=blockedReturn pending tasks with unmet dependencies, HITL-gated tasks, and explicitly blocked tasks
state=openFilter to open tasks (pending, in_progress, pr_open, approved)
state=closedFilter to closed tasks (merged, done, deploying, deployed, cancelled)
state=in_progressFilter to actively running tasks
statusFilter by exact status value (e.g. ?status=pr_open)
sessionFilter by planning session slug
assigneeFilter by assignee (admin tokens only — agent tokens are auto-scoped)
repoFilter by repo in org/repo format
branchFilter by branch name
prFilter by PR number
claimedByFilter by the agent that claimed the task
limitMax results (pagination)
offsetStart offset (pagination)
# All pending tasks assigned to this agent
curl -sf -H "Authorization: Bearer $SHIPWRIGHT_TASK_STORE_TOKEN" \
  "$SHIPWRIGHT_TASK_STORE_URL/tasks?status=pending" | jq '.tasks'

# All ready tasks (dependencies met, pending, not HITL-gated)
curl -sf -H "Authorization: Bearer $SHIPWRIGHT_TASK_STORE_TOKEN" \
  "$SHIPWRIGHT_TASK_STORE_URL/tasks?ready=true" | jq '.tasks'

POST /tasks

Create a task. Returns 201 on success, 409 if a task with the same id already exists.

Required fields: title, status

Recommended fields for agent pickup: id, repo (org/repo format), branch, assignee

curl -sf -X POST \
  -H "Authorization: Bearer $SHIPWRIGHT_TASK_STORE_TOKEN" \
  -H "Content-Type: application/json" \
  "$SHIPWRIGHT_TASK_STORE_URL/tasks" \
  -d '{
    "id": "SHE-1.1",
    "title": "Add feature X",
    "status": "pending",
    "repo": "org/repo",
    "branch": "feat/she-1-1-add-feature-x",
    "assignee": "<agent-id>"
  }' | jq .

POST /tasks/bulk

Insert an array of tasks. Skips 409 conflicts silently. Returns { inserted, updated }.

curl -sf -X POST \
  -H "Authorization: Bearer $SHIPWRIGHT_TASK_STORE_TOKEN" \
  -H "Content-Type: application/json" \
  "$SHIPWRIGHT_TASK_STORE_URL/tasks/bulk" \
  -d '[{"id": "SHE-2.1", "title": "...", "status": "pending", "repo": "org/repo", "branch": "feat/she-2-1-..."}]' \
  | jq .

GET /tasks/distinct

Return the distinct sessions and repos visible to the caller. Useful for filtering UI dropdowns.

curl -sf -H "Authorization: Bearer $SHIPWRIGHT_TASK_STORE_TOKEN" \
  "$SHIPWRIGHT_TASK_STORE_URL/tasks/distinct" | jq .

GET /tasks/:id

Fetch a single task by ID. Returns 404 if not found. Agent tokens get 403 if the task belongs to another agent.

curl -sf -H "Authorization: Bearer $SHIPWRIGHT_TASK_STORE_TOKEN" \
  "$SHIPWRIGHT_TASK_STORE_URL/tasks/SHE-1.1" | jq .

PATCH /tasks/:id

Partial update — only provided fields are changed. Returns the updated task.

Agent tokens are restricted to their own tasks. The repo field must remain in org/repo format.

# Transition to in_progress
curl -sf -X PATCH \
  -H "Authorization: Bearer $SHIPWRIGHT_TASK_STORE_TOKEN" \
  -H "Content-Type: application/json" \
  "$SHIPWRIGHT_TASK_STORE_URL/tasks/SHE-1.1" \
  -d '{"status": "in_progress", "startedAt": "2024-01-15T10:00:00Z"}' | jq .

# Transition to pr_open (requires pr + prCreatedAt)
curl -sf -X PATCH \
  -H "Authorization: Bearer $SHIPWRIGHT_TASK_STORE_TOKEN" \
  -H "Content-Type: application/json" \
  "$SHIPWRIGHT_TASK_STORE_URL/tasks/SHE-1.1" \
  -d '{"status": "pr_open", "pr": 42, "prCreatedAt": "2024-01-15T11:00:00Z"}' | jq .

# Transition to approved (requires ciFixAttempts)
curl -sf -X PATCH \
  -H "Authorization: Bearer $SHIPWRIGHT_TASK_STORE_TOKEN" \
  -H "Content-Type: application/json" \
  "$SHIPWRIGHT_TASK_STORE_URL/tasks/SHE-1.1" \
  -d '{"status": "approved", "ciFixAttempts": 0}' | jq .

DELETE /tasks/:id

Delete a task permanently. Returns 204 on success.

curl -sf -X DELETE \
  -H "Authorization: Bearer $SHIPWRIGHT_TASK_STORE_TOKEN" \
  "$SHIPWRIGHT_TASK_STORE_URL/tasks/SHE-1.1"

POST /tasks/:id/claim

Atomically claim a task. Returns 409 if already claimed by another agent.

Agent tokens: claimedBy is set to the token’s agent ID automatically. Admin tokens: claimedBy must be provided in the request body.

curl -sf -X POST \
  -H "Authorization: Bearer $SHIPWRIGHT_TASK_STORE_TOKEN" \
  -H "Content-Type: application/json" \
  "$SHIPWRIGHT_TASK_STORE_URL/tasks/SHE-1.1/claim" | jq .

POST /tasks/:id/heartbeat

Touch the heartbeatAt timestamp to signal liveness. Call this periodically during long-running tasks to prevent the stale-claim reaper from releasing the task.

curl -sf -X POST \
  -H "Authorization: Bearer $SHIPWRIGHT_TASK_STORE_TOKEN" \
  "$SHIPWRIGHT_TASK_STORE_URL/tasks/SHE-1.1/heartbeat" | jq .

POST /tasks/:id/complete

Mark a task done. Use for tasks that complete without opening a PR.

curl -sf -X POST \
  -H "Authorization: Bearer $SHIPWRIGHT_TASK_STORE_TOKEN" \
  "$SHIPWRIGHT_TASK_STORE_URL/tasks/SHE-1.1/complete" | jq .

POST /tasks/:id/fail

Mark a task blocked. Optionally include a reason in the request body.

curl -sf -X POST \
  -H "Authorization: Bearer $SHIPWRIGHT_TASK_STORE_TOKEN" \
  -H "Content-Type: application/json" \
  "$SHIPWRIGHT_TASK_STORE_URL/tasks/SHE-1.1/fail" \
  -d '{"reason": "CI environment unavailable"}' | jq .

POST /tasks/:id/release

Unclaim a task — resets claimedBy and claimedAt, returns status to pending. Use when an agent is shutting down or voluntarily giving up a task.

curl -sf -X POST \
  -H "Authorization: Bearer $SHIPWRIGHT_TASK_STORE_TOKEN" \
  "$SHIPWRIGHT_TASK_STORE_URL/tasks/SHE-1.1/release" | jq .

?ready=true Semantics

When ?ready=true (or ?state=ready) is set, ?status and ?id filters are ignored. Only ?session applies as a post-filter.

A task is ready when all of the following are true:

  1. status === "pending"
  2. hitl is not true (HITL tasks are excluded until manually cleared)
  3. Every dependency in dependencies[] is satisfied

Dependency-satisfied rules (first match wins):

RuleConditionResult
1Dependency status is merged, done, deploying, deployed, or cancelledSatisfied
2Dependency is on the same branch and has status pr_open or approvedSatisfied (bundled PR)
3Dependency has status pr_open with a PR number, and GitHub reports the PR as mergedSatisfied
4None of the above matchNot satisfied — task excluded

Note: The task store has no direct GitHub access. Rule 3 (GitHub-merged check) resolves to false in the task store itself — this rule is applied by the agent harness when it calls ?ready=true.

Diagnosing empty ?ready=true results

When { tasks: [], total: 0 } is returned:

Likely causeHow to check
No tasks assigned to this agentUse an admin token to see all ready tasks
HITL flag setQuery ?status=pending — check for "hitl": true. Clear the flag once the human action is complete
Dependencies not satisfiedQuery ?status=pending — check each task’s dependencies[] and verify each dep’s status against the four rules
Queue is emptyNo pending tasks exist at all

Task Schema

All fields from the Task model. Fields marked as required are enforced on creation or the relevant status transition.

Core fields

FieldTypeRequiredNotes
idstringon createStable key — used by dependency resolution and all updates. Convention: SHE-X.Y
titlestringalwaysShort description of the task
statusTaskStatusalwaysSee Status Lifecycle
repostring?recommendedorg/repo format; required for dev-task worktree creation
branchstring?recommendedBranch name; absent → task is skipped by dev-task
assigneestring?recommendedAgent ID; unset = pool task (any agent with repo scope can pick it up)
dependenciesstring[]Array of task IDs that must be satisfied before this task is ready
descriptionstring?Full task description (Markdown)
acceptanceCriteriastring[]Checklist items for task completion
layerstring?Architectural layer (e.g. Shared, API, UI)
sessionstring?Planning session slug for grouping related tasks
sourcestring?Origin label (e.g. plan session name)
typestring?Task category (e.g. feature, fix, test)
prioritystring?Priority hint for ordering
hoursfloat?Estimated effort in hours
issuestring?Linked GitHub issue URL or ID
modelstring?Preferred Claude model: haiku, sonnet, or opus
complexityint?Complexity score (1–5)
hitlboolean?When true, excluded from ?ready=true until manually cleared
notestring?Free-form notes
metadataJSON?Escape hatch for structured data without dedicated columns

Status transition fields

FieldTypeSet when
startedAtstring?in_progress
print?pr_open (required)
prUrlstring?pr_open (the GitHub PR URL)
prCreatedAtstring?pr_open (required)
ciFixAttemptsint?approved or merged (required)
mergeCommitstring?merged
mergedAtstring?merged
deployingAtstring?deploying
deployedAtstring?deployed
completedAtstring?done
cancelledAtstring?cancelled
blockedAtstring?blocked
blockedReasonstring?blocked (human-readable reason)

Claim and liveness fields

FieldTypeSet when
claimedBystring?→ claimed (agent ID)
claimedAtstring?→ claimed (ISO timestamp)
agentHintstring?Creation — suggested agent to pick up this task
heartbeatAtstring?→ heartbeat (ISO timestamp of last liveness ping)

HITL fields

FieldTypeSet when
hitlboolean?Set to true when human action is required before proceeding
hitlNotifiedAtstring?ISO timestamp when the HITL notification was sent

Execution metrics (set by agent on completion)

FieldTypeNotes
inputTokensint?Total Claude input tokens used
outputTokensint?Total Claude output tokens used
cacheReadTokensint?Prompt cache read tokens
cacheCreationTokensint?Prompt cache creation tokens
costUsdfloat?Estimated USD cost
effortLevelstring?low, medium, or high
coverageDeltafloat?Test coverage change (positive = improved)
simplifyTotalint?Total simplification findings
simplifyDryint?DRY violations found
simplifyDeadCodeint?Dead code findings
simplifyNamingint?Naming issue findings
simplifyComplexityint?Complexity findings
simplifyConsistencyint?Consistency findings

System fields

FieldTypeNotes
createdAtDateTimeSet by the database on creation
updatedAtDateTimeUpdated by the database on every write

HTTP Status Codes

CodeMeaning
200Success (GET, PATCH, POST action endpoints)
201Created (POST /tasks)
204No content (DELETE)
400Bad request — missing required field, invalid format, or repo not in agent scope
403Forbidden — agent token tried to access another agent’s task
404Not found
409Conflict — task ID already exists (POST) or task already claimed (claim endpoint)

Curl Examples: Full Happy-Path Lifecycle

The following sequence demonstrates the complete lifecycle from picking a ready task to heartbeat:

BASE="$SHIPWRIGHT_TASK_STORE_URL"
TOKEN="$SHIPWRIGHT_TASK_STORE_TOKEN"
AUTH="Authorization: Bearer $TOKEN"

# 1. Check for an interrupted task first (resume if found)
INTERRUPTED=$(curl -sf -H "$AUTH" "$BASE/tasks?status=in_progress" | jq -r '.tasks[0].id // empty')

# 2. If nothing in progress, pick the next ready task
if [ -z "$INTERRUPTED" ]; then
  TASK=$(curl -sf -H "$AUTH" "$BASE/tasks?ready=true" | jq '.tasks[0]')
  TASK_ID=$(echo "$TASK" | jq -r '.id')
else
  TASK_ID="$INTERRUPTED"
fi

echo "Working on task: $TASK_ID"

# 3. Claim the task (atomic — 409 if another agent beats you to it)
curl -sf -X POST -H "$AUTH" -H "Content-Type: application/json" \
  "$BASE/tasks/$TASK_ID/claim" | jq .

# 4. Mark in_progress
curl -sf -X PATCH -H "$AUTH" -H "Content-Type: application/json" \
  "$BASE/tasks/$TASK_ID" \
  -d "{\"status\": \"in_progress\", \"startedAt\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}" | jq .

# 5. Heartbeat (call periodically during long work)
curl -sf -X POST -H "$AUTH" "$BASE/tasks/$TASK_ID/heartbeat" | jq .

# 6. Open a PR (requires pr number and timestamp)
PR_NUMBER=42
curl -sf -X PATCH -H "$AUTH" -H "Content-Type: application/json" \
  "$BASE/tasks/$TASK_ID" \
  -d "{\"status\": \"pr_open\", \"pr\": $PR_NUMBER, \"prCreatedAt\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}" | jq .

Required Fields Per Status Transition

TransitionFields that must be set
in_progressstartedAt (strongly recommended)
pr_openpr (PR number), prCreatedAt (ISO timestamp)
approvedciFixAttempts (set to 0 if no CI fixes were needed)
mergedciFixAttempts, mergedAt (strongly recommended)
blockedblockedReason (strongly recommended), blockedAt

Enforcement: The task store does not hard-validate these fields on every PATCH — missing fields silently result in incomplete records that may break downstream tooling (e.g. the deploy scanner checks for pr when picking up pr_open tasks). Always set the required fields on the relevant transition.

See also

  • PRs API — PR review state tracking: GET/POST/PATCH /prs, claim, heartbeat, patch cycles, and release
  • Configuration ReferenceSHIPWRIGHT_TASK_STORE_URL, SHIPWRIGHT_TASK_STORE_TOKEN, and all other env vars
  • The Agent — how the agent runtime consumes this API to run the dev-task loop