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 var Description
SHIPWRIGHT_TASK_STORE_URL Base URL of the task store service (provisioned at deploy time)
SHIPWRIGHT_TASK_STORE_TOKEN Bearer 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

Status Meaning
pending Not yet started; eligible for ?ready=true pickup if dependencies are met
in_progress Claimed by an agent; work is underway
pr_open PR has been opened on GitHub; pr and prCreatedAt must be set
approved PR passed review; ciFixAttempts must be set
merged PR merged to main; mergedAt should be set
deploying Deploy pipeline is running; deployingAt should be set
deployed Successfully deployed to production; deployedAt should be set
blocked Explicitly paused — waiting on human action or unresolvable dependency
done Task complete without a PR (e.g. research or HITL tasks)
cancelled Permanently abandoned

Endpoints

GET /tasks

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

Query param Description
ready=true Return only tasks that are ready to pick up (see ?ready=true semantics)
state=ready Alias for ?ready=true
state=blocked Return pending tasks with unmet dependencies, HITL-gated tasks, and explicitly blocked tasks
state=open Filter to open tasks (pending, in_progress, pr_open, approved)
state=closed Filter to closed tasks (merged, done, deploying, deployed, cancelled)
state=in_progress Filter to actively running tasks
status Filter by exact status value (e.g. ?status=pr_open)
session Filter by planning session slug
assignee Filter by assignee (admin tokens only — agent tokens are auto-scoped)
repo Filter by repo in org/repo format
branch Filter by branch name
pr Filter by PR number
claimedBy Filter by the agent that claimed the task
limit Max results (pagination)
offset Start 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):

Rule Condition Result
1 Dependency status is merged, done, deploying, deployed, or cancelled Satisfied
2 Dependency is on the same branch and has status pr_open or approved Satisfied (bundled PR)
3 Dependency has status pr_open with a PR number, and GitHub reports the PR as merged Satisfied
4 None of the above match Not 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 cause How to check
No tasks assigned to this agent Use an admin token to see all ready tasks
HITL flag set Query ?status=pending — check for "hitl": true. Clear the flag once the human action is complete
Dependencies not satisfied Query ?status=pending — check each task’s dependencies[] and verify each dep’s status against the four rules
Queue is empty No 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

Field Type Required Notes
id string on create Stable key — used by dependency resolution and all updates. Convention: SHE-X.Y
title string always Short description of the task
status TaskStatus always See Status Lifecycle
repo string? recommended org/repo format; required for dev-task worktree creation
branch string? recommended Branch name; absent → task is skipped by dev-task
assignee string? recommended Agent ID; unset = pool task (any agent with repo scope can pick it up)
dependencies string[] Array of task IDs that must be satisfied before this task is ready
description string? Full task description (Markdown)
acceptanceCriteria string[] Checklist items for task completion
layer string? Architectural layer (e.g. Shared, API, UI)
session string? Planning session slug for grouping related tasks
source string? Origin label (e.g. plan session name)
type string? Task category (e.g. feature, fix, test)
priority string? Priority hint for ordering
hours float? Estimated effort in hours
issue string? Linked GitHub issue URL or ID
model string? Preferred Claude model: haiku, sonnet, or opus
complexity int? Complexity score (1–5)
hitl boolean? When true, excluded from ?ready=true until manually cleared
note string? Free-form notes
metadata JSON? Escape hatch for structured data without dedicated columns

Status transition fields

Field Type Set when
startedAt string? in_progress
pr int? pr_open (required)
prUrl string? pr_open (the GitHub PR URL)
prCreatedAt string? pr_open (required)
ciFixAttempts int? approved or merged (required)
mergeCommit string? merged
mergedAt string? merged
deployingAt string? deploying
deployedAt string? deployed
completedAt string? done
cancelledAt string? cancelled
blockedAt string? blocked
blockedReason string? blocked (human-readable reason)

Claim and liveness fields

Field Type Set when
claimedBy string? → claimed (agent ID)
claimedAt string? → claimed (ISO timestamp)
agentHint string? Creation — suggested agent to pick up this task
heartbeatAt string? → heartbeat (ISO timestamp of last liveness ping)

HITL fields

Field Type Set when
hitl boolean? Set to true when human action is required before proceeding
hitlNotifiedAt string? ISO timestamp when the HITL notification was sent

Execution metrics (set by agent on completion)

Field Type Notes
inputTokens int? Total Claude input tokens used
outputTokens int? Total Claude output tokens used
cacheReadTokens int? Prompt cache read tokens
cacheCreationTokens int? Prompt cache creation tokens
costUsd float? Estimated USD cost
effortLevel string? low, medium, or high
coverageDelta float? Test coverage change (positive = improved)
simplifyTotal int? Total simplification findings
simplifyDry int? DRY violations found
simplifyDeadCode int? Dead code findings
simplifyNaming int? Naming issue findings
simplifyComplexity int? Complexity findings
simplifyConsistency int? Consistency findings

System fields

Field Type Notes
createdAt DateTime Set by the database on creation
updatedAt DateTime Updated by the database on every write

HTTP Status Codes

Code Meaning
200 Success (GET, PATCH, POST action endpoints)
201 Created (POST /tasks)
204 No content (DELETE)
400 Bad request — missing required field, invalid format, or repo not in agent scope
403 Forbidden — agent token tried to access another agent’s task
404 Not found
409 Conflict — 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

Transition Fields that must be set
in_progress startedAt (strongly recommended)
pr_open pr (PR number), prCreatedAt (ISO timestamp)
approved ciFixAttempts (set to 0 if no CI fixes were needed)
merged ciFixAttempts, mergedAt (strongly recommended)
blocked blockedReason (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