@openhorizon/workflows
v0.1.6
Published
Deterministic multi-agent workflow orchestration as an MCP server — agent()/parallel()/pipeline() scripts in a hardened VM sandbox, subagents executed via the Cursor SDK.
Maintainers
Readme
@openhorizon/workflows
Deterministic multi-agent workflow orchestration as an MCP server. Workflow
scripts (agent() / parallel() / pipeline() / select() / phase() /
log() / budget) run in a hardened Node vm sandbox; each agent() call
executes as a fresh Cursor SDK agent.
A faithful port of Claude Code's "dynamic workflows" (Workflow tool,
v2.1.170) to the launch-and-poll MCP shape: the same script contract, VM
membrane, determinism rules, concurrency caps, journal-based resume, and
pipeline-over-barrier orchestration semantics — usable from any MCP client
(Cursor, Claude Code, Codex, ...). Plus a live terminal viewer for watching
runs unfold (npx @openhorizon/workflows), and first-class support for
agentic map-reduce over whole codebases.
Setup
{
"mcpServers": {
"workflows": {
"command": "npx",
"args": ["-y", "@openhorizon/workflows"],
"env": { "CURSOR_API_KEY": "cursor_..." }
}
}
}The bare command auto-detects how it was launched: an MCP client spawns it with
piped stdio (no TTY) → it starts the MCP server; a human runs it in a real
terminal → it opens the viewer below. Force either with serve / tui.
Live viewer (TUI)
Watch every run unfold in a terminal dashboard — phases, per-agent status, model, tokens, tool calls, durations, and prompt/result previews:
npx @openhorizon/workflows # interactive terminal → opens the viewer
npx @openhorizon/workflows tui # force the viewerIt's a read-only Ink dashboard over the run state the server persists (see Run state). Three screens:
- Runs — every run, newest first: status, description,
done/total agents, duration, and output tokens. - Run — a two-pane panel: a Phases sidebar (with each phase's done/total) on the left, and the selected phase's agents on the right (status, model, tokens, duration).
- Agent — a two-pane panel: the phase's agents sidebar on the left, and the
selected agent on the right — status · model,
tokens · tool calls · duration, the prompt (presseto expand), the live Activity (the tool the subagent is running right now, e.g.bash(rg …)), and the outcome (result or error).
Keys: ↑↓/jk move · enter/→ drill in (run → phase → agent) · esc/←
back · e expand the prompt · p/space pause · ? help · q quit. Launch
and stop runs from your coding agent (the workflow MCP tools); the viewer only
observes.
Skill
Install the /workflows skill — a guide that teaches your agent to author and
drive these tools — into your coding agents:
npx @openhorizon/workflows skill installInstalls globally (every project sees it) into auto-detected agents via
agent-install. Options:
npx @openhorizon/workflows skill install -a cursor # target a specific agent (repeatable)
npx @openhorizon/workflows skill install --project # current project instead of global
npx @openhorizon/workflows skill install --helpAfter installing, reload your agent and run /workflows. The MCP server above
must also be configured.
Tools
workflow— launch (or resume) a run; returns immediately with a run ID.workflow_status— progress snapshot (phases, agents, logs, failures, result); lists all runs withoutrunId.workflow_wait— long-poll until terminal, capped at 55s per call.workflow_stop— abort a running workflow (resumable afterwards).
Agentic map-reduce
For whole-codebase verdicts — security scans, deprecated-API sweeps, breaking-change detection, large migrations — a single search-driven agent is the wrong shape: it spends its budget finding the work, its context becomes the bottleneck, and "I've looked everywhere" is unfalsifiable. The runtime supports the map-reduce pattern directly:
- Plan (agentic) — an
agent({schema})studies the repo and authors selectors: deterministicgit greppatterns. - Shard (deterministic) —
select()runs the selectors over the whole repo with no model in the loop and zero token cost; the script buckets the resulting signals into bounded batches. - Map (agentic) —
pipeline(batches, ...)fans out onecacheMode: 'independent'agent per batch, each with a fresh, focused context and a findings schema. - Reduce (agentic) — one agent dedupes, connects cross-shard chains, and prioritizes the non-empty findings.
Coverage is guaranteed by construction: select() yields a finite work queue
and every batch is assigned. Two runtime pieces make it work:
select(query)— a constrained, read-only git query over the run'scwd.{grep: pattern, glob?, ignoreCase?, fixedString?, maxMatches?}returns[{file, line, text}]from tracked + untracked-not-ignored files;{files: glob}returns paths;{changed: commitish, glob?}returns paths changed since that commit (uncommitted included, deletions excluded). Globs with**use git:(glob)semantics. More than 4096 results throws an explicit error — narrow the query or passmaxMatches(no silent caps). This is the single deliberate read-only host capability exposed to scripts; it is not journaled — it re-runs fresh on resume, so a resumed scan selects against the current repo state.agent({cacheMode: 'independent'})— keys the resume cache on prompt+opts alone instead of the whole preceding call chain. Shard prompts embed their signals, so on a re-run every shard whose signals didn't change replays from cache regardless of order, insertion, or misses elsewhere — an incremental re-scan pays only for the diff. (The default'chained'mode keeps gold's unchanged-prefix semantics.)
See examples/mapreduce.workflow.js for a
runnable end-to-end scan, and the /workflows skill for the authoring
template.
Configuration (env)
| Variable | Default | Meaning |
| --------------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| CURSOR_API_KEY | — | Cursor API key the subagents bill to (required). |
| WORKFLOWS_MODEL | auto | Fallback subagent model when the workflow model arg / run --model is omitted. |
| WORKFLOWS_MAX_CONCURRENCY | min(16, cores-2) | Concurrent subagent cap per run. |
| WORKFLOWS_FIRST_TOKEN_MS | 120000 | Fail an agent fast if it emits no output token (text/tool call) within this many ms — catches throttled/stuck models that would otherwise retry-loop at 0 tokens. 0 disables. |
| OPENHORIZON_WORKFLOWS_DIR | ~/.openhorizon/workflows | State home (runs, journals, results). |
| WORKFLOWS_AGGRESSIVE | 0 | When 1/true, the workflow tool advertises a standing "run a workflow by default" opt-in instead of the conservative gate. |
Aggressive ("ultracode") mode
By default the workflow tool only launches on an explicit opt-in (matching
Claude Code's built-in). To make orchestration the default — "ultracode" style —
there are two complementary switches:
- Server-wide: start the server with
WORKFLOWS_AGGRESSIVE=1. The tool description then carries a standing opt-in, so the agent runs a workflow for every substantive task. Restart the server to toggle. - Per-session keyword: MCP can't push a per-turn reminder into the host
agent, so use a Cursor rule.
.cursor/rules/workflows-aggressive.mdc(shipped in this repo) turns on when you typeultracodeand off when you saysolo. For the same behavior across all projects, paste its body into Cursor Settings → Rules → User Rules (Cursor has no global rules directory on disk).
Run state
One directory per run under the state home: script.js (the iterate/resume
handle), journal.jsonl (chained-hash agent cache backing
resumeFromRunId), state.json (status snapshot), progress.jsonl (event
log), result.json (the script's return value).
Divergences from the Claude Code original
- Completion is poll-based (
workflow_status/workflow_wait) — MCP servers cannot push a task notification into the host conversation. budgetis a per-run output-token pool (set via thetokenBudgetinput), not the host session's shared turn budget.- Subagents can't auto-inherit the host agent's model (MCP exposes no caller
model), so the caller passes it: the
workflowmodelarg (orrun --model) sets the per-run default — your own model to match yourself, or a fast one likecomposer-2.5— falling back toWORKFLOWS_MODEL(elseauto).agent({model})overrides per call. Any model string may carry variant params as a query-string suffix —claude-opus-4-8?thinking=true&effort=max&context=1m— for max thinking/effort/context; use the exact catalog spelling, since unknown params are silently ignored rather than rejected. workflow()nests one level (a child runs in its own hardened realm but shares this run's concurrency cap, agent counter, abort, token budget, and resume cache), and takes{scriptPath}or inline{script}— running a saved workflow by name needs a registry this build doesn't ship.agent({agentType})is not available in this build;agent({isolation:'remote'})is unavailable exactly like gold.- Two extensions beyond gold for agentic map-reduce: the
select()hook (deterministic read-only git selection over the run's cwd) andagent({cacheMode: 'independent'})(per-call resume-cache keying for shard workers). Gold has neither; scripts that avoid them remain gold-compatible.
Development
pnpm install
pnpm --filter @openhorizon/workflows typecheck
pnpm --filter @openhorizon/workflows test
bun run build
# The server must run under Node, not Bun — the Cursor SDK's HTTP/2 stack
# crashes under Bun; bun is used for bundling only.
node dist/cli.js serve # MCP server
node dist/cli.js tui # viewerExercising the viewer
The viewer reads whatever is under OPENHORIZON_WORKFLOWS_DIR. To drive it
without live agents, seed fake run state into a scratch dir and point the viewer
at it (OPENHORIZON_WORKFLOWS_DIR=/tmp/demo node dist/cli.js tui). The TUI was
verified end-to-end by running it inside localterm
(a browser terminal) and driving + screenshotting it with
agent-browser.
