Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

orchestrator

A small multi-agent orchestrator built on the Claude Code CLI. One command takes a user prompt, decomposes it into a task list, dispatches each task to a stateless sub-agent, and synthesizes a final response.

node scripts/run.js "your prompt here"

Works with a Claude Pro / Max subscription or an Anthropic API key — whichever your local claude CLI is authenticated against. No ANTHROPIC_API_KEY env var required.


How it works

                 ┌────────────────────────────────────────────┐
   user prompt → │ head agent  ·  decompose                   │ → state/session.json (tasks: pending)
                 │   head_agent.py  OR  .claude/agents/       │
                 │                       head-agent.md        │
                 └────────────────────────────────────────────┘
                                       │
                                       ▼
                 ┌────────────────────────────────────────────┐
                 │ scripts/dispatch.js                        │ → state/session.json (tasks: completed/failed)
                 │   spawns `claude --agent sub-agent` per    │
                 │   pending task whose deps are completed    │
                 └────────────────────────────────────────────┘
                                       │
                                       ▼
                 ┌────────────────────────────────────────────┐
                 │ head_agent.py  summarize                   │ → state/session.json (final_summary)
                 └────────────────────────────────────────────┘
                                       │
                                       ▼
                                 final response

Four components, one shared state file (state/session.json):

  1. head_agent.py — Python module + CLI. Two subcommands: decompose and summarize. Calls Claude to plan and to synthesize. Never executes the user's work itself.
  2. .claude/agents/head-agent.md — Optional Claude-Code-native planner. Interactive variant of the Python decomposer; plans and tracks state across conversation turns. Also never executes.
  3. scripts/dispatch.js — Node runner. Reads state/session.json, spawns a sub-agent per pending task (respecting dependencies), writes results back, marks tasks completed or failed.
  4. .claude/agents/sub-agent.md — Claude Code subagent definition. Stateless single-task executor: gets one task object as its brief, returns a short plain-text result summary, retains nothing.

scripts/run.js wires the headless path together. See Two ways to drive it for when to pick which planner.


Prerequisites

  • Python 3.10+ (no third-party Python packages required)
  • Node.js 18+ (uses only Node built-ins)
  • Claude Code CLI (claude) installed and on PATH. Install: https://docs.claude.com/claude-code

That's it — no pip install, no npm install.

Authentication

This project runs entirely through the claude CLI, so it inherits whatever auth the CLI is logged into. Either path works:

  • Claude Pro / Max subscription — run claude once interactively and complete the browser login flow. The orchestrator then uses your subscription quota for every model call. No env vars needed.
  • Anthropic API key — run claude /login inside Claude Code and pick the API-key option (recommended), or export ANTHROPIC_API_KEY in your shell. Billed via console.anthropic.com.

Pick one — you don't need both. To verify, run claude -p "say hello"; if you get a response, the orchestrator will work.

Run

node scripts/run.js "Compare the climate policies of Norway and Sweden and summarize the main differences."

run.js will:

  1. Call head_agent.py decompose → writes state/session.json with a task list.
  2. Call scripts/dispatch.js → spawns sub-agents to execute each task in dependency order, writing each task's result back into state/session.json.
  3. Call head_agent.py summarize → reads the completed session and prints the final user-facing response to stdout.

state/session.json is left in place after the run so you can inspect what happened.


Two ways to drive it

The repo ships two equivalent planners, sharing one canonical state file at state/session.json:

1. Headless / scripted — scripts/run.js

head_agent.py plans and summarizes; scripts/run.js and scripts/dispatch.js glue it to the sub-agents.

node scripts/run.js "your prompt here"

Use this when invoking from a shell, CI job, or another program. It's the fastest path from prompt to final response and the components are non-interactive.

2. Interactive — .claude/agents/head-agent.md

A Claude Code subagent that plans and tracks state across turns. Inside a Claude Code session, ask Claude to use the head-agent subagent:

> use the head-agent to plan: build a CLI tool for fuzzy-finding bookmarks

It will read state/session.json, produce (or update) the task list, write the state back, and end its message with the JSON state block. On follow-up turns it amends the plan, marks tasks done, or records failures — the JSON block is always the contract.

When you're ready to execute the planned tasks, switch to the dispatcher:

node scripts/dispatch.js              # uses state/session.json
python3 head_agent.py summarize       # final response

Both planners write the same schema to the same file, so you can mix them freely: plan interactively, execute headlessly, summarize either way.


File layout

orchestrator/
├── head_agent.py              # plan + summarize (Python, drives `claude -p`)
├── requirements.txt           # no third-party deps — comment-only
├── scripts/
│   ├── run.js                 # main entry point
│   └── dispatch.js            # sub-agent runner
├── .claude/
│   └── agents/
│       ├── head-agent.md      # Claude-Code-native planner (interactive)
│       └── sub-agent.md       # stateless task executor (Claude Code subagent)
└── state/
    └── session.json           # canonical session state (shared by all four components)

session.json schema

