Skip to main content
Glama
dotfrankruan

Workforce Orchestrator MCP

by dotfrankruan
README.md
# Workforce Orchestrator MCP

A universal execution workforce for Codex and other MCP clients.

The orchestrator turns a planner (Codex, Claude, or any MCP-capable client) into
a supervisor over **pluggable worker backends**: task DAGs, workflows, budgets,
roles, presets, acceptance tracking, metrics, durable process supervision, git
isolation, and an artifact cache.

**The planner thinks. Workers execute. The orchestrator schedules. The
supervisor owns state and Git. Humans retain final control.**

```
                         Planner (Codex)
                              │
                              ▼
                    Workforce Orchestrator
                              │
                        Backend Router
                              │
        ┌──────────────┬──────┴───────┬──────────────┐
        ▼              ▼              ▼              ▼
   DeepSeek        OpenCode      OpenAI API     Anthropic API
   Harness         CLI           (direct)       (direct)
        │              │              │              │
        └──────────────┴──────────────┴──────────────┘
                              │
                     isolated workspaces
                     artifacts · metrics · git
```

## Backends

| Backend id | Runtime | Session continuation | Durable reattach | Credentials |
|---|---|---|---|---|
| `dsh` | DeepSeek Harness CLI (headless profile) | yes | yes | owned by dsh (`~/.dsh/.env`) |
| `opencode` | OpenCode CLI (`opencode run --format json`) | yes | yes | owned by OpenCode (`opencode auth`) |
| `openai` | Direct OpenAI Chat Completions or Responses API | yes | no (in-process) | `OPENAI_API_KEY` / config |
| `anthropic` | Direct Anthropic Messages API | yes | no (in-process) | `ANTHROPIC_API_KEY` / config |
| `fake` | Local test backend, no API calls | yes | no | none |

The registry is deterministic and never silently falls back. Selection order:

1. explicit `backend` on the task/workflow task
2. `routing.roles.<role>` from `~/.workforce/config.json`
3. `routing.default`
4. `default_backend`
5. `WORKFORCE_DEFAULT_BACKEND`
6. `WORKFORCE_BACKEND` (unless `auto`)
7. first locally available backend in order `dsh → opencode → openai → anthropic`
8. `dsh`

`auto` is the default, so a fresh install uses whichever runtime is actually
available on the machine. An unavailable explicitly-selected backend is a clear
error — nothing is auto-installed or silently substituted.

## What's new in v5.1

- **DSH model selection.** The DeepSeek Harness backend can now run a chosen
  model — per task (`spawn_task({ model: "k3-256k" })`), per config
  (`backends.dsh.model`), or per environment (`WORKFORCE_DSH_MODEL`). The
  headless app has no `--model` flag, so the backend applies selection with
  the launcher's documented `--patch` overlay on `agent-default-model`
  (provider + model + reasoningEffort), based on facts read locally from
  `dsh --dump-config`. No provider API is contacted to discover models.
- **Model discovery.** New `list_models` MCP tool and `node dist/cli.js models`
  command list the models the runtime is configured to serve plus the current
  default. `list_backends` / `get_backend` include `models` + `default_model`.
- **Universal per-task model.** `model` works on every backend that advertises
  `capabilities.modelSelection` (dsh, opencode, openai, anthropic). Backends
  that don't support it reject the parameter explicitly instead of ignoring it.
- Per-task model is persisted (schema v7), shown in results/status, honored by
  `continue_worker`, included in cache keys, and aggregated in
  `get_system_metrics().models`.
- Backend failure errors now include a bounded, redacted stderr tail — e.g.
  invalid models surface the runtime's `UNKNOWN_MODEL` message directly.

### Expanded backend support (part of v5.1)

- Removed all vendor-specific naming. Environment variables,
  branches, state paths, package/bin names, and messages are now universal.
- Added **OpenCode** as a first-class CLI backend with durable JSONL logs,
  session continuation, usage/tool events, and reattach support.
- Added **direct OpenAI** backend: Chat Completions (`/chat/completions`) or
  Responses (`/responses`), with a tool loop.
