Workspace Manifests
A manifest is a single YAML file that describes a Crewship workspace as data — agents, skills, credentials, MCP servers, and sidecar containers (Redis, Postgres, etc.).crewship apply --file manifest.yaml converges the live workspace toward the file’s declared state through the same REST API the UI uses; nothing skips RBAC, nothing writes to the database directly.
This guide is the narrative tour. For per-field reference, see Configuration → Manifest Schema. For the apply / export CLI flags, see CLI → apply and CLI → export.
When to use it
One file = one workspace
Copy-paste a crew between teams, machines, or environments without re-clicking through the UI.
Git as source of truth
Commit your crews next to your code. Diff agent prompts. PR-review skill changes.
Reproducible setups
crewship apply is idempotent — re-running the same manifest produces the same workspace.CI-friendly
Run from a pipeline with
--yes --from-env to provision a fresh workspace per branch.Mental model
They are the workhorses, but not the whole vocabulary — the manifest system
accepts 20 kinds (
internal/manifest/schema.go), applied in dependency
order by the same planner: Crew, Agent, Integration, Workspace,
Project, Label, Milestone, WorkflowTemplate, TriageRule,
RecurringIssue, SavedView, Routine, FeatureFlag, InstanceSetting,
Recipe, CrewTemplate, Connector, Hook, Skill, Issue.
examples/manifests/full-complete.yaml exercises most of them in one file;
per-kind field reference lives in the
manifest schema.
Your first manifest
Save this ascode-review.crew.yaml:
--from-env reads ANTHROPIC_API_KEY and GH_TOKEN from the process environment. Skip it (or set --secrets-file env.list) and the credentials are created as PENDING slots that show up in the UI as “Needs value” with a CTA to fill them in.
Apply modes
Re-running apply is idempotent and convergent — the manifest is the source of truth, so resources missing from the live workspace get created, drifted ones updated, and resources that disappeared from the manifest get deleted.
What sync does delete when missing from the manifest:
- crews (per workspace bundle)
- agents within each declared crew
- agent skill bindings (
agent_skillsjoin) - agent credential bindings (
agent_credentialsjoin) - MCP servers on each declared crew
- skills at workspace scope (often shared across multiple crews)
- credentials themselves (their values may live elsewhere)
Credential safety
Values arrive at apply-time through one of three paths:status=PENDING. Agents that need a pending credential fail with credential not configured until the user fills it in; the PENDING sentinel is never injected into the agent’s environment, so the LLM can’t read or exfiltrate it.
After apply, the CLI prints the list of pending env vars so you know what’s still left to fill in:
Skills: three sources
Every skill entry picks one source:The validator rejects dangling references (agent points at a skill the manifest doesn’t declare) — no half-applied state.
Sidecar services (Redis, Postgres, etc.)
Declare sidecar containers that run alongside the agent on the crew bridge network. Agents reach them by service name —redis:6379, postgres:5432 — over a private network that’s never published to the host.
No
credentials: block, and no env_refs for the passwords: both images
are in the auto-managed catalog, so apply generates POSTGRES_PASSWORD and
REDIS_PASSWORD, boots each sidecar with its own secret and adds both to
every agent in the crew. Declaring one of those names in credentials: as
well is rejected at validate time — the manual row and the generated one
would race for the same workspace credential (#1712).- Pull the image (best-effort: tolerates registry outages when a local copy exists).
- Create per-crew named volumes (
crewship-svc-{crew-slug}-{crew-id}-vol-{name}). - Start the container with a DNS alias matching
name, on the crew bridge. - Wait for
healthcheck.testto report HEALTHY — capped at 60 s across all sidecars in the crew (internal/provider/docker/sidecar.go:EnsureCrewServices). - Only then start the agent runtime.
Services declared without a
healthcheck: block aren’t gated — Crewship considers them ready as soon as Docker reports the container running. Use a healthcheck whenever the agent’s first call depends on the service being initialized (Postgres after initdb, Redis after AOF replay, etc.).Volumes are always named volumes. The validator rejects bind-mount paths so manifests stay portable across machines.
Auto-managed service credentials
Rather than hand-declaring aGENERIC_SECRET credential and wiring
env_refs, a service asks Crewship to generate and manage the secret
itself — which is what the crew above relies on:
AUTO_MANAGED credential row at apply time (attributed
to the crew’s lead agent), is injected into the sidecar (as an env var by
default, or as a command arg via inject_as_command for images like Redis),
and is appended to every crew agent’s env_refs automatically so agents can
reach the service with the right value. Fields and defaults:
You rarely write this by hand: for well-known images (
postgres:*, mysql:*,
mariadb:*, mongo:*, redis:*, rabbitmq:*, …) the parser merges a sugar
default in automatically — see auto-managed credentials
for the full image catalog. Explicit entries win over sugar defaults with the
same name.
Datastores are always password-protected, Redis included. A stock
redis:* sidecar boots with --requirepass <generated> and hands agents
the secret as a REDIS_PASSWORD env credential — bridge isolation is never
the only gate. Supply your own command: on the service to opt out and
manage the password yourself.When sidecars start — and when they are removed
Sidecars come up with the crew’s container, whatever woke it: a chat message,crewship issue start, a scheduled agent, an inbound webhook, a
routine’s agent or script step, the web terminal, or the dashboard’s start
button. All of those go through the same crew-start contract
(internal/crewstart), so a crew that declares services: gets them on every
path.
Before v1.x this was true of the chat path only — a crew whose first
activity was
issue start, a schedule or a webhook came up with no sidecars at
all and no error anywhere, so redis:6379 simply did not resolve inside the
container (#1708). If you drove Crewship headlessly and concluded services:
did not work, this is why. Verify with crewship crew services <crew>: it
reads the live container runtime, not your manifest.env_refs resolve against the crew’s
credentials (the credential_crews link a workspace secret gets when it is
attached to a crew) rather than against whichever agent happened to trigger the
start: a per-agent value would give the same crew a different Postgres per
agent and restart it whenever the trigger changed.
On a container provider without sidecar support (Apple Containers today) the
crew still starts. The drop is logged, and chat shows “Sidecar services
declared but this container provider doesn’t support them yet” rather than
letting the crew believe it has a database.
A sidecar that fails to start (image pull, port clash, failing healthcheck)
fails the crew start everywhere except the web terminal, which opens the
shell anyway and says so — that is the surface you use to diagnose the failure,
and the crew’s own container is up by then.
The confirmation prompt says this too, and names the volumes, so it does not
depend on having read this page:
services: gets the plain one-line prompt it always had, and
--yes skips the confirmation as it always did — so a script that already
passes --yes deletes volumes without asking. Audit those before upgrading.
Two crews with the same slug are separate all the way down.
crews is
unique on (workspace, slug), not on slug alone, so two workspaces can both hold
a data-crew — and one crewshipd serves every workspace against one Docker
daemon. Sidecar containers and volumes therefore carry the globally-unique crew
id, the way the crew’s own container has since audit C1:Start, stop, list and delete all select on that label by exact equality — never
by a name prefix, because slugs may contain hyphens and one crew’s prefix can
prefix another’s names (
data vs data-vol-x). Deleting one workspace’s
data-crew leaves the other workspace’s data-crew Postgres running with its
data intact, and each crew’s own cleanup still happens (#1732; before it, the
delete refused outright whenever a slug was shared).Inventory: what’s actually running
The manifest above is what you declared. What’s actually running can drift from it — a sidecar you stopped by hand while debugging, or one Docker OOM-killed, still shows up as “configured” if you only look at the crew’s stored config.crewship crew services answers the live question
instead, reading straight from Docker rather than any stored snapshot:
- STATUS is the container’s current state (
running/stopped/creating/error) — the same vocabularycrewship crew container-statususes for the crew’s own runtime container. - TYPE is inferred from the image (
postgres/redis/mysql/mongodb/other), the same inference the capabilities endpoint uses for a crew’s datastore capabilities. - PORTS are the container-internal ports the sidecar exposes — never a host mapping, since sidecars are never published to the host (see above).
GET /api/v1/crews/{crewId}/services, which any agent can call directly for
the same live facts as JSON.
Delivering files into the crew: files:
A crew spec can carry local files (scripts, fixtures, configs) that apply
delivers into the crew’s shared volume — visible in-container under
/crew/shared. This is how a routine’s deterministic scripts travel with
the workspace manifest instead of a manual crew files save after every
rebuild:
destmust stay undershared/(/crew/shared/...spellings are normalized). The crew’s/output,/secrets, and agent homes are off-limits to declarative delivery on purpose; traversal is rejected.- Each file is capped at 1 MiB — the same budget as an inline code-step body. Bigger assets belong in object storage or the devcontainer image.
- On a running crew, an overwrite routes through the container
(
PUT /api/v1/crews/{crewId}/files/save) so in-container permissions stay correct. Re-delivering byte-identical content is a no-op and succeeds even on a stopped crew; delivering changed content to a stopped crew returns409— start the crew and re-apply.
Alternative: in-container databases via devcontainer features
If you prefer the database’s lifecycle to match the agent’s container (a smaller setup, but DB restarts whenever the agent restarts), you can use devcontainer features instead:localhost:5432 and localhost:6379. Works today with zero provisioner changes, but the DB lives inside the same container as the agent.
Workspace bundles: multi-crew setups
When you want to ship more than one crew in a single file, switch tokind: Workspace:
credentials: / skills: block.
Plan → confirm → apply
Apply is two-pass: it computes the full plan first, prints it, asks for confirmation on anything destructive, and only then mutates. The flow mirrorsterraform plan && terraform apply so you can always see what’s about to change.
--dry-run runs the plan phase only — perfect for code review or CI:
Exporting an existing workspace
crewship export is the round-trip partner of apply. It pulls the current workspace state and renders it as a manifest — useful for backing up, snapshotting, or migrating a workspace someone else built in the UI.
IDE autocomplete
The hosted JSON Schema endpoint (
https://schemas.crewship.ai/v1/manifest.json) is not live yet — it ships in a follow-up release. Until then, validation falls back to the YAML language server’s basic shape checks. The crewship apply --dry-run path is the authoritative validator in the meantime.CI patterns
--secrets-file:
Examples
The repo ships with these reference manifests underexamples/manifests/:
See also
- CLI → apply — every flag and exit code
- CLI → export — round-trip the other way
- Configuration → Manifest Schema — flat reference of every field
- Guides → Devcontainers — for in-container database workflows
- Guides → Credentials — credential lifecycle, status, rotation