kind: Routine
What it is
kind: Routine declares a workspace-scoped declarative AI workflow
— what Crewship has historically called a “pipeline” in the database
and a “routine” in the UI. A routine is a versioned, schedulable,
webhook-dispatchable DAG of steps (agent runs, HTTP calls, code
blocks, transforms, waits) that the workspace’s agents can invoke and
that humans can trigger directly via cron or webhook.
This kind subsumes the legacy crewship routine save -f X.json
flow while remaining fully backward-compatible: the JSON body that
the old CLI sent under --file is exactly the shape that lives under
spec: (modulo the manifest-only schedules + webhook fields
described below). An operator migrating from JSON to YAML can copy
their existing routine.json body verbatim into spec:, add the
manifest envelope (apiVersion, kind, metadata), and have it
apply identically. No field renames, no semantic drift — the
internal/pipeline.Parse server-side validator is the same code path
in both paths.
Routine is the biggest kind in the manifest system because one
document atomically deploys three sibling rows:
- one
pipelinesrow (the routine definition itself) - zero or more
pipeline_schedulesrows (cron triggers) - zero or one
pipeline_webhooksrow (public dispatch token)
crewship apply -f routine.yaml creates / updates / prunes
all three in one transaction-like sequence — what used to take three
CLI calls (routine save + routine schedule create + routine webhook create) is now one declarative file.
YAML schema
Examples
Minimal — single-step routine, no triggers
Realistic — Discord hourly sync with cron + webhook
CLI reference
| Command | Purpose |
|---|---|
crewship apply -f routine.yaml | Create / update the routine + its schedules + its webhook. |
crewship apply --dir ./manifests/ | Apply every routine (and every other kind) in a directory tree. |
crewship apply --dry-run -f routine.yaml | Show the plan (per-row create/update/delete) without mutating. |
crewship export crew uo-outlands | Round-trip: dump every routine labeled crew: uo-outlands back to YAML. |
crewship export workspace | Dump every routine in the workspace (no crew filter). |
crewship routine list | Pre-manifest CLI: still works, shows existing routines (read-only here). |
crewship routine save -f routine.json | Legacy — equivalent to applying a Routine document with no schedules/webhook. Kept for back-compat. |
REST endpoint mapping
How each manifest field lands on a REST call and ultimately a DB column:| Manifest field | HTTP verb | Path | DB column / table |
|---|---|---|---|
metadata.slug + spec.* (routine DSL) | POST | /api/v1/workspaces/{ws}/pipelines/save | pipelines.slug + pipelines.definition_json |
metadata.name | (same as above) | (same as above) | pipelines.name |
metadata.labels.crew | (resolved client-side to crew id) | (same as above; server validates) | pipelines.author_crew_id |
spec.schedules[] | POST | /api/v1/workspaces/{ws}/pipeline-schedules | pipeline_schedules.* |
spec.schedules[].name | (same as above) | (same as above) | pipeline_schedules.name |
spec.schedules[].cron | (same as above) | (same as above) | pipeline_schedules.cron_expr |
spec.schedules[].timezone | (same as above) | (same as above) | pipeline_schedules.timezone |
spec.schedules[].inputs | (same as above; JSON-encoded) | (same as above) | pipeline_schedules.inputs_json |
spec.schedules[].enabled | (same as above) | (same as above) | pipeline_schedules.enabled |
spec.webhook | POST | /api/v1/workspaces/{ws}/pipeline-webhooks | pipeline_webhooks.* |
spec.webhook.enabled | (same as above) | (same as above) | pipeline_webhooks.enabled |
spec.webhook.token_env_ref | (resolved at Plan time; not persisted) | (n/a — the server mints its own token) | — |
Validation rules
Validate (client-side, before any REST call fires):metadata.nameandmetadata.slugrequired.metadata.labels.crewrequired; must appear in the workspace’s declared crews OR remote crews.spec.dsl_versionrequired.spec.stepsmust contain at least one step.- Every
step.agent_slug(ontype: agent_runsteps) must appear in the workspace’s declared agents OR remote agents. We DO NOT validate the agent-membership-in-parent-crew constraint here; the server enforces that at apply time. - Every schedule must have a non-empty
name; names unique within the document. - Every schedule’s
cronmust parse withgithub.com/robfig/cron/v3’s standard parser (5 fields + descriptor support). - Every schedule’s
timezonemust parse viatime.LoadLocation. - Webhook
token_env_refis NOT validated here — workspace creds aren’t inWorkspaceContexttoday. A Plan-time advisory line on the report flags missing resolution; Validate stays purely structural so Export → re-apply round-trips cleanly.
internal/pipeline.Parse and
internal/pipeline.Validate. We deliberately don’t re-implement that
logic here — duplicating it would just create skew opportunities.
Apply behavior
ApplyUpsert (default)
-
Routine row. Look up
/pipelines/{slug}:- missing →
Action=Create, POST/pipelines/save - drifted (name, description, or canonical definition JSON
differs) →
Action=Update, POST/pipelines/saveagain (the save endpoint is idempotent on slug; it bumps the version) - identical →
Action=Unchanged
- missing →
-
Schedules. List
/pipeline-schedulesfiltered by slug, match-by-name against the declared schedules. For each:- declared, not on remote →
Action=Create, POST/pipeline-schedules - declared and drifted (cron, timezone, enabled, or inputs differ)
→
Action=Update, PATCH/pipeline-schedules/{id} - declared and identical →
Action=Unchanged - on remote but no longer declared →
Action=Delete, DELETE/pipeline-schedules/{id}(the manifest is the source of truth)
- declared, not on remote →
-
Webhook. GET the routine’s webhook (if any):
- declared
enabled: true, no remote →Action=Create, POST/pipeline-webhooks - declared, remote drifted →
Action=Update, which is delete-then-recreate (no PATCH endpoint exists) - declared and identical →
Action=Unchanged webhook:omitted (orenabled: false) but remote exists →Action=Delete
- declared
ApplyStrict
Refuses to update or delete; if any routine in the manifest already
exists on the server, apply stops with an error. Use in CI when “this
manifest must create fresh resources” is the requirement.
ApplyReplace
Destructive recreate: emits Action=Delete for every existing
routine + schedule + webhook that matches a manifest slug, then
creates everything fresh. The webhook token + signing secret change
on replace (server-minted on each create) — operators must
re-distribute the new public URL after a replace.
Round-trip via export
crewship export crew <crew-slug> emits one Routine document per
routine where pipelines.author_crew_id resolves to <crew-slug>.
Each document includes the full DSL under spec: plus every nested
schedule and the (optional) webhook block.
What round-trips losslessly:
metadata.name,metadata.slug,metadata.labels.crew- The entire routine DSL (
spec.dsl_version,spec.description,spec.inputs,spec.steps,spec.credentials_required,spec.estimated_cost_usd,spec.estimated_duration_seconds,spec.max_cost_usd,spec.egress_targets) spec.schedules[](every field — name, cron, timezone, enabled, inputs)spec.webhook.enabledonly
spec.webhook.require_token— the server storessigning_secret_setinstead; export emitsrequire_token: trueimplicitly via the default.spec.webhook.token_env_ref— purely a Plan-time hint to the CLI about which env var holds the public token; not persisted.
token_env_ref
manually before re-applying, otherwise the Plan layer will print a
warning that the webhook’s public URL won’t be discoverable from the
env.
Code-step limitation
type: code is part of the DSL surface and the validator accepts it,
but the production CodeRunner is not yet wired
(internal/pipeline/runner_code.go). A routine that declares a code
step will save successfully and the schedule will fire on cron,
but the step itself fails at runtime with:
crewship apply surfaces this at plan time as a yellow warning so
you see the gap before the cron fires the first time:
Conversion recipe
Replace the code step with anagent_run against an agent whose
tool_profile: FULL (or any profile that includes shell). The agent
runs the same command from inside its container, which is already
wired end-to-end.
Before:
See also
- Your First Crew — the parent concept; routines reference a crew via
metadata.labels.crew.step.agent_slugresolves against agents-in-crew. - Connector —
credentials_requiredtypes are resolved against workspace credentials, which may come from installed connectors. - Hook —
pre_run/post_runhooks fire around every routine invocation. schemas/routine.v1.json— JSON Schema for the DSL portion ofspec:(everything exceptschedules+webhook). Use with VSCode / JetBrains autocomplete: add"$schema": "./schemas/routine.v1.json"to a standaloneroutine.jsonfile.