- Added **direct Anthropic** backend: Messages API with a tool loop.
- Direct API backends expose `read_file`, `write_file`, `list_directory`, and
  `run_shell`, scoped to the task workspace.
- `WORKFORCE_BACKEND=auto` plus automatic local backend discovery.
- Workflow token budget is now `max_worker_tokens` (vendor alias removed).

## Quick start

```bash
npm install
npm run build

# Optional: see which backends are locally available, and their models
node dist/cli.js backends
node dist/cli.js models

# Start the MCP server on stdio
node dist/cli.js serve   # or: npx workforce-mcp serve
```

Zero-config behavior depends on what is installed:

- `dsh` on PATH (or `/usr/local/bin/dsh`) → DeepSeek Harness
- otherwise `opencode` on PATH → OpenCode
- otherwise an OpenAI/Anthropic API key in the environment → direct API
- otherwise the server starts and reports a clear availability error at spawn

## Installation

Requirements:

- macOS or Linux
- Node.js ≥ 22 (uses built-in `node:sqlite`; verified on Node 24)
- git
- at least one worker backend:
  - DeepSeek Harness CLI, or
  - OpenCode CLI, or
  - an OpenAI-compatible API key, or
  - an Anthropic API key

```bash
npm install
npm run build
```

## Codex MCP setup

```toml
[mcp_servers.workforce]
command = "node"
args = ["/absolute/path/to/workforce-orchestrator-mcp/dist/cli.js", "serve"]
```

Each argv element must be its own TOML string. A single
`"…/cli.js serve"` string makes node look for a file literally named
`cli.js serve`.

Optional environment overrides:

```toml
[mcp_servers.workforce.env]
WORKFORCE_BACKEND = "auto"
WORKFORCE_MAX_CONCURRENT = "4"
WORKFORCE_ROOT = "~/.workforce/workers"
# Direct API backends:
# OPENAI_API_KEY = "..."
# ANTHROPIC_API_KEY = "..."
```

## Backend configuration (`~/.workforce/config.json`)

The orchestrator works with zero configuration. The optional config file is the
single place to pin executables, choose models, and route roles.

- **First CLI start** → if the file is absent, a minimal starter config is
  written automatically (`{"default_backend":"auto", ...}`). It pins nothing,
  so local backend discovery still applies.
- **Existing file** → strict JSON (no comments, no YAML), read once at startup
  and never modified by the server.
- **WORKFORCE_CONFIG** → override the config file location (useful for CI,
  tests, or per-project configs).
- To start from the fully populated reference instead of the starter:

  ```bash
  mkdir -p ~/.workforce
  cp config.example.json ~/.workforce/config.json
  node dist/cli.js backends
  ```

### Schema

```jsonc
{
  "default_backend": "auto",
  "backends": {
    "dsh": {
      "command": "/usr/local/bin/dsh",
      "profile": "headless",
      "model": "k3",                 // default model for every dsh run
      "provider": "kimi-coding",     // provider that owns the model
      "reasoning_effort": "max"      // passed through as reasoningEffort
    },
    "opencode": {
      "command": "opencode",
      "model": "anthropic/claude-sonnet-4-5",
      "agent": "build",
      "auto_approve": true,
      "args": []
    },
    "openai": {
      "api_key_env": "OPENAI_API_KEY",
      "base_url": "https://api.openai.com/v1",
      "model": "gpt-4.1",
      "api": "chat_completions",     // or "responses"
      "max_output_tokens": 8192,
      "temperature": 0.2,
      "max_iterations": 48
    },
    "anthropic": {
      "api_key_env": "ANTHROPIC_API_KEY",
      "base_url": "https://api.anthropic.com",
      "model": "claude-sonnet-4-5",
      "api_version": "2023-06-01",
      "max_output_tokens": 8192,
      "max_iterations": 48
    }
  },
  "routing": {
    "default": "dsh",
    "roles": {
      "reviewer": "openai",
      "researcher": "anthropic"
    }
  }
}
```

All fields are optional. Unknown keys are ignored for forward compatibility.
Backend ids referenced in `default_backend` / routing must be registered,
otherwise startup fails with an explicit error.

