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.
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:
sprint review, which dispatches a code-reviewer crew against the diff./flow.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:
id, description, skills: the skills list names work-shape descriptors the role is good at (renamed from capabilities in #448; existing manifests must use the new key). Each skill in turn declares an intrinsic capability profile (code, reasoning, instruction_following, agentic_tool_use) that role-to-model selection will score against.tool_palette: allow and deny lists of tools (read, exec, edit, etc.)escalation_contract: what the role does when it can't solve the task (bail, retry-with-hint, hand-off-to)role_family (optional): specialist (default) or utility; specialist roles get the autonomous-dispatch preamble injected ahead of their prompt (they can't ask questions mid-dispatch, so they escalate with a BLOCKED: line instead).feedback_templates (optional): per-signal overrides for the runtime's model-facing nudges (cycle/loop/cascade/etc.); falls back to the built-in wording when unset.darkmux role list # every role in the index
darkmux role show coder # full details for one role
darkmux crew dispatch coder --message "Add tests for the new helper in src/foo.rs"
What happens (default runtime, internal):
.md system prompt.darkmux-runtime:latest image is present locally. Bails loud with build instructions if either is missing.darkmux-runtime container per dispatch with a mounted workspace tempdir: kernel-enforced isolation, no cross-task context leak by construction.dispatch.turn, dispatch.tool, and dispatch.reasoning flow records as they happen.dispatch complete or dispatch error record; optionally snapshots --watch paths to verify the role's SIGNOFF claims.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.
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.
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
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.
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.
# 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
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.
Mission/sprint is overhead. It pays back only when you'd benefit from durable state for the work. Three patterns to pick from:
| Pattern | Shape | When |
|---|---|---|
| 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.
{
"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
}
{
"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
}
The smallest useful shape: one mission, one sprint. Just enough to see the lifecycle move and the cyan records land in the viewer.
mkdir -p ~/.darkmux/missions ~/.darkmux/sprints
The created_ts field is just seconds since the Unix epoch. Get one:
date +%s
# 1778685866 (your value will differ)
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
}
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
}
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.
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.
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.
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.
# 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.
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.
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.
darkmux mission start <m> + darkmux sprint start <s1>: operator/cyan records land in the viewer.darkmux crew dispatch coder --message "<sprint task>": local/green records (start + complete) with the model field stamped.darkmux sprint complete <s1>: duration arc visible in the wall-clock graphic.darkmux sprint review --sprint-id <s1>: QA pass; verdict emits to the viewer. Address findings.darkmux mission add-sprint rather than filing a separate mission.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.
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.