04 · advanced

Missions, sprints, and crew dispatch

The advanced agentic surface. Use darkmux's crew abstraction to dispatch local-AI roles (coder, code-reviewer, scribe), track multi-step work as missions made of sprints, and run structured QA via sprint review. Optional: everything in earlier sections works without these concepts.

When you'd use this

If you're driving darkmux as a CLI for personal model loadout management, you don't need this. The mission/sprint/crew layer is for operators using darkmux as the local-AI tier of an agentic workflow, where a frontier orchestrator (Claude, GPT, etc.) is delegating routine implementation work to local models.

Specifically, this section is useful if you want:

Crew: roles + dispatch

A role is a named local-AI capability: a manifest under templates/builtin/roles/<id>.json (or ~/.darkmux/roles/<id>.json for operator overrides) paired with a system prompt at the same path with a .md extension. The manifest declares:

Inspect roles

darkmux role list                # every role in the index
darkmux role show coder          # full details for one role

Dispatch a role

darkmux crew dispatch coder --message "Add tests for the new helper in src/foo.rs"

What happens (default runtime, internal):

  1. darkmux loads the role manifest + the .md system prompt.
  2. Pre-flight: verifies Docker is reachable and the darkmux-runtime:latest image is present locally. Bails loud with build instructions if either is missing.
  3. Spins up a fresh darkmux-runtime container per dispatch with a mounted workspace tempdir: kernel-enforced isolation, no cross-task context leak by construction.
  4. Streams the model's reasoning and tool calls via the in-house Rust loop; emits dispatch.turn, dispatch.tool, and dispatch.reasoning flow records as they happen.
  5. Emits a final dispatch complete or dispatch error record; optionally snapshots --watch paths to verify the role's SIGNOFF claims.
Verification boundary.