### Credentials

Prefer environment variables over storing secrets in the config file:

| Backend | Primary env | Alternate env |
|---|---|---|
| OpenAI | `WORKFORCE_OPENAI_API_KEY` | `OPENAI_API_KEY` |
| Anthropic | `WORKFORCE_ANTHROPIC_API_KEY` | `ANTHROPIC_API_KEY` |

`api_key_env` lets you name a different environment variable. A custom
`base_url` can point at a local OpenAI-compatible gateway that does not require
a key; in that case the backend is considered available even with an empty key.

`dsh` and `opencode` own their credentials (for example `~/.dsh/.env` and
`opencode auth`); the orchestrator does not read or manage them.

### Model selection

Every backend with `capabilities.modelSelection` accepts a per-task model:

```json
{ "task": "...", "mode": "code", "model": "k3-256k" }
```

Precedence (first hit wins), per backend:

1. `model` on the task / workflow task / `continue_worker`
2. `backends.<id>.model` in `~/.workforce/config.json`
3. `<BACKEND>_MODEL` environment variable (e.g. `WORKFORCE_DSH_MODEL`)
4. The runtime's own default (for dsh: `agent-default-model` in the profile)

Discover what is available **without spending quota** (local config only):

```bash
node dist/cli.js models          # default backend
node dist/cli.js models dsh      # specific backend
```

The same data is exposed as the `list_models` MCP tool and inside
`list_backends` / `get_backend` (`models`, `default_model`).

**How dsh applies it.** The headless app takes no `--model` flag, so the
backend writes a small `--patch` overlay next to the task logs
(`dsh-model.yml`) that replaces the `agent-default-model` entry with
`provider`, `model`, and `reasoningEffort`. Base values are read from
`dsh --dump-config --profile <profile>` (a local command; never the network,
never credentials). An invalid model is not swallowed: the run fails and the
error carries the runtime message, e.g.
`backend exited with code=1 …; stderr: dsh: UNKNOWN_MODEL: pi-ai provider "kimi-coding" has no configured model "…"`.

For the other backends the override flows through their native mechanism:
opencode `--model`, OpenAI `model` field, Anthropic `model` field.

### Environment variables

| Variable | Default | Meaning |
|---|---|---|
| `WORKFORCE_BACKEND` | `auto` | `auto`, `dsh`, `opencode`, `openai`, `anthropic`, `fake` |
| `WORKFORCE_DEFAULT_BACKEND` | — | Routing default (overrides `WORKFORCE_BACKEND`) |
| `WORKFORCE_CONFIG` | `~/.workforce/config.json` | Config file path (created on first start if absent) |
| `WORKFORCE_ROOT` | `~/.workforce/workers` | State root (worktrees, task workspaces, db) |
| `WORKFORCE_DB` | `<root>/workers.db` | SQLite database |
| `WORKFORCE_MAX_CONCURRENT` | `4` | Max parallel tasks |
| `WORKFORCE_DEFAULT_TIMEOUT` | `3600` | Default per-task timeout (s) |
| `WORKFORCE_MAX_TIMEOUT` | `21600` | Timeout ceiling (s) |
| `WORKFORCE_LOG_MAX_BYTES` | `32768` | `get_worker_log` cap |
| `WORKFORCE_DIFF_MAX_BYTES` | `65536` | Full-diff cap |
| `WORKFORCE_FINAL_TEXT_MAX_CHARS` | `8000` | Final report text cap |
| `WORKFORCE_TERMINATE_GRACE_MS` | `5000` | SIGTERM→SIGKILL grace |
| `WORKFORCE_DSH_COMMAND` | `/usr/local/bin/dsh` or PATH | dsh executable |
| `WORKFORCE_DSH_PROFILE` | `headless` | dsh profile |
| `WORKFORCE_DSH_MODEL` | profile default | Default dsh model (e.g. `k3-256k`) |
| `WORKFORCE_DSH_PROVIDER` | profile default | Provider owning the dsh model |
| `WORKFORCE_DSH_REASONING_EFFORT` | profile default | e.g. `max` |
| `WORKFORCE_OPENCODE_COMMAND` | `opencode` | OpenCode executable |
| `WORKFORCE_OPENCODE_MODEL` | OpenCode default | `provider/model` |
| `WORKFORCE_OPENCODE_AGENT` | OpenCode default | Agent name |
| `WORKFORCE_OPENCODE_AUTO` | `true` | Pass `--auto` |
| `WORKFORCE_OPENCODE_ARGS` | — | Extra CLI flags (space-separated) |
| `WORKFORCE_OPENAI_API_KEY` | — | OpenAI key |
| `WORKFORCE_OPENAI_BASE_URL` | `https://api.openai.com/v1` | OpenAI base URL |
| `WORKFORCE_OPENAI_MODEL` | `gpt-4.1` | OpenAI model |
| `WORKFORCE_OPENAI_API` | `chat_completions` | `chat_completions` or `responses` |
| `WORKFORCE_ANTHROPIC_API_KEY` | — | Anthropic key |
| `WORKFORCE_ANTHROPIC_BASE_URL` | `https://api.anthropic.com` | Anthropic base URL |
| `WORKFORCE_ANTHROPIC_MODEL` | `claude-sonnet-4-5` | Anthropic model |
| `WORKFORCE_ANTHROPIC_VERSION` | `2023-06-01` | API version header |
| `WORKFORCE_ENABLE_SUBAGENTS` | `false` | Allow research/general subagent delegation |
| `WORKFORCE_MAX_SUBAGENT_DEPTH` | `1` | Subagent depth limit |
| `WORKFORCE_MAX_SUBAGENTS_PER_TASK` | `3` | Subagent count limit |

