crewship routine
Manage workspace routines — AI-authored, repeatable workflow recipes that any crew can invoke.crewship pipeline (back-compat — every routine X invocation also works as pipeline X)
See Routines guide for the conceptual overview.
Subcommands
For batch evaluation across the eval-* routine fleet (matrix sweep, head-to-head tier compare, regression baselines), see the eval CLI.
crewship routine list
List workspace routines, sorted by usage by default.
Output columns: SLUG, INVOC, LAST STATUS, AUTHOR CREW, DESCRIPTION
crewship routine get <slug>
Show full routine detail including the DSL definition.
--format json|yaml|ndjson returns the routine row exactly as the API sent it.
This command previously owned a local
--format flag accepting only
human or json. Because the local flag took the name format it shadowed
the global one and captured its -f shorthand, so -f json worked while
-f yaml and -f ndjson were rejected outright — on a command whose help
advertised all five. The global flag now applies; --format human still
renders the human report.crewship routine access <slug>
Ask the server which routine actions your current workspace membership permits.
run is conditional even when your role or routine.run grant qualifies: status,
inputs, dependencies, budget and concurrency are checked when you start it.
crewship routine save
Save a new routine from a JSON DSL file. Test_run gate runs first; if it fails, save aborts with a structured error pointing at the failing step.
Atomic authoring (B8): passing
--cron creates the routine, its version,
and the schedule together — a bad cron expression or timezone rolls the
whole save back, not just the trigger. The save output names the first
fire time: Trigger: schedule 0 9 * * 1-5 — first run 2026-09-08T07:00:00Z.
With --draft, the trigger is created DISABLED and the output instead
shows Trigger: schedule ... — DISABLED, awaiting MANAGER approval plus
the exact crewship routine schedules activate <id> command to turn it on
— that command is also how the one approval item this raises gets
resolved. --cron and --trigger-manual are mutually exclusive.409 schedule_conflict on re-save. A recipe change that an enabled,
unpinned plan’s stored preset can no longer satisfy (a renamed or newly
required input) is refused, and the CLI prints the server’s hint. The way
out is the same save carrying the repaired preset — the plan is updated
before the preset gate runs, so this lands where the bare save did not:schedules update --inputs, or disable → publish →
repair → re-enable) work too; this one is a single command.--author-agent is not a credit line. Every crewship step in the routine
dispatches under that agent’s identity, and issue.comment has no other
source for one — a comment needs an author it can name. Saving a routine that
contains an issue.comment step without --author-agent is refused:400: agent_id is required.crewship routine draft
Keep edits separate from the published recipe. These commands require MANAGER
or above in the selected workspace. A saved draft survives editor closure and
server restart; it does not start work or activate its proposed schedule.
get returns either the saved draft or a revision-zero baseline. For a new
recipe, fill document.slug, document.name, and document.definition; the
slug must match the envelope. save returns the new revision envelope. Use a
separate output file so shell redirection cannot truncate the input file.
publish validates the exact definition without running its steps, then
publishes the exact saved ID/revision. Capability changes that require review
need an explicit --approve-risk. A stale editor, changed published recipe or
incompatible recurring-plan preset returns a conflict; the draft stays saved.
Unpinned recurring plans follow the newly published recipe. Accepted runs and
pinned plans keep their version.
discard deletes only the revision named by the file. Legacy routine save
continues to publish directly; it has not become a draft-save command.
Chat authoring uses get_routine_draft and save_routine_draft against the
same revision envelope. The agent returns an editor_url containing the
saved draft ID. Open it in Routines to review Recipe / Test / Publish.
A discarded-and-recreated draft rejects the old link. Neither agent draft
tool publishes or activates a trigger; legacy save_routine still publishes
directly. Crew/workspace/acting-agent identity is checked by the server.
A publication or rollback blocked by a recurring preset returns HTTP 409
with schedule_conflict (schedule_id, name, reason). An enabled plan
following HEAD must remain compatible with the selected recipe. Pinned
plans retain their archived version; disabled plans do not block publication.
To repair incompatible schemas, save the recipe and its trigger inputs in
one operation, or disable the plan, publish, then repair its inputs and enable
it. Re-enabling validates the effective recipe version and inputs, including
a wake routine’s preset. Disabling an invalid legacy plan remains possible.
Saving new inputs still checks the current target; pausing alone does not
make inputs for an unpublished draft valid against the old HEAD.
The browser keeps live plans, webhooks and budgets in Plan, outside the
draft editor. Cancelling Edit discards local edits, not an explicitly saved
draft. Publish opens a change review and requires confirmation before
validation and publication. Run reviews inputs and possible effects;
Test validates without running agents or external actions.
New manual one-time, delayed and debounced starts pin the accepted published
version. Coalescing preserves the first pin and validates the replacement
inputs against it. routine pending list shows the version. Missing archives
are refused explicitly; new manual deferrals without an archive return 409.
Only older already-stored unpinned entries retain live-at-dispatch behavior.
crewship routine delete <slug>
Soft-delete a routine after a confirmation prompt. The row is preserved for
audit, but the routine is no longer available for normal listing or invocation.
Its schedules are deleted with it: the same transaction soft-deletes and
disables every schedule targeting the routine, so nothing keeps firing (or
plan-listing) against a deleted routine.
Backed by
DELETE /api/v1/workspaces/{ws}/pipelines/{slug}.
crewship routine run <slug>
Invoke a saved routine against the live execution tier.
When
--wait polls an existing run, failed, interrupted, cancelled, canceled, and
needs_reconciliation terminate polling with a nonzero exit in both human and
machine formats. Reconciliation requires inspecting the execution outcome before
retrying; it is never reported as successful completion.
Output: run_id, status, output, duration_ms, cost_usd. Immediate runs
use COMPLETED, FAILED, or CANCELLED; WAITING means the run is parked at
an approval waitpoint and is not a failure; DEDUPED means the idempotency key
returned the original run. A delayed, debounced or --fire-at invocation
returns SCHEDULED with pending_id, fire_at, priority, and coalesced
instead of a run id (a --fire-at start also appears in
pending list with its pinned RECIPE VERSION). With
--async an in-flight run returns IN_PROGRESS plus the run id. A run that
finishes before the asynchronous acknowledgement may still return its ordinary
200 result. The API uses 202 for deferred and in-flight async receipts.
If the routine hits a
kind: approval gate, run terminates with status: WAITING (exit 0, not a failure): it prints paused at approval step: <id> plus the waitpoint token and the approve/reject command hints, then releases its execution slot. Resume it with crewship routine waitpoints approve|reject <token>. See HITL waitpoints.With --wait, the CLI instead keeps polling the run until the approval is resolved and the run finishes — the “trigger and get the real outcome” path for scripts and agents. You can also wait later with crewship wait --routine <run_id>. Deferred receipts (SCHEDULED from --delay/--debounce-key) have no run id yet and are not waitable.Common errors
- API error (500):
pipeline: concurrency_key rendered to empty value (referenced input missing or empty)— the DSL declared aconcurrency_keytemplate that references an input you didn’t supply, and the template has no literal text to fall back on. See Troubleshooting in the routines guide for the four fixes.
crewship routine result <run_id>
Prints a finished run’s deliverable: the final output, followed by a
Files changed during this run section listing the artefacts on the
run’s crew that were written inside the run window (e.g. report.pdf,
summary.md) with a fetch hint. For a document-processing routine the
files are the deliverable, so this is the one view that surfaces them.
--client view lists the same files by bare name only — the
internal crew id, agent-slug paths, and the crew files get fetch hint
are stripped so the output is safe to forward to a customer. Backed by
GET …/pipeline-runs/{runId}/files.
crewship routine report <run_id>
Build a shareable report containing a run’s inputs, step outcomes and outputs,
final output, and (in the operator view) cost and duration. Markdown is the
default; use -f html for a self-contained HTML page.
crewship routine approve <slug>
Approve a routine sitting in proposed status — the maker-checker checkpoint that flips it live so crews can invoke it. Requires the MANAGER+ role.
POST /api/v1/workspaces/{ws}/pipelines/{slug}/approve. Output: Routine <slug> → <status>. A 403 role error or 409 “routine is awaiting approval” run refusal is rendered from the server’s Problem Details response.
crewship routine reject <slug>
Reject a proposed routine, removing it. Requires the MANAGER+ role.
POST /api/v1/workspaces/{ws}/pipelines/{slug}/reject. Output: Routine <slug> → <status>.
crewship routine disable <slug>
Airbag switch: disable an active routine. Cancels any in-flight runs and refuses new invocations (run then returns 409 routine is disabled). Requires the OWNER/ADMIN role.
POST /api/v1/workspaces/{ws}/pipelines/{slug}/disable. Output: Routine <slug> → <status>; when runs are cancelled a second line reports cancelled N in-flight run(s).
crewship routine enable <slug>
Re-enable a disabled routine so it accepts invocations again. Requires the OWNER/ADMIN role.
POST /api/v1/workspaces/{ws}/pipelines/{slug}/enable. Output: Routine <slug> → <status>.
crewship routine doctor <slug>
Preflight checklist that walks every checkable precondition before run and emits a ✓/⚠/✗ report. Catches the blind alleys (missing crew provisioning, agent slug typo, missing credential, contradictory validation gate, cost cap too tight) at one round-trip instead of one failure-per-run.
routine_exists— routine present in the workspaceauthor_crew— author crew exists, has a devcontainer config, provisioning status iscompleted(notin_progressorfailed)agent_slugs— every step’sagent_slugand everyoutcomes.grader_agent_slugresolves to an agent in the author crew (runtime resolution is crew-scoped)credential:<TYPE>— every entry incredentials_requiredhas an active workspace credential of that typeegress_allowlist—egress_targetsdeclared whenhttpsteps are present; no dead entries that match no host (*,*.*, empty); warns on loopback hosts (localhost,127.0.0.0/8) — legitimate on dev boxes, so a warning, not a failurecost_cap—max_cost_usdis set and is at least 1.5×estimated_cost_usdvalidation:<step_id>— gates are structurally satisfiable (min_length ≤ max_length, no string in bothmust_containandmust_not_contain)
crewship routine bench <slug>
Run a routine N times with the same inputs and report pass-rate, cost stats, and latency distribution. Statistical stability characterisation — use to decide whether a routine is production-ready at its current tier.
Output reports total / mean / p95 / max cost; p50 / p95 / max duration; fail-reason breakdown bucketed into
cost-cap / rubric-fail / gate-fail / auth-fail / timeout / other. Headline verdict label so the operator doesn’t need to interpret the stats:
crewship routine backtest <slug>
Replay a corpus of recent, successful runs against a candidate version and diff the outputs — validate a routine change before rollout, without creating a new version or touching which version is live. The live version is unchanged: --against names an immutable versions row; the backtest never bumps head_version and never changes what run/schedules/webhooks execute. Replayed runs still execute real work and may make external writes or incur model costs.
Composes two existing primitives — no new engine surface:
- Corpus selection —
GET /api/v1/workspaces/{ws}/pipelines/{slug}/run-records?status=completed, filtered client-side to the--lastwindow (newest-first, capped at--limit). - Pinned-version replay — for each corpus run,
POST /api/v1/workspaces/{ws}/pipelines/runs/{run_id}/replaywith{"pinned_version": N}. This replays that run’s original captured inputs against the pinned version’s definition instead of head (samePinnedVersionmechanism the cron scheduler and webhook dispatch already use for pinned triggers) and is graded against what was actually recorded.
Aggregate verdict (also the process exit code —
REGRESSION_DETECTED exits non-zero, CI-friendly like eval baseline diff):
For a matrix sweep across many routines/tiers or a saved regression baseline, see
eval scenarios and eval baseline — backtest answers a narrower question (“is this candidate version safe to promote?”) using the workspace’s own recent traffic as the corpus instead of an authored eval suite.
crewship routine appearance
A routine’s icon and colour — how it renders in lists. A workspace ends up with dozens of routines that look identical; an icon makes one findable at a glance, using the same set crews and projects use.
Presentation only. It never touches the definition, so setting an icon does not create a routine version, does not re-run the governance risk check, and does not invalidate a save token. Leave a routine unset and the UI derives a stable icon from its slug, so every routine still looks different without anyone choosing.
routine appearance get <slug>
routine appearance set <slug>
--clear removes both.
--color takes a palette id: blue, emerald, violet, amber, rose, cyan, lime, fuchsia.
API: PATCH /pipelines/{slug}/appearance.
crewship routine budget
Per-routine monthly spend budget (#1422 item 3) — a cap you set out-of-band from the DSL, compared against actual pipeline_runs.cost_usd for the current calendar month. Distinct from DSL max_cost_usd (a per-RUN hard gate the executor enforces mid-run); this is a budget-vs-actual view, not an enforcement gate — going over doesn’t block the next run.
routine budget get <slug>
Requires crewship login and a workspace context; the API is a member-scoped
read. --format json returns the response fields exactly, including month,
spent_usd, and (when a positive cap exists) pct_used and over_budget.
$spent of $cap (pct%), or a “no budget set” hint with the current spend if none is configured. --format json returns {slug, has_budget, monthly_budget_usd, month, spent_usd, pct_used, over_budget} (pct_used/over_budget are omitted when has_budget is false).
routine budget set <slug> --amount <N>
Setting a budget never touches the DSL and never bumps the routine’s version history — it’s independent of
routine save/rollback.
The mutation requires the OWNER/ADMIN manage capability and returns 200 OK
with the same budget object as budget get.
routine budget summary
ROUTINE BUDGET SPENT PCT STATUS (STATUS shows OVER when spend exceeds the cap), plus a workspace total line. --format json returns {month, routines: [...], total_budget_usd, total_spent_usd}.
This is a member-scoped read and returns 200 OK; each routine row includes
slug, monthly_budget_usd, spent_usd, and optional pct_used/
over_budget.
crewship routine state
Inspect and repair a routine’s durable cross-run state — the key/value bucket behind {{ routine.state.<key> }} and a step’s state_write binding. See Cross-run state for how a routine reads and writes it.
State is scoped per (routine, schedule): two cron schedules of the same routine keep independent watermarks, and manual / webhook runs share a separate bucket shown as (manual/webhook). That isolation is the reason this command exists — when a routine “stops seeing new items”, the stuck cursor sits in a bucket you can’t guess from the outside, and until now nothing outside a run could read it.
routine state list <slug>
schedule: <id> followed by a KEY VALUE UPDATED table. Omitting --schedule shows every bucket — that’s the diagnostic default. UPDATED is usually the tell: a cursor frozen days ago next to schedules that ran this morning.
Passing --schedule "" explicitly is not the same as omitting it: an empty value selects the shared manual/webhook bucket, while an absent flag means “all buckets”.
--format json returns {slug, buckets: [{schedule_id, entries: [{key, value, updated_at}]}]}.
routine state set <slug> <key> <value>
{{ routine.state.<key> }}. This is the preferred repair — rewinding to a known-good cursor is bounded, where clearing is not.
routine state rm <slug> <key>
routine state clear <slug>
crewship routine records <slug>
List runs for a routine using the pipeline_runs projection (introduced in migration v83). Backed by indexed columns, so this is O(log n) where runs is O(scan-with-LIKE) over journal_entries. Prefer records for filterable / scriptable run history.
Falls back with a clear message to
crewship routine runs <slug> when the server predates v83.
Reading the TRIGGER and CHAIN columns
TRIGGER is not a verbatim print of the row’s triggered_via. Every deferred
run — cron schedules and automations alike — is fired by
the same dispatcher and stored with triggered_via: "schedule", so the raw
field cannot tell the two apart. When a run carries an automation_name, the
column reads automation and SOURCE names the rule that started it:
REPORTED USAGE is the run’s cost_usd: the usage the agent CLIs reported, priced
at list rates. It is not a billed amount. Calls under a subscription plan are not
charged per call, and only metered calls reach the cost ledger (crewship cost).
Machine formats keep the field name cost_usd.
OUTCOME is the §9.6 routing decision (work package B6, #2349) — - when the run
predates the outcome column. It is not the same question STATUS answers: a run whose
process completed cleanly but never reported a recognised outcome — while holding an
agent_run step that could have — shows FAILED here (error_message names it
"no outcome reported"); an agentless routine, which has nobody to report one, shows
SUCCEEDED with no error. NEEDS_HUMAN is the one value
that raises an inbox item (crewship inbox list --kind run_needs_human). Full vocabulary:
API reference → Issues → Outcome.
CHAIN appears only when some run on the page was composed rather than
started directly — a routine that another routine called, or one an automation
fired from an event a run emitted. The number is chain_depth: hops from
whatever a human did, capped at 8. A workspace where nothing composes never
sees the column.
Structured output (--format json|yaml|ndjson) always carries the underlying
fields, whether or not the table drew them:
crewship routine runs <slug>
List recent invocations from the routine’s journal history. The human table
shows timestamp, event type, severity, run ID, and summary; structured formats
return the complete rows.
Backed by
GET /api/v1/workspaces/{ws}/pipelines/{slug}/runs.
crewship routine active
List in-flight runs across the entire workspace (not scoped to a single slug). Useful when triaging “is anything still running?” before a deploy or restart.
GET /api/v1/workspaces/{ws}/pipelines/runs/active — the handler note in internal/api/pipeline_runs.go flags it as single-replica scope: in a multi-replica deployment the response only reflects runs hosted by the replica the request landed on.
Output columns: RUN_ID (truncated), SLUG, STATUS (running, or waiting for a run parked at a waitpoint), STARTED, CANCEL_REQ, CONCURRENCY_KEY. Empty list prints No active runs.
Pair with routine cancel <run_id> to terminate any of the listed runs.
crewship routine runs-all
List runs of any status across every routine in the workspace — not scoped to one slug, and not limited to in-flight runs. Backs the Activity dock’s Runs sub-tab.
Backed by
GET /api/v1/workspaces/{ws}/pipeline-runs, sorted started_at DESC (newest first).
Output columns: RUN_ID (truncated), SLUG, STATUS, MODE, STARTED, DURATION, REPORTED USAGE (~$ reported by the agent CLIs, not billed spend; — when none), TRIGGER. Empty list prints No runs.
Distinct from
routine runs <slug> / routine records <slug> (one routine’s history) and routine active (in-flight only, no filters): runs-all is the cross-pipeline, any-status feed, filterable server-side.crewship routine logs <run_id>
Fetch the full journal trace for a single run. Post-mortem and CI-diagnostics surface — every step’s prompt, output, validation verdict, and cost in one document.
--show-outputs prints the full, untruncated output each step returned (from the run’s persisted step_outputs), for post-mortem “what did this step actually return?” debugging — the slug-free state view previously showed only status/error, and routine run truncates step outputs to a preview. Works on the slug-free form (routine logs <run_id> --show-outputs); the whole run map (including step_outputs) is also available via --format json.--slug):
DURATION and COST columns surface the per-step duration_ms / cost_usd from the journal payload (em-dash means “not applicable” — started / failed events don’t carry them). Matches the Runs-tab waterfall in the UI so the same numbers reconcile across surfaces.
Pair with
routine watch for live observation and routine logs for post-hoc forensics.
crewship routine dry-run <slug>
Preview what the routine WOULD execute without invoking agents. Returns a structured would_execute report with each step’s resolved tier, rendered prompt, and estimated cost.
crewship routine validate [file.json|file.yaml]
Offline DSL validation with no server call (by default). Reads from the file argument or stdin if absent.
validate catches three authoring mistakes that used to only surface at save or the first run:
concurrency_keythat can render empty — a key built entirely from{{ inputs.X }}refs where none isrequired: true/defaulted (e.g."{{ inputs.account_id }}"withaccount_idoptional) is rejected: a run that omits the inputs renders the key empty and dies at run time (concurrency_key rendered to empty value). Anchor one referenced input, or add literal text ("tenant-{{ inputs.account_id }}"always renders non-empty) — both accepted.- Unsatisfiable output gates — a step whose
validationhasmin_length > max_length, or the same token in bothmust_containandmust_not_contain, is rejected: no output could ever pass it. - Dead egress entry — an
egress_targetsvalue of*,*.*, or an empty host matches no real host at run time (targets are literal/subdomain-suffix matched, not globbed), so it silently denies everyhttpstep’s egress. Rejected at author time. List real hostnames instead, or omitegress_targetsentirely for unrestricted egress. (Loopback hosts likelocalhost/127.*are matchable and stay adoctorwarning, not a hard error — legitimate on dev boxes.)
Resolving agent slugs offline
By defaultvalidate skips the “does this agent_slug exist?” check because it needs the crew roster. Supply the roster to make typos fail here instead of at save:
agent_slug (or an outcomes.grader_agent_slug) not in the resolved set fails validation, naming the step — and, if a close match exists in the resolved roster, suggests it (did you mean: triage?). The same fuzzy suggestion applies to an unresolved {{ inputs.X }} reference against a typo’d input name.
Every failure in one pass, with a jump-to path
validate no longer stops at the first problem — it accumulates every static-check failure it finds and reports them all together, each prefixed with a JSON-pointer path to the offending field (/name, /steps/2/agent_slug, /steps/0/http/url, …) so an editor/LSP integration can jump straight there:
capabilities | claude -p → validate → fix authoring loop (see Routines cookbook): an AI author fixing every reported field in one revision converges in fewer round-trips than fixing one error, re-running, hitting the next one, and repeating. The one exception is the step-count bound (MaxPipelineSteps, 500) — a definition already over that limit fails immediately on its own, since walking the rest of the checks against an oversized definition isn’t meaningful.
crewship routine capabilities <crew>
Dump the authoring bundle for a crew: the routine DSL schema, agent slugs,
container CLIs and datastores, connected integrations and enabled tools, and
wired runtimes. The human view is a summary; JSON/YAML/NDJSON include the full
bundle, including the schema.
GET /api/v1/crews/{crewId}/capabilities.
crewship routine init
Scaffold an editable routine DSL locally. With no flags it prints a minimal
one-step agent_run skeleton; --script emits a deterministic script-backed
scaffold; --from clones an existing routine definition and requires the
server. --from and --script are mutually exclusive.
crewship routine schema
Print the published draft 2020-12 routine DSL JSON Schema, or write it to a
file for editor autocomplete and offline authoring.
crewship routine fixture-test <recipe.json|recipe.yaml>
Test a selected top-level step without contacting Crewship or executing external
work. This is separate from validate (no step computation) and step-run
(real execution). The HTTP API exposes the same implementation at
POST /api/v1/workspaces/{workspaceId}/pipelines/fixture_test, and --remote
sends this command’s input there instead of running it in the binary — the
same request body, the same report, and the server’s 400 (untestable recipe
or step) or 403 (below the create tier) relayed as the usual API error.
Use it from an agent container that has a token but no local build, or to
confirm the server’s build reaches the verdict yours does. The default stays
offline: no login, no workspace, no network.
Transforms use the same pure Go transform implementation as a live run and
refuse missing template values. Agent/HTTP/script steps only validate the
supplied replacement; without it they fail closed. Other types are not
supported. Output schema validation blocks file and remote schema references;
local fragment references remain supported.
The report includes
execution_mode: fixtures, output_source, exact
definition_hash, fixture_hash, output, valid, validation_declared, and
limitations. No declared checks is explicitly distinguished from passing a
real output contract. A failed output check returns a nonzero CLI exit code.
This does not run the DAG, conditions, retries, outcome graders or real model
calls, does not prove credentials/service behavior, and does not mint a
publication token. Test data is supplied per request/file; there is no shared
production fixture cache or persistent fixture store. Editing a definition or
input changes its evidence hash. Existing run exports can be used as input
files subject to their normal workspace access.
In the editor’s Test section, Load captured data copies a run’s recorded
inputs and available outputs on request. The source run/hash remain visible.
The archive and output read must both be available; failures never become empty
fixtures. For agent/HTTP/script steps, Use captured output for this step is
an explicit replacement choice. Import does not resume the source or run work.
No server environment or credential vault is read for --env, --metadata
or --secrets; they are explicit samples.
crewship routine step-run <slug> <step>
This executes real work and can make external writes or incur model costs.
--input and --outputs supply data; they do not isolate the executed step.
The response includes execution_mode: live. The legacy simulated: true
means only that no pipeline run record is created.
Execute one agent_run, http, script, or transform step in isolation,
without a DAG or persisted run record. wait, notify, call_pipeline, and
code steps are unsupported. HTTP and script steps still perform their real
egress/container work.
The human output includes the PASS/FAIL validation verdict and output; JSON,
YAML, and NDJSON return the structured result. Backed by
POST /api/v1/workspaces/{ws}/pipelines/{slug}/step_run.
crewship routine tag <slug>
Add or remove discovery tags on a routine definition — distinct from run tags. Definition tags power cross-crew discovery via routine list --tag <tag>. Pass --add (repeatable) and/or --remove; at least one is required.
Adds are sent as
PUT /api/v1/workspaces/{ws}/pipelines/{slug}/tags; a removal is DELETE /api/v1/workspaces/{ws}/pipelines/{slug}/tags/{tag}. When both flags are passed, the add runs first (Tagged … +[…]), then the removal (Untagged … -…).
crewship routine step-override
Override a single step’s prompt or model without bumping the routine version — the change applies on the next run and can be cleared to revert to the authored value. A parent command with three children.
step-override set <slug> <step_id>
Set a prompt and/or model override for one step. Pass at least one of --prompt / --model.
Backed by
PUT /api/v1/workspaces/{ws}/pipelines/{slug}/steps/{step_id}/override.
step-override clear <slug> <step_id>
Remove a step’s override, reverting it to the authored prompt/model.
DELETE /api/v1/workspaces/{ws}/pipelines/{slug}/steps/{step_id}/override.
step-override list <slug>
List the active step overrides for a routine.
GET /api/v1/workspaces/{ws}/pipelines/{slug}/overrides. Output columns: STEP, MODEL, PROMPT (truncated to 50 chars). No overrides prints No step overrides — routine runs as authored.
crewship routine watch <slug>
Stream live run + step events for a routine. Polls every 2s, dedupes events, prints in chronological order with ANSI colour by status.
Ctrl-C / SIGTERM exit cleanly.
crewship routine versions <slug>
* marker tags the current HEAD.
crewship routine versions show <slug> --version N
Show one immutable version’s metadata and full DSL definition.
Backed by
GET /api/v1/workspaces/{ws}/pipelines/{slug}/versions/{version}.
crewship routine diff <slug> --from N --to M
Unified diff between two versions’ definitions (#1422 item 5) — versions show <n> dumps one version at a time for external diffing; this is the
native in-product equivalent. The diff is computed over pretty-printed
JSON, so a single field change reads as a single-hunk diff instead of the
whole minified blob changing.
Identical versions (same
definition_hash) print a one-line confirmation
instead of an empty diff. --format json returns {slug, from_version, to_version, from_hash, to_hash, identical, unified_diff}.
crewship routine rollback <slug> --to N calls this same endpoint after a
successful rollback to print a “What changed” view against the
version it just rolled back from — best-effort, so a diff-fetch failure
never fails the rollback itself.
crewship routine iterate <slug>
Run a scored improvement loop over a saved routine: run → a grader
agent scores the output against your rubric ({"score": 0-100, "feedback": "..."}) → below target, an optimizer agent rewrites the
definition → local validation → you confirm → the definition saves as a
new immutable version whose change_summary records the round and
score (iterate round 2: score 74/100 — misses the TL;DR). The loop
stops at --target or after --rounds.
Guardrails, by construction:
- Every save goes through the normal gates — the server-side
test-run gate and governance maker-checker apply exactly as for a
human author. A definition that gains risky steps (
http,code, egress) lands asproposedand the loop stops with a message; the optimizer prompt also instructs against adding such steps. - Everything is versioned — each accepted round is one row in
routine versions;crewship routine rollback <slug> --to Nundoes any of it. - Human-in-the-loop by default — each save asks for confirmation;
--yesis for unattended runs and stays auditable via the version trail. - A run parked on an approval waitpoint aborts the loop (iterate does not drive approval flows); deferred/deduped runs abort too.
--format json emits the same machine-readable.
crewship routine rollback <slug>
Repoints the routine’s HEAD at the target version and makes its definition live — no new version row is created (versions are deduped by content hash). History is preserved, so you can re-roll forward by another rollback.
crewship routine export <slug>
Output:
crewship-pipeline-bundle/v1 JSON to stdout. By default any type: script files the routine references are inlined into the bundle so it is portable. Suitable for pipe to routine import or commit to git.
crewship routine import [bundle.json]
Pre-validates JSON locally before sending — typos at the keyboard get a local error rather than a 400 from the server.
crewship routine cancel <run_id>
cancelled and the pipeline.run.failed journal payload has status CANCELLED. Completed effects are not undone. Exceeding the run’s own deadline remains failed / FAILED in both records. Already-terminal runs return 409.
A run parked at a waitpoint (waiting) is cancelled in place: the row becomes cancelled and its pending approvals stop being actionable. If an approval, signal or event resumes the same run at that moment, exactly one side wins. Either the resume is refused and later resumes find the run cancelled, or the resumed run is cancelled before its next step and records cancelled itself. The response carries "parked": true when the cancel itself recorded the row; otherwise the run records cancelled when it stops.
crewship routine tree <run_id>
Show a run together with its child runs — those spawned via call_pipeline steps, deferred dispatch, or replay. Useful for tracing a fan-out or a parent→child chain end-to-end.
GET /api/v1/workspaces/{ws}/pipeline-runs/{run_id}/tree.
Output columns: RUN ID, PARENT ((root) for the top run), ROUTINE, STATUS, VIA (triggered_via), COST.
crewship routine changes <run_id>
Show the git diff a run’s crew container produced: resolves the run to its crew (invoking_crew_id, falling back to the routine’s author_crew_id), then reports that crew container’s uncommitted changes — a file list with add/delete counts, plus the unified diff. Powers the Activity dock’s Changes tab.
GET /api/v1/workspaces/{ws}/pipeline-runs/{run_id}/changes.
Output columns: STATUS, PATH, +, -, followed by the unified diff text.
A run with no resolvable crew, or whose crew container isn’t a live git repo, answers
is_repo:false rather than an error — the CLI prints “No git changes: …” and exits 0. That is the normal case for a workspace-level routine with no crew attached, not a failure. An unknown run_id is still a 404.crewship routine replay <run_id>
Re-run a prior run with its original captured inputs. The new run is stamped is_replay=true + replay_of=<run_id> (steps can short-circuit side effects via {{ env.is_replay }}) and inherits the source run’s tags so it groups with the original.
Backed by
POST /api/v1/workspaces/{ws}/pipelines/runs/{run_id}/replay (body: {"pinned_version": N} when --version is set). Output: Replayed <run_id> → new run <new_id>: <status> ($cost), plus the run output if non-empty.
crewship routine metadata <run_id>
Mutate a run’s metadata scratchpad. Later steps read the result as {{ run.metadata.x }}. Each flag takes a JSON object; pass at least one.
Backed by
PATCH /api/v1/workspaces/{ws}/pipeline-runs/{run_id}/metadata. Prints the updated metadata object.
crewship routine signal <run_id>
Deliver a payload to a running run parked on a wait:event step (input-stream injection). The payload becomes that wait step’s output.
Backed by
POST /api/v1/workspaces/{ws}/pipeline-runs/{run_id}/signal.
Needs a run id, so it only works when you already have one. To wake every
run parked on an event without knowing which they are, use
crewship signal send instead.
crewship routine executions <run_id>
Every step invocation a run made — retries, foreach items and nested
call_pipeline steps each get their own row — with status, agent, model and
the size of the output. Pages of 100; the command prints the next-page
command when more remain. Distinct from routine logs
(the journal trace) and routine tree (child runs,
not steps).
Output columns: EXECUTION_ID, PATH (
execution_path, e.g. /review/items/2/summarise), ATTEMPT, KIND, STATUS (running, completed, failed, skipped, waiting, cancelled; a running row of a finished run reads interrupted), AGENT, MODEL, STARTED, ENDED, OUTPUT (size), ERROR.
The list carries sizes, never transcripts — an execution’s output can be
megabytes, so it is a second call with --execution-id, which prints the
output verbatim (-f json gives { id, output }). -f json on the list is
the endpoint’s page: { rows[], next_cursor }. An unknown run or execution is
404; a malformed cursor is 400.
crewship routine artifacts <run_id>
The artifacts a run declared — an artifacts envelope in a step’s JSON
output, or /crew/shared paths in its handoff — promoted, immutable, and tied
to the step execution that produced them. Distinct from
routine result, which lists files that changed
on the crew during the run. Pages of 50.
--artifact-id and --download are mutually exclusive.
Output columns: ARTIFACT_ID, KIND, LABEL, STATE, MEDIA_TYPE, SIZE (content_bytes), PATH (the producing execution_path), SOURCE, ERROR.
-f json on the list is the endpoint’s page: { artifacts[], truncated, next_cursor }; with --artifact-id it is { id, content }. A download is raw
bytes with the artifact’s own content type, never JSON.
crewship routine calendar
The routines workspace’s Plan board as a list. One window, three kinds of
event: planned occurrences computed from enabled schedules, pending
one-time or deferred starts already parked (run --fire-at, --delay,
--debounce-key), and run rows that executed. Past entries are real runs —
never yesterday’s cron extrapolated backwards.
Output columns: AT, KIND, ROUTINE, RESULT (
status/outcome for run rows), VERSION (pinned recipe for pending rows and pinned plans), INPUTS (the read-only preset preview, — for runs), ID (schedule_id:instant for planned, pending_id, or run_id).
The table is sorted by time; -f json is the endpoint’s body { events[], truncated } in the server’s order (planned, pending, run). Planned and pending
events carry an inputs preview that keeps safe primitives and replaces
credential, file and secret-looking values with {type: "credential"|"file"|"redacted"}
— it cannot replay a run. A window holding more than the server returns
(1,000 events) prints a truncation warning: narrow the window rather than
paging. A window outside zero to 32 days, or a malformed instant, is 400.
crewship routine errors
List the workspace’s failed runs bucketed by a stable error fingerprint (failing step + normalized message) so like failures group together. Pick a fingerprint and replay the whole bucket with bulk-replay.
Backed by
GET /api/v1/workspaces/{ws}/pipelines/runs/errors. Output columns: FINGERPRINT, COUNT, ROUTINE, STEP, SAMPLE ERROR (truncated to 60 chars). No failures prints No failed runs. 🎉.
crewship routine bulk-replay
Replay every failed run under one fingerprint — the fix-and-retry companion to errors. Ship the fix, grab the fingerprint, then re-trigger the whole bucket.
Backed by
POST /api/v1/workspaces/{ws}/pipelines/runs/bulk_replay. Output: Bulk replay: <replayed>/<requested> runs re-triggered for fingerprint <fp>.
crewship routine pending
List or cancel deferred routine triggers — the parked dispatches created by run --delay, --ttl, --debounce-key, or --priority. The commands inspect waiting, dispatched and terminal receipts, or cancel a waiting start.
pending list
GET /api/v1/workspaces/{ws}/pipelines/pending (200 OK, member-scoped). It defaults to at most 100 not-yet-fired rows. Use --status pending|fired|failed|expired|cancelled|all to inspect other receipts; terminal/history lists return newest first.
The response retains id, pipeline_slug, optional debounce_key, priority, RFC3339Nano fire_at, pinned_version and the read-only inputs preview. It also includes status, run_id (empty until linked), dispatch_attempts, safe last_error, nullable expires_at and next_attempt_at, and can_cancel (the start is still pending and the caller has the update capability). The table adds STATUS, ATTEMPTS, RUN ID and REASON alongside the scheduled time and recipe version. Machine formats retain the full receipt. An empty default list prints No deferred triggers pending.
pending get <pending_id>
GET /api/v1/workspaces/{ws}/pipeline-pending/{pendingId}. Returns the same receipt shape for a stable accepted ID, including failed, expired, cancelled and fired starts after they leave the default pending list. An unknown or another workspace’s ID returns 404.
fired means the dispatcher claimed the start; it does not mean the routine completed. run_id may stay empty while the synchronous executor runs. Legacy fired rows without a run link are also returned unchanged; emptiness alone cannot establish whether work was lost. Once a link exists, use routine result to inspect the run’s own status.
Temporary routine concurrency rejection returns the start to pending with exponential backoff (2, 4, 8, 16, 32, then at most 60 seconds). Retry keeps the accepted ID, recipe pin, payload, priority and original fire_at; next_attempt_at controls eligibility. An explicit TTL remains the deadline and ends waiting as expired with a reason. Starts without TTL fail visibly after ten capacity attempts. The 5-second sweep and available worker slots also affect dispatch time; this is not a FIFO or ETA guarantee.
Other executor errors are not retried automatically: the receipt becomes failed with a safe public reason. After a debounce start has been attempted, newer triggers open a separate debounce window so they cannot rewrite its accepted retry payload. Canceling is still possible while a retry is pending. A claimed/running dispatch is canceled through its run once the run link is available.
pending cancel <pending_id>
update capability (MANAGER+).
Backed by POST /api/v1/workspaces/{ws}/pipelines/pending/{pending_id}/cancel,
which returns 200 OK with { "ok": true, "cancelled": "<pending_id>" }.
Missing, expired, already-fired, or already-cancelled items return 404;
missing database wiring returns 503.
crewship routine schedules
Manage cron-driven triggers. Scheduler runs in-process and ticks every 30s.
schedules list
slug@vN (e.g. daily-digest@v3). The --json output carries it as target_pipeline_version, plus last_status (COMPLETED | FAILED | SKIPPED | WAITING — WAITING means the run parked on an approval gate, which is healthy, not a failure).
schedules create
create is the only command that mints a schedule ID, and every other
schedule command takes that ID with no prefix-matching fallback — so to
script the family, read it back with a machine format rather than parsing
the human output:
schedules update <id>
Pass any subset of --cron / --timezone / --name / --enabled / --inputs / --catchup / --pin-version / --unpin / --max-failures / --wake-slug / --wake-inputs / --no-wake / --fail-closed to update. --max-failures is the positive consecutive-failure threshold at which the schedule auto-disables; omitting it keeps the existing threshold. An existing version pin survives updates that don’t mention it; --pin-version N re-pins, --unpin removes the pin so fires track head again. Likewise the wake gate is preserved unless touched: --wake-slug sets/replaces the probe, --wake-inputs replaces its inputs, and --no-wake removes the gate so the schedule fires on every tick again (--no-wake and --wake-slug/--wake-inputs/--fail-closed are mutually exclusive). The fail-closed policy is preserved unless mentioned: --fail-closed holds the run on a probe failure, --fail-closed=false restores fail-open. --catchup sets skip, once, or all; omitting it preserves the current policy.
schedules preview
Show the next fire times for a cron expression and timezone — stateless, so
it works for a schedule that has not been saved yet (the reliability
editor’s live preview and this command call the same server endpoint,
GET .../pipeline-schedules/preview).
The computation uses real IANA tzdata, so it reflects DST transitions
exactly: a spring-forward day with no valid local time for the cron’s
minute/hour is skipped, and a fall-back day’s ambiguous local time can
appear twice, one hour apart in UTC.
crewship routine schedules enable <schedule_id>
Enable a schedule. The next scheduler tick can fire it; the cron expression
and other schedule settings are unchanged. schedules disable <schedule_id>
is the mirror image: it stops future ticks without altering anything else.
Schedule <schedule-id> enabled. (or disabled.). With
--format json, the acknowledgement contains the schedule id and
enabled: true / false. Enabling re-validates the plan’s saved inputs
against the recipe it will run and answers 400 naming the input when they
no longer fit; disabling is never judged.
schedules now <id>
Force-fire ad-hoc (out-of-cycle invocation for testing). Honours the schedule’s version pin — it executes exactly what the next cron tick would, and answers 409 if the pinned version no longer exists.
schedules activate <id>
Turn on a draft trigger created by atomic routine authoring (routine save --cron ... --draft, or an agent’s save_routine call with "activation": "draft"). Enables the schedule, recomputes next_run_at from the current
time, and resolves the one approval item the draft raised as approved. This
is the one decision that item asks for.
schedules delete <id>
--yes skips the confirmation prompt.
Machine-readable output
Everyschedules subcommand honours the global -f/--format
(table|json|yaml|ndjson). list and create emit the full API row;
now, update, enable, disable, activate, and delete emit an
acknowledgement carrying the schedule id plus the verb that was applied:
crewship routine webhooks
Manage event-driven triggers. Each webhook is token-addressed (POST to /api/v1/webhooks/{token}), HMAC-signed for delivery integrity — every webhook has a signing secret, generated server-side when you do not supply one — and rate-limited per token.
A webhook’s ingress profile fixes how a delivery is signed and shaped, and is chosen once at create:
The profile cannot be changed by
update; create a new webhook to switch a sender’s protocol. See Webhooks → GitHub pull requests for the server contract.
webhooks list
crewship or github); machine output carries it as ingress_profile. The explicit --json flag is a deprecated alias for --format json.
A version-pinned webhook shows the pin in the ROUTINE column as slug@vN. LAST STATUS is COMPLETED | FAILED | DEDUPED | WAITING (WAITING = the dispatched run parked on an approval gate — healthy, resumes on approval).
webhooks receipts list
--webhook together with --source-id asks “did you receive this one”
and answers with zero or one row; --source-id on its own is refused because a
sender’s delivery id is only unique within a webhook.
Output columns: ID, WEBHOOK, SOURCE ID, RUN, RUN STATUS, RECEIVED, DEDUP UNTIL
RUN STATUS reads (run record not available) when there is no run record to
read: the run may not have started yet (the receipt is written before
execution starts), may have failed before a record was written, or its record
may have been removed by run retention. The absence does not say which, and
the CLI does not guess. A receipt deduplicates
redeliveries for 30 days from acceptance; a daily sweeper removes it after
that, and the same identifier then runs again.
webhooks receipts get <receipt-id>
crewship routine logs <run-id>.
webhooks create
webhooks update <id>
Edit a webhook in place (F21) — before this command existed, changing
anything meant delete + recreate, which mints a new token and therefore a
new public URL, breaking every sender already configured against the old
one. update never touches the token.
At least one flag is required.
--format json on an ordinary edit (no
--rotate-secret) never includes a signing_secret — only a rotating call
does, exactly once, in that response. There is no --ingress-profile here:
the server answers 400 to a profile change, so switching a sender between
the crewship and github formats means a new webhook.
crewship routine webhooks url <webhook_id>
Print the public URL for an existing webhook without revealing its signing
secret. The command lists webhooks in the active workspace and matches the
exact ID.
The default base URL comes from the configured server (or
CREWSHIP_SERVER); the printed value is <base-url>/api/v1/webhooks/<escaped-token>
for a crewship-profile webhook and
<base-url>/api/v1/webhooks/<escaped-token>/github-pull-request for a
github-profile one — the suffix selects GitHub’s signature scheme on the
server, so it is part of the URL you paste into GitHub. If the ID is not
found, the command exits with webhook <id> not found.
webhooks delete <id>
webhooks fire
create response, or the bare token
(with --base-url, or the configured server URL). The profile is inferred
from the /github-pull-request suffix and can be forced with --profile;
firing as github adds the suffix to a bare token. The token and the secret
are never printed back.
Output:
Accepted (202), Already received (202) or Ignored (200),
then the profile, the source id the receipt is filed under (github:<id> for
the GitHub profile), the receipt id, and the run id with the
crewship routine logs <run-id> line to follow it. Machine output is
{http_status, profile, source_delivery_id, status, run_id, delivery_id, duplicate, reason}.
Any non-2xx answer is the server’s error and a non-zero exit: 401 wrong
secret, 404 unknown or disabled token — or the wrong profile for the URL
(a crewship webhook fired under /github-pull-request, or the reverse),
409 the same delivery id with a different body (the original is kept),
413 body over the limit, 429/503 retry after Retry-After. The full
table is in Webhooks → Pipeline dispatch.
crewship routine waitpoints
Inspect + decide on pending HITL approval waitpoints.
waitpoints list
waitpoints show <token>
Full prompt + metadata for a single waitpoint.
waitpoints approve <token> / waitpoints reject <token>
waited:approval:approved; its
comment is stored with the decision and does not replace that output. An
approved typed wait returns serialized DecisionAnswer JSON with action_id,
data, and optional comment. Downstream steps can parse it from
{{ steps.<wait_step_id>.output }}. Rejection follows the wait step’s failure
behavior.
The decision is attributed to the person whose CLI token made it. An agent cannot decide a waitpoint — an attempt with an agent’s internal token is refused (401 before the route, 403 from the resolve door), the waitpoint stays pending, and the attempt is listed by crewship audit --action waitpoint.decision_refused.
Typed decisions and named actions
An approval wait can declaredecision_form. Its fields use the same typed input
contract as Run and Schedule. The schema is frozen when the waitpoint is created;
editing or publishing the recipe does not change an outstanding question.
routine waitpoints show <token> -f json, then
submit the chosen action and typed values:
--action must match a declared ID, and approve/reject must match that action’s
approved flag. Continuing actions require all required fields; rejection may
omit them. Provided values still undergo type and choice validation. Unknown
fields are rejected. Defaults preserve false and 0.
The successful wait step returns JSON containing action_id, data, and an
optional comment. Downstream transforms or conditions can route on these
values using the existing workflow engine. An action with approved: false
uses the existing denial/OnFail path; use approved: true for an alternate
continuing branch such as revision. Legacy waits keep waited:approval:approved.
The same form is available in the run detail and Inbox. A rich decision never
uses standing trust and cannot be completed by a legacy body-only approval.
The public token callback cannot answer it either: a waitpoint that carries a
decision_form refuses the callback with 403 (typed decisions require an
authenticated workspace member) and the attempt is recorded as
waitpoint.decision_refused; such rows list no callback_url. Legacy binary
approvals keep the callback. Expiry and the first
accepted verdict are enforced by the waitpoint store.
crewship routine trust
Manage standing approval grants — the answer to “I have approved this same gate twenty times”.
A grant is scoped to one gate of one routine and pinned to that routine’s current definition hash. Editing the routine moves the hash, so no grant matches and the gate asks again. A grant never becomes a silent path: when it fires it writes an ordinary approved waitpoint attributed to the operator who granted it.
MANAGER+ is required to grant or revoke. Listing is readable by anyone who can see the routine.
trust list <slug>
STATE is live (still firing), revoked (somebody withdrew it), or spent (expired or out of uses). A DEFINITION marked (stale) belongs to a routine body that has since changed — the grant is already inert.
Revoked grants stay in the list on purpose: the audit question is who trusted this gate and who took it back, which a live-only list cannot answer.
trust grant <slug> --step <step_id>
A second grant for the same gate and definition returns
409 rather than resetting the existing grant’s use counter.
When the governing crew is at
autonomy_level: strict, standing grants are ignored entirely — strict means every governable action reaches an operator, regardless of what has been granted. The governing crew is the routine’s author crew (a routine runs in its author’s context), or the invoking crew on a cross-crew invocation.trust revoke <slug> <grant-id>
The audit trail
Granting and revoking each write a journal entry, because disarming a human gate is the bluntest decision the approval system offers and it used to be the only one that left no trace:
Both are attributed to the deciding user (
actor_type: user). definition_hash is the load-bearing field — a grant only fires against that exact routine body, so an entry without it records that something was trusted but not what.
approval. namespace because they are the same control at two levels, so listing both families together answers “who let this through, by any route”:
crew_id, agent_id, mission_id or trace_id, so crewship journal --crew/--agent/--mission/--trace-id cannot narrow to them. --since and --actor-type user can. The refs (trust_grant_id and friends) are not searchable: -q searches summary and payload only, and refs are in neither — but the routine slug and step id are repeated into the summary, so -q <slug> does find a gate’s decisions. crewship journal get <entry-id> is what prints refs and payload; the list view shows neither.
Global flags
All routine subcommands accept these global flags:See also
- Routines guide — concepts, DSL spec, two-tier execution, troubleshooting
- CLI overview — global flags + auth
- Scheduling guide — recurring background work primitives
Answer a typed waitpoint
crewship routine waitpoints approve <token> --action <id> --input '{"count":0,"enabled":false}'
sends a named decision with typed fields through the authenticated workspace
endpoint. Use routine waitpoints reject <token> for an action whose approved value is false. The server
checks action consistency and the frozen form; malformed values fail without
resolving the waitpoint. The authenticated user’s identity is recorded separately
from the answer. Legacy approvals still work without --action or --input.