kind: Crew
What it is
kind: Crew is the full-CRUD authoring surface for one crew: Create on
first apply, Update on drift, Unchanged when the declared spec already
matches the server. Where CrewTemplate is a
one-shot deploy of a server-side blueprint (no in-place updates),
kind: Crew owns every field of the crew row directly — runtime image,
devcontainer overlay, mise toolchain, and sidecar services.
Both kinds write to the same crews table; the difference is
provenance and lifecycle. An exported workspace round-trips through
kind: Crew for crews whose slug doesn’t match a known template (the
common case, since operators usually rename on deploy).
The kind is implemented in internal/manifest/kinds/crew.go.
Agents are separate documents. A standalonekind: Crewdocument defines only the crew row (and its sidecars / container config). Its agents are authored either as standalonekind: Agentdocuments that reference this crew bycrew_slug, or inline under akind: Workspacebundle’s nested crew shape. The standalone Crew document does not carry anagents:list.
YAML schema
Top-level spec fields
spec.devcontainer
Models the subset of devcontainer.json operators commonly tweak;
unmodeled keys pass through via raw: (typed fields win on collision).
Pinning features
A ref may name a tag or a digest, and the difference decides whether your crew is reproducible.:2 starts pointing at a newer 2.x the key is unchanged:
the crew keeps the image it already has, and nothing announces that a newer one
exists. That is the right default for stability — a crew does not silently
change under you — but it means the tag alone does not tell you what you are
running.
Every build therefore records what each ref resolved to. Read it back with:
= is pinned, ~ is floating. The same information appears on the feature
chips in the Builder popover, where a floating feature is tinted and its tooltip
carries the resolved digest.
Two caveats worth knowing:
- A feature’s own options can float independently.
VERSION: latestinstalls the newest tool on every rebuild even when the feature ref is pinned to a digest — pin the option too if you need the tool version fixed. - Crews provisioned before version tracking existed report
(not recorded — provisioned before version tracking). Rebuild to record it.
spec.runtime_imageis the canonical image. The legacyspec.devcontainer.imageis the same thing — set one only; a divergent pair is rejected as a paste mistake.
spec.mise
spec.services[]
Each entry is one sidecar on the crew’s private bridge network. The
wire shape mirrors the server’s serviceWire.
spec.files[]
Local files (scripts, fixtures, configs) delivered into the crew’s shared
volume at crewship apply time — visible in-container under /crew/shared.
This is how a routine’s deterministic scripts travel with the manifest
instead of a manual crewship crew files save after every rebuild. Re-applied
on every apply (idempotent PUT through the same /files/save endpoint).
type: script routine step (see docs/manifest/routine.md “Script steps”)
can then run them token-zero: path: scripts/parse_vypis.py. A missing or
oversized local file fails the plan (and --dry-run), not mid-apply.
Bundled files are written to the crew’s /crew/shared bind source on the
host, so they are present the moment the crew container comes up — including
crews that have no agents and whose container is provisioned lazily on
the first routine run. Inspect them with crewship crew files list <crew> --path shared.
Examples
Minimal
With devcontainer sizing + mise toolchain
With a Postgres sidecar
CLI reference
REST endpoint mapping
How each manifest field maps onto the create/update request body:
Endpoints used:
Validation rules
CrewDocument.Validate (offline) enforces:
-
apiVersion/kind, when set, equalcrewship/v1/Crew. -
metadata.nameandmetadata.slugare non-empty; slug is kebab-case. -
spec.color, when it starts with#, is a valid 6-digit hex code. -
spec.runtime_imageis required (no sane default; the server tolerates NULL but falls back to a wrong-for-you built-in image). -
spec.devcontainer.image, when set, must equalspec.runtime_image. -
devcontainer.memory_mbis 6–262144 andcpusis 0.01–512 (0 or an omitted key means “use the server default” — 4096 MB and 2.0 CPUs). These lower bounds are Docker’s own: a crew configured below them is rejected here, at apply time, rather than passing every layer and then failing inside the daemon on every wake — which wedges every run of that crew. The upper bound oncpuscannot be fully checked server-side, because the daemon’s real limit is the host’s core count. There is a second, higher floor — the memory one agent actually needs, 2048 MB by default. A crew between the two applies successfully and the server returns a warning: the container is created and the agent CLI is then OOM-killed (exit 137) on start.Validatedoes not enforce it, deliberately. That floor is theruntime.agent_min_memory_mb/runtime.agent_min_cpusinstance settings, which an operator can move, and offline validation (apply --dry-runwith no server) cannot read them — a compiled-in copy would reject manifests the server would accept. Seecrewship crewfor the settings and their defaults. - Each service: DNS-label name, unique within the crew, image present, numeric ports only, parseable healthcheck durations, named (not bind) volumes with unique mounts.
Apply behavior
ApplyUpsert (default)
- Remote missing →
ActionCreate: POST the full body. - Remote present, fields drift →
ActionUpdate: a sparse PATCH of only the drifted fields. Empty declared scalars and nil devcontainer/mise/services are skipped — so the manifest never overwrites a value the operator set via the UI. - Remote present, no drift →
ActionUnchanged.
devcontainer_config / mise_config / services_json are compared
after JSON normalisation, so server-side key reordering doesn’t trigger
phantom drift. services: [] (empty array) clears all sidecars; an
absent services: key leaves them alone.
Round-trip via export
crewship export workspace calls ExportCrews, which renders one
kind: Crew per crew (sorted by slug). The devcontainer / mise JSON is
decoded back into the typed sub-fields where possible, with anything
unmodeled stashed under raw: so the round-trip stays byte-stable.
Columns the manifest doesn’t model (cached_image, config_hash,
container_ttl_hours, network_mode) are dropped.
See also
- Agent — references this crew via
spec.crew_slug. - CrewTemplate — one-shot deploy of a server blueprint.
- Integration — crew-scoped MCP servers.
- Workspace — the top-level bundle that nests crews + agents.
- Backend:
internal/api/crews_create.go,internal/api/crews_update.go,internal/api/crew_services.go. - This kind’s Go implementation:
internal/manifest/kinds/crew.go.