## MCP tools

### Orchestration

| Tool | Purpose |
|---|---|
| `create_workflow` | Key-based task DAG; shared workspace; budgets; failure policy; cycles rejected. |
| `run_workflow_preset` | Expand a built-in preset around a goal (`options.dry_run` = plan only). |
| `list_presets` / `list_roles` | Built-in presets and role templates. |
| `get_workflow` / `list_workflows` | Transparent plan and live status. |
| `wait_workflow` | Event-driven wait for terminal state. |
| `cancel_workflow` | Cancel all non-terminal tasks; preserves workspaces/artifacts. |
| `get_workflow_result` | Compact aggregate: outcomes, artifacts, metrics, warnings. |
| `get_workflow_diff` | Integrated diff vs base (stat by default). |
| `review_task_result` | Record acceptance (`accepted`/`rejected`/`needs_revision`). |
| `get_task_metrics` / `get_workflow_metrics` / `get_system_metrics` | Objective counters and quality stats. |

### Tasks

| Tool | Purpose |
|---|---|
| `spawn_task` | Spawn any mode with any workspace, deliverables, capabilities, backend, model. |
| `spawn_tasks` | Batch-spawn up to 16 independent tasks. |
| `wait_task` / `wait_tasks` | Efficient event-driven waiting (`all`/`any`). |
| `get_task_result` | Compact result: status, summary, artifacts, deliverables, usage. |
| `list_artifacts` / `get_artifact` / `get_artifact_metadata` | Artifact metadata and capped content. |
| `get_task_usage` | Tokens, steps, tool calls by tool, runs, duration. |
| `list_backends` / `get_backend` | Backend availability, capabilities, factual metadata, model catalog. |
| `list_models` | Models a backend serves + its default (local config only, no API call). |

### Workers (legacy aliases, fully supported)

| Tool | Purpose |
|---|---|
| `spawn_worker` | `spawn_task(mode=code, git_worktree)`. |
| `get_worker` / `list_workers` | Compact worker status. |
| `continue_worker` | Resume the task in the same workspace/backend session. |
| `cancel_worker` | Graceful terminate; workspace and changes preserved. |
| `get_worker_result` | Legacy git-focused compact result. |
| `get_worker_log` / `get_worker_diff` | Redacted, capped diagnostics. |

## Task modes