The dispatch container is intentionally minimal: no project toolchains. The coder, code-reviewer, and test-designer roles are instructed (#398) not to install toolchains or run build/test/lint inside the container: their job ends at "the edit is in place + the tests are written," and they name the verification commands the frontier should run on the host afterward. So a role's report won't claim "tests passed" for commands it never ran. Verification is the frontier orchestrator's step, by design.

Opting into openclaw

If you already have openclaw installed and prefer its dispatch path:

darkmux crew dispatch coder --runtime openclaw --message "..."

The openclaw path uses darkmux/<role-id> agents in openclaw's config; run darkmux crew sync first if you've edited a role manifest since the last sync.

Sync the role registry (openclaw only)

Only needed if you use --runtime openclaw. The internal runtime reads role manifests directly each dispatch: no registry to reconcile.

darkmux crew sync                # reconcile openclaw agents.list[] with manifests
darkmux crew sync --dry-run      # preview without writing

Missions + sprints

A mission is a named objective: a JSON file under ~/.darkmux/missions/<id>.json with a description, a list of sprint_ids, and a status (active/closed/paused). A sprint is a time-boxed work unit inside a mission: its own JSON file under ~/.darkmux/sprints/<id>.json with status (planned/running/complete/abandoned), depends_on, and timestamps.

Note for operators with existing state: earlier versions of darkmux nested everything under ~/.darkmux/crew/ (roles/, missions/, sprints/). The loader transparently falls back to that layout, so existing state keeps working. Run darkmux doctor for a copy-pasteable mv script to flatten when you're ready.

Authoring a mission (recommended)

The preferred path: use darkmux mission propose, which takes unstructured input (pasted text, a Jira ticket via acli, a GitHub issue via gh) and dispatches the mission-compiler utility agent to emit a structured proposal you approve before any file lands.

Worked example: proposing a mission from stdin

# Pipe operator intent into the compiler
pbpaste | darkmux mission propose --from-stdin
# or use a file:  darkmux mission propose --from-file intent.txt

The compiler returns a proposal summary:

┌─ Proposal ──────────────────────────────
  Mission: draft-blog-post
  Description:
    Draft a blog post on local-AI bounded-task patterns.

  Sprints (2):
    1. draft-blog-post-s1-outline (depends_on: —)
       Write a one-page outline: thesis, three supporting sections.
    2. draft-blog-post-s2-draft (depends_on: draft-blog-post-s1-outline)
       Flesh out the outline into a full first draft.
└─────────────────────────────────────────

Approve [y] · Edit/regenerate with hint [e] · Reject [n]: y
mission propose: persisted 1 mission + 2 sprints
next: darkmux mission start draft-blog-post to begin (or pass --start next time)

The proposal is persisted to disk as JSON files under ~/.darkmux/missions/ and ~/.darkmux/sprints/. Pass --start to transition immediately:

darkmux mission propose --from-stdin --start
# ... proposal approved, files written, mission → Active

Authoring by hand (low-level alternative)

If you prefer to skip the propose step or are building automation, you can write the JSON files directly. This is the escape hatch: recommended for operators who want full control or are scripting workflows.

These are operator-edited JSON files. darkmux doesn't require a creation verb; you write the file directly, then use the CLI to transition status.

When to use missions vs. not

Mission/sprint is overhead. It pays back only when you'd benefit from durable state for the work. Three patterns to pick from:

PatternShapeWhen
Skip No mission. Just darkmux crew dispatch coder --message "..." ad-hoc. Single-shot work, no follow-up expected. The dispatch record itself is the only artifact you need.
Duration container One mission with sprint_ids: []. Start it; close it; that's it. You want start/close timestamps for an engagement-level chunk of work, but the work isn't decomposable into named sub-steps.
Decomposed plan Mission + N sprints. Pre-plan or grow via mission add-sprint. Multi-step work where the wall-clock arcs per sprint matter, dependencies between sprints matter, or you want the viewer's timeline as an explicit feedback surface.

The practical heuristic: if you can't articulate what the viewer would show you at the end of the mission, you probably don't need a mission. If the answer is "a 3-arc timeline with these durations and these dispatch records", missions earn their keep.

Mission JSON shape

{
  "id": "deploy-rewrite",
  "description": "Rewrite the deploy pipeline to use the new artifact store",
  "status": "active",
  "sprint_ids": ["deploy-s1-baseline", "deploy-s2-cutover"],
  "created_ts": 1778685866
}

Sprint JSON shape

{
  "id": "deploy-s1-baseline",
  "mission_id": "deploy-rewrite",
  "description": "Capture a baseline of current deploy timing + artifact sizes",
  "status": "planned",
  "depends_on": [],
  "created_ts": 1778685866
}

Worked example: a minimum-viable mission

The smallest useful shape: one mission, one sprint. Just enough to see the lifecycle move and the cyan records land in the viewer.

1. Make sure the directories exist

mkdir -p ~/.darkmux/missions ~/.darkmux/sprints

2. Pick a current Unix epoch timestamp

The created_ts field is just seconds since the Unix epoch. Get one:

date +%s
# 1778685866   (your value will differ)

3. Save the mission JSON

Save the following at ~/.darkmux/missions/draft-blog-post.json (replace the timestamp with what date +%s just gave you):

{
  "id": "draft-blog-post",
  "description": "Draft a blog post on local-AI bounded-task patterns.",
  "status": "active",
  "sprint_ids": ["draft-blog-post-s1-outline"],
  "created_ts": 1778685866
}

4. Save the sprint JSON

Save at ~/.darkmux/sprints/draft-blog-post-s1-outline.json:

{
  "id": "draft-blog-post-s1-outline",
  "mission_id": "draft-blog-post",
  "description": "Write a one-page outline: thesis, three supporting sections, closing reframe.",
  "status": "planned",
  "depends_on": [],
  "created_ts": 1778685866
}

5. Drive the lifecycle

darkmux mission start draft-blog-post
# mission `draft-blog-post` → Active

darkmux sprint start draft-blog-post-s1-outline
# sprint `draft-blog-post-s1-outline` → Running

# …do the work, by hand or via `darkmux crew dispatch scribe ...`…

darkmux sprint complete draft-blog-post-s1-outline
darkmux mission close draft-blog-post
# mission `draft-blog-post` → Closed   duration=<wall-clock seconds>

Every transition emits a flow record. Open darkmux.com/flow/, click Connect, and you'll see the cyan operator-tier records as the lifecycle moves.

Mission without sprints (duration container)

You can omit sprints entirely. A mission with sprint_ids: [] is valid: it's just a start/close timestamp container for an engagement-level chunk of work that isn't decomposable.

{
  "id": "japan-trip-2026-05",
  "description": "10-day Japan trip, May 20-30.",
  "status": "active",
  "sprint_ids": [],
  "created_ts": 1778685866
}

Then just darkmux mission start japan-trip-2026-05 when the trip begins and darkmux mission close japan-trip-2026-05 when it ends. The viewer will show the wall-clock duration.

You can also mission add-sprint later if structure emerges mid-trip. The empty-sprint mission grows into a decomposed one as needed.

Growing the plan: the multi-sprint shape

For mission-shaped work with clear decomposition — a campaign, a deploy, a multi-week research arc — the natural pattern is one mission JSON + one sprint JSON per planned step. Same shape as the minimum-viable example, just more sprint files.

For example, a marketing-campaign mission might have three sprints: positioning research, messaging draft, launch checklist. Each sprint JSON references the mission via mission_id, declares depends_on if it must follow another sprint, and the mission's sprint_ids array lists them all.

The depends_on edges encode "must complete first", not display order. Display order in the viewer follows the mission's sprint_ids array.

Mission/sprint is a generic plan abstraction.

The examples here aren't all software engineering: blog drafts, marketing campaigns, travel planning all fit. Anywhere you want explicit state for "this is what I'm working toward" and "this is the named piece I'm currently on," mission + sprint is the shape. The schema doesn't care what the work is.

Drive the lifecycle

# Mission state machine
darkmux mission start <id>        # → Active (stamps started_ts)
darkmux mission pause <id>        # Active → Paused
darkmux mission resume <id>       # Paused → Active (paused_ts preserved)
darkmux mission close <id>        # → Closed (terminal)

# Sprint state machine
darkmux sprint start <id>         # Planned/Abandoned → Running
darkmux sprint complete <id>      # Running → Complete (terminal)
darkmux sprint abandon <id>       # Planned/Running → Abandoned

Every transition emits a flow record. Open the viewer in a new tab as you run these. The sprint-progress widget in the header shows N/M done across active missions, and the record stream below shows the cyan operator-tier transitions in order.

Adding a sprint mid-flight

If you discover scope mid-mission that belongs in the current plan rather than a new mission, use darkmux mission add-sprint:

# Append a new sprint to the end of the mission's plan
darkmux mission add-sprint <mission-id> \
  --sprint-id <new-id> \
  --description "..." \
  [--depends-on <other-sprint-id>]

# Insert in the middle — immediately after an existing sprint
darkmux mission add-sprint <mission-id> \
  --sprint-id <new-id> \
  --description "..." \
  --after <existing-sprint-id>

The verb writes the new Sprint JSON, updates the Mission's sprint_ids array (at the right position), and emits a sprint added flow record. Idempotent on exact-match (same id + mission + description); errors on collision or dangling --after.

Sprint review: QA-by-default

darkmux sprint review runs a code review against the current branch's diff vs. base. It dispatches the code-reviewer role, parses the structured QA-REVIEW-SIGNOFF block from the response, and emits a verdict.

darkmux sprint review                          # review current branch vs auto-detected base
darkmux sprint review --base main              # explicit base
darkmux sprint review --sprint-id deploy-s1    # tag the review with a sprint id
darkmux sprint review --require-clean          # exit 1 if any blockers

Verdict bucket reflects the worst severity found:

Output is structured JSON with the full findings list. Use --require-clean in CI or pre-commit scripts that need to gate on clean reviews.

The review emits its own flow records (Review category, frontier-tier for the orchestrator narrative, local-tier for the actual reviewer dispatch). Watch the viewer during a sprint review to see the full cascade.

Putting it together: a typical agentic workflow

  1. Write a mission JSON describing what you're trying to do, with sprint ids planned.
  2. Write the first sprint JSON.
  3. darkmux mission start <m> + darkmux sprint start <s1>: operator/cyan records land in the viewer.
  4. Dispatch the work: darkmux crew dispatch coder --message "<sprint task>": local/green records (start + complete) with the model field stamped.
  5. darkmux sprint complete <s1>: duration arc visible in the wall-clock graphic.
  6. darkmux sprint review --sprint-id <s1>: QA pass; verdict emits to the viewer. Address findings.
  7. Open a PR. If the verdict was clean (or flags-only with nothing material), merge.
  8. Repeat for subsequent sprints. Discover scope mid-flight? darkmux mission add-sprint rather than filing a separate mission.
  9. darkmux mission close <m> when the plan is done.

Every step writes a flow record. The viewer renders them as an annotated timeline. Wall-clock arcs on the dispatch + sprint records show duration; the sprint-progress widget shows N/M done.

Operator sovereignty (a note)

darkmux's mission/sprint/crew model is intentionally operator-owned: missions and sprints are JSON files you write; the CLI manages state transitions but never auto-creates or auto-abandons. The orchestrator is expected to propose structure — "this looks like 3 sprints; want me to scaffold them?" — but you accept or modify before files land.

If a sprint sits in Running status for hours, that's not a bug in darkmux; it's the operator deciding to leave it open. The viewer's wall-clock graphic shows an unbounded sweep on stale running sprints: clearly-wrong-looking signal that the operator forgot to close one. By design.