Baton
The sole ticket / DAG / session-subagent control plane over OpenSpec changes. A local MCP server exposes deterministic session actions; the main session delegates each assignment with the DSH
subagenttool, binds the child id, and reconciles the completion notice against a sealed receipt.
The problem it solves
OpenSpec is strong on spec-driven decomposition (proposal -> tasks -> archive)
but does not execute. Baton turns an approved change into finished work,
deterministically: the main session never decides ticket state itself. It
forwards the deterministic actions returned by the Baton MCP server to DSH
subagents verbatim, then reconciles each completion notice against a sealed
receipt (the assignment's verification commands run green in an isolated
worktree). Free-text subagent output is ignored.
A sealed receipt is hash-bound evidence produced locally by receipt.py
(task-packet hash + clean, advanced HEAD + exit-0 verification results). It is
integrity evidence, not a cryptographic proof against a hostile subagent.
The change's tasks and the execution share the same granularity (one assignment ~ one task), and three seams are bridged deterministically:
| Seam | Baton's answer |
|---|---|
| Dependency (section-order DAG -> what to run next) | SessionController.actions() computes the frontier fresh every call and atomically claims distinct tasks |
| Concurrency (<=5 parallel tasks) | one isolated git worktree per assignment; integration serialized by a repository-scoped lock |
| State (subagent done -> task resolved) | a sealed receipt is the only success evidence; the controller integrates the branch, finalizes the attempt, and flips the task's [x] in tasks.md |
Baton is the conductor. SessionController is the sole deterministic
scheduler; the main session is a mechanical forwarder; OpenSpec is the plan
face.
Install (DSH)
Baton is a DSH bundle: one package mounts the baton skill and starts the local
MCP server. It requires DSH 0.2.1-rc.2 or newer, a profile that provides the
subagent tool (the shipped dsh-base does), and Python 3.10+ on PATH (or
BATON_PYTHON pointing at it).
# From a checkout
dsh plugin add /path/to/baton
# Once published
dsh plugin add dsh-openspec-baton
The package name is dsh-openspec-baton to avoid colliding with the unrelated
dsh-baton plugin. The repository and brand remain baton, and the MCP server
keeps the baton namespace (mcp__baton__*).
Quick start
# In your business project, write an OpenSpec change (the project's normal
# openspec/AGENTS.md workflow) and get it approved:
# openspec/changes/<change-id>/proposal.md + tasks.md (unchecked N.M tasks)
Then ask the session:
run baton on <project_path> <change-id>
The skill drives actions -> subagent -> bind_agent -> completion notice
-> reconcile until the batch returns complete.
Architecture
main session (mechanical forwarder - no model-driven state decisions)
|- mcp__baton__actions(project_path, feature, session_id, capacity<=5)
| -> SessionController.actions(): recover/reconcile sealed receipts,
| compute frontier, atomically claim, prepare isolated worktrees +
| immutable Task Packets, return dispatch/recover/resolve_merge/
| wait/blocked/complete actions
|- subagent(dispatch.prompt, cwd=dispatch.cwd) -> child id
|- mcp__baton__bind_agent(assignment_id, claim_token, agent_id)
|- runtime sends the subagent completion notice
|- mcp__baton__reconcile(assignment_id, claim_token, agent_terminal, ...)
-> sealed receipt -> succeeded + integrated; otherwise unknown + recover
SessionController (the sole deterministic scheduler)
|- durable plan store (.dsh/baton/queue.db): revisions, attempts, leases,
| sealed receipts, completion facts
|- isolated assignment worktrees + a shared integration worktree
|- serial git integration; merge conflicts preserved (never auto-aborted)
`- latest-plan completion (all required tickets verified + integrated)
Capabilities
Session-subagent execution (sole path):
- Deterministic scheduling - claim, lease, worktree, receipt verification, integration, completion are all scripted; the model never decides state.
- Isolated worktrees - one branch per assignment; capacity <= 5 sliding window; integration serialized by a repository-scoped lock.
- Sealed receipts -
scripts/receipt.py verify/sealis the only accepted success evidence; a completion notice without a receipt flips the attempt tounknown.
Control plane:
- OpenSpec-native cognitive phase - "use baton to finish X" means the
session runs the project's OpenSpec workflow (align -> write the change ->
user approves), then drives execution to
complete. No self-built Grill/Brief/approval state machine. - Live plan = edit tasks.md - add tasks or sections at runtime; the next
actions()call records a new plan revision and the frontier is recomputed. In-flight packets stay immutable.
Observability: scripts/ticket_board.py produces a pure static HTML
Kanban board over a project's OpenSpec changes (zero process, zero port,
colored feature tags, open via file://).
Bundle contents
| Path | Purpose |
|---|---|
index.js |
mounts the bundled skills/ provider and the stdio MCP server |
cordis.patch.yml |
DSH bundle insert row (dsh-openspec-baton) |
skills/baton/SKILL.md |
the baton skill (actions -> subagent -> reconcile) |
mcp-servers/baton-server.py |
local stdio MCP server (mcp__baton__*) |
scripts/ |
deterministic controller, plan store, receipts, queue CLI |
History
Baton v1 drove an external ralph.sh loop. v2 brought the loop into Python.
v3 replaced the subprocess model with subagents but still used a shared PRD +
passes flags. v4 retired the PRD/passes protocol and made
SessionController the sole deterministic scheduler. v5 (current) adopts
OpenSpec as the single plan face - the change's tasks.md is the ticket source
(section-order DAG, [x] write-back), and the self-built cognitive/amendment
engines are retired.
Requirements
- DSH
0.2.1-rc.2or newer, withsubagentavailable (dsh-base) - Python 3.10+ (
python3on macOS/Linux,pythonon Windows, orBATON_PYTHON) + pytest - A git target project (the controller prepares isolated worktrees)
- The target project uses OpenSpec (
openspec/changes/<id>/tasks.md)
Regression
The standard regression entry - full pytest suite (incl. temp-git-fixture e2e), a live MCP stdio smoke test of the server, and a docs/file consistency check:
./tests/regression.sh
Documentation
- docs/architecture.md - why the deterministic control plane is the core IP
- docs/ticket-format.md - the OpenSpec change -> ticket mapping spec
License
MIT
No comments yet. Be the first to write one.