| Mode | Purpose | Default workspace |
|---|---|---|
| `code` | Implement, fix, test, debug, commit in an isolated worktree | git_worktree |
| `research` | Investigate (web when available), synthesize, record `sources.json` | temporary |
| `data` | Analyze CSV/JSON/SQLite/logs; inputs read-only, outputs as files | directory (if path given) |
| `docs` | README/architecture/migration docs | any |
| `file` | Batch organize/classify/rename/convert inside the workspace | directory/temporary |
| `ops` | Logs/config/service diagnosis; proposals, no destructive changes | directory/temporary |
| `review` | Read-only code review; may run tests | git_worktree |
| `general` | Generic knowledge work with file deliverables | temporary |

Each mode has its own prompt template under [src/prompts/](src/prompts/).

## Workspaces

- **git_worktree** — isolated branch `workforce/worker/<id>` and worktree under
  `~/.workforce/workers/<repo>-<hash>/<id>/worktree`. The orchestrator never
  merges, pushes, or touches your main branch.
- **directory** — existing caller directory, canonicalized; artifact indexing is
  scoped to it.
- **temporary** — fresh per-task directory under the state root, kept after
  completion.

## Workflows, DAGs, and git isolation

- Workflows share one workspace; git workflows share one integration branch
  (`workforce/workflow/<wf-id>`).
- Each git workflow task gets its own worktree and branch
  (`workforce/task/<id>`), materialized from the current integration HEAD.
- Task commits are cherry-picked onto the integration branch through a
  per-workflow async mutex. Conflicts abort cleanly and are surfaced with the
  conflicting files/commits — never auto-resolved.
- `continue_worker` on a conflicted task creates a fresh worktree from the
  latest integration HEAD (`workforce/task/<id>-r<N>`) and retries integration.
- Failure policies: `stop_dependents` (default), `continue`,
  `cancel_remaining`.
- Budgets: per-task token/time/tool/iteration caps and per-workflow
  `max_worker_tokens`, `max_duration_seconds`, `max_concurrent_tasks`,
  `max_tasks`. Breaches produce terminal `budget_exceeded`.

## Durable supervision

- Child-process backends (`dsh`, `opencode`) write stdout/stderr directly to
  durable log files, so a supervisor crash does not break the worker.
- On restart: a live PID with matching identity is reattached (log replay,
  counters, timeout, budgets); a dead PID with a durable terminal event is
  finalized normally; a dead PID with no terminal evidence becomes
  `interrupted`. Exit codes are never fabricated.
- Graceful server shutdown detaches workers instead of killing them. Only
  `cancel_worker`, `cancel_workflow`, timeout, or budget terminate them.
- Direct API backends (`openai`, `anthropic`) run in-process and cannot be
  reattached; an in-flight request becomes `interrupted` on restart, with the
  workspace and artifacts preserved.

## Git ownership

Workers edit and test files. The supervisor verifies and commits successful
changes on git worktrees:

- commit message from a `suggested_commit_message:` line in the final report,
  or `workforce/worker/<task-id>: apply delegated changes`;
- worker-created commits are accepted, remaining dirty files are finalized in a
  follow-up commit;
- cancelled / timed-out / failed runs are never auto-committed;
- commit identity uses the repo's existing `user.name`/`user.email`, with a
  fallback identity only when missing;
- supervisor git commands: `status`, `diff`, `add`, `commit`, `rev-parse`,
  `log`, `cherry-pick` (+`--abort`), `worktree`, `branch`. Never push, merge,
  reset, clean, or rebase.

## Waiting and compact context

Do not poll. `wait_task` / `wait_tasks` / `wait_workflow` are event-driven and
return as soon as terminal state is reached (or the wait budget expires). A wait
timeout never cancels a worker.

Compact results contain status, summary, artifact metadata, usage, and
warnings. Thinking, raw tool output, full logs, full datasets, and giant diffs
never enter context automatically. Fetch them explicitly with `get_artifact`,
`get_worker_log`, or `get_worker_diff`; all are capped and redacted.

## Deliverables and artifacts

`deliverables: ["report.md", ...]` is written into the worker prompt as a hard
requirement and verified on disk at completion. Missing files are recorded as
`deliverables_missing` (the task can still be `completed`).

Every produced file is indexed in SQLite with path, mime type, size, and
sha256. Artifacts must live inside the task workspace; arbitrary paths are
rejected. Binary artifacts return metadata only.

## Roles and presets

Built-in roles: `implementer`, `tester`, `researcher`, `reviewer`,
`docs_writer`, `data_analyst`, `debugger`, `ops_analyst`.

Built-in presets: `bug_fix`, `feature_development`, `quality_audit`,
`research_report`, `data_analysis`. Presets are transparent DAGs — inspect them
with `list_presets` / `get_workflow`, or dry-run with
`run_workflow_preset({options:{dry_run:true}})`.

## CLI

```bash
node dist/cli.js serve
node dist/cli.js workers|tasks [--status S] [--limit N]
node dist/cli.js task <id>
node dist/cli.js inspect <id>
node dist/cli.js artifacts <id>
node dist/cli.js artifact <id> <path> [--max-bytes N]
node dist/cli.js usage <id>
node dist/cli.js logs <id> [--lines N]
node dist/cli.js diff <id> [--full]
node dist/cli.js cancel <id>
node dist/cli.js workflows
node dist/cli.js workflow <id>
node dist/cli.js workflow-wait <id> [--timeout S]
node dist/cli.js workflow-cancel <id>
node dist/cli.js review-task <id> <accepted|rejected|needs_revision> [--feedback "..."]
node dist/cli.js task-metrics <id> | workflow-metrics <id> | metrics
node dist/cli.js roles | presets | cache-stats
node dist/cli.js backends | backend <id>
```

## Development

```bash
npm run build
npm test                 # unit + integration (fake/local backends, temp repos)
npm run test:unit
npm run test:integration
```

Smoke / live E2E scripts:

```bash
node scripts/mcp-smoke.mjs /path/to/git/repo   # MCP protocol smoke (fake backend)
node scripts/real-dsh-e2e.mjs                  # live dsh coding E2E
node scripts/research-e2e.mjs                  # live research E2E
node scripts/wait-e2e.mjs                      # live parallel wait E2E
node scripts/workflow-e2e.mjs                  # live 3-task DAG E2E
node scripts/parallel-e2e.mjs                  # live parallel git writers E2E
node scripts/reattach-e2e.mjs                  # live supervisor-restart reattach E2E
```

## Security

- **Subprocess backends**: argv-array spawn only; prompts/session ids cannot
  inject shell commands.
- **Direct API backends**: file tools resolve paths strictly inside the task
  workspace. `run_shell` is an intentional agent capability and executes in
  that workspace; do not run untrusted models on machines you cannot afford to
  lose.
- **Credentials**: dsh/OpenCode credentials are never read. API keys are read
  only from the configured environment variables and are never emitted in
  results, logs, diffs, artifacts, or errors (all output passes a redaction
  filter).
- **Paths**: `repo_path`/`workspace.path` are canonicalized and validated; all
  state lives under the worker root; artifact/deliverable/diff paths must be
  relative and traversal-free.
- **Ops mode**: prompt-level prohibition of sudo/destructive commands/SSH/system
  changes; proposals over direct modification. Advisory, not a hard sandbox.
- **Process ownership**: PID-tracked with start-token identity; reconciliation
  never assumes liveness from a bare PID.

## Known limitations

- Direct API backends are in-process: no durable reattach across supervisor
  restarts, and their shell tool inherits the server's permissions.
- Token budgets are best-effort, checked per backend event; a run can overshoot
  within one step.
- Test pass/fail in compact results is heuristic (from the final report); git
  facts and deliverables are verified directly.
- Directory-workspace artifact discovery is mtime-based.
- Capabilities are advisory (prompt-level), not a security sandbox.
- Artifact cache is opt-in and only for dependency-free tasks.
- Subagent support is prompt/config only; the runtime manages subagents.
- No automatic worktree/workspace garbage collection (manual, by design).

## License

Licensed under the [Apache License, Version 2.0](LICENSE).
Copyright 2026 Frank Ruan.

Maintenance

ActivityMaintained
ResponsivenessNo issues