{
  "run_id": "run-20260520T031200Z-ab12cd",
  "user_prompt": "...",
  "created_at": "2026-05-20T03:12:00+00:00",
  "updated_at": "2026-05-20T03:14:55+00:00",
  "status": "initialized | decomposed | summarizing | finalized | failed",
  "model": "claude-sonnet-4-6",
  "tasks": [
    {
      "id": "fetch-data",
      "description": "Fetch climate policy summaries for both countries.",
      "agent_type": "research",
      "dependencies": [],
      "inputs": {},
      "outputs": {},
      "status": "pending | in_progress | completed | failed | skipped",
      "result": "Plain-text summary from the sub-agent.",
      "error": null
    }
  ],
  "final_summary": "The user-facing response, populated by `summarize`.",
  "metadata": {}
}

Every component reads and writes this same shape — the markdown head agent emits it inline at the end of every response, the Python head agent writes it on decompose/summarize, and the dispatcher updates per-task status/result/error in place.


Running each stage on its own

You don't have to use run.js — every stage has a standalone CLI.

Decompose only:

python3 head_agent.py decompose "your prompt" --session state/session.json --pretty

Run dispatch on an existing session:

node scripts/dispatch.js state/session.json

Summarize a completed session:

python3 head_agent.py summarize --session state/session.json

The head agent class is also importable:

from head_agent import HeadAgent

agent = HeadAgent(
    session_path="state/session.json",
    model="claude-sonnet-4-6",   # optional
    claude_bin="claude",          # optional — override path to the CLI
    timeout_s=300,                # optional — per-call timeout
)
state = agent.decompose("your prompt")
# … run dispatch …
final = agent.summarize()

Every Claude call shells out to claude -p under the hood, so the importable API has the same auth story as the CLI: subscription or API key, whichever the claude binary is logged into.


Configuration

Environment variables (all optional):

Var Default Used by Purpose
PYTHON_BIN python3 run.js Python interpreter to invoke.
SESSION_FILE ./state/session.json run.js Override the shared session-state path.
CLAUDE_BIN claude head_agent.py, dispatch.js Path to the Claude Code CLI.
HEAD_AGENT_TIMEOUT_S 300 head_agent.py Per-call timeout for claude -p invocations from the head agent.
DISPATCH_TIMEOUT_MS 600000 dispatch.js Per-task wall-clock timeout.
DISPATCH_PERMISSION bypassPermissions dispatch.js Permission mode passed to spawned claude processes.

No ANTHROPIC_API_KEY is needed — authentication is handled entirely by the claude CLI (see Authentication). The model used by the head agent defaults to claude-sonnet-4-6 and is overridable per command with --model.

A note on bypassPermissions

The dispatcher spawns claude with --permission-mode bypassPermissions so sub-agents can run unattended. This skips Claude Code's interactive permission prompts — which is what you want for headless orchestration, but it also means a sub-agent will execute its tools (file edits, shell commands, web requests) without asking you first. If you'd rather be in the loop, set DISPATCH_PERMISSION=acceptEdits or default.


Customizing the agents

Both Claude Code subagents (.claude/agents/head-agent.md and .claude/agents/sub-agent.md) are normal subagent files — edit their frontmatter to change available tools, or edit the body to change the contract.

Load-bearing rules for the sub-agent

The dispatcher passes one task object as the user message; everything else flows from sub-agent.md.

  1. No follow-up questions. The sub-agent must act on the task brief alone — there's no human in the loop during dispatch.
  2. Final message is plain prose. The dispatcher captures stdout verbatim as task.result. If you change this contract, also change how head_agent.py summarize consumes results.

Load-bearing rules for the head-agent

The markdown head-agent shares state with the rest of the pipeline via state/session.json.

  1. Never executes. It plans, updates state, and reports — full stop. Execution belongs to the sub-agents that the dispatcher spawns.
  2. Schema-compatible output. The JSON block at the end of every response must match the canonical schema shown above. Other components consume it directly.
  3. Only state/session.json is writable. No other files should be touched by the planner.

Troubleshooting

claude: command not found during dispatch. The Claude Code CLI isn't installed or isn't on PATH. Install it, or set CLAUDE_BIN=/full/path/to/claude.

unrecognized arguments: --agent sub-agent or similar. Older Claude Code CLI versions may use a different flag for selecting an agent. Update Claude Code, or edit the args array near the top of runSubAgent in scripts/dispatch.js.

claude CLI not authenticated, or claude -p hangs / fails immediately. Run claude once in an interactive terminal and complete the login flow (Pro/Max subscription OR API-key option). Once claude -p "hi" returns a response, the orchestrator will work too. No ANTHROPIC_API_KEY is required if you authenticated via subscription.

Some tasks are stuck pending after dispatch finishes. Their dependencies failed. Inspect state/session.json for the upstream error field. The summarize step will still run and will acknowledge the failed tasks.

Sub-agent returns an empty summary. The dispatcher treats this as a failure (error: "sub-agent returned an empty summary"). Re-running typically fixes transient cases; persistent ones usually mean the task description was too ambiguous for the sub-agent to act on.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages