Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 28 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Cord

A coordination protocol for trees of Claude Code agents.
A coordination protocol for trees of coding agents.

One goal in, multiple agents out. They decompose, parallelize, wait on dependencies, and synthesize — all through a shared SQLite database.

Expand Down Expand Up @@ -31,17 +31,24 @@ No workflow was hardcoded. The agent built this tree at runtime.
## Prerequisites

- [uv](https://docs.astral.sh/uv/) (Python package manager)
- [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) installed and authenticated (`claude` command available)
- An Anthropic API key with sufficient credits
- [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) installed and authenticated (`claude` command available) for `--runtime claude`
- [Codex CLI](https://developers.openai.com/codex/app-server/) installed and authenticated (`codex` command available) for `--runtime codex-app-server`
- [Amp CLI](https://ampcode.com/) installed and authenticated (`amp` command available) for `--runtime amp`
- An Anthropic API key with sufficient credits for `--runtime claude`
- OpenAI auth (ChatGPT login or API key) configured in Codex for `--runtime codex-app-server`

## Install

```bash
git clone https://github.com/kimjune01/cord.git
cd cord
uv sync
# optional runtime extras
uv sync --extra amp
```

If published as a package, the same extra is available as `cord[amp]`.

## Usage

```bash
Expand All @@ -53,14 +60,27 @@ cord run plan.md

# Control budget and model
cord run "goal" --budget 5.0 --model opus

# Select runtime explicitly
cord run "goal" --runtime codex-app-server --model gpt-5.2-codex
cord run "goal" --runtime claude --model opus
cord run "goal" --runtime amp

# Runtime shorthand flags
cord --codex run "goal"
cord --claude run "goal"
cord --amp run "goal"
```

**Options:**

| Flag | Default | Description |
|------|---------|-------------|
| `--budget` | 2.0 | Max USD per agent subprocess |
| `--model` | sonnet | Claude model (sonnet, opus, haiku) |
| `--model` | sonnet (`claude`), gpt-5.2-codex (`codex-app-server`), none (`amp`) | Model override for the selected runtime |
| `--runtime` | codex-app-server | Agent runtime (`codex-app-server`, `claude`, `amp`) |

`amp` runtime currently ignores `--model` and `--budget` overrides (warning is printed).

## How it works

Expand Down Expand Up @@ -127,7 +147,8 @@ src/cord/
prompts.py # Prompt assembly for agents
runtime/
engine.py # Main loop, TUI
dispatcher.py # Launch claude CLI processes
harness/ # Runtime adapter registry + implementations
codex_app_server_worker.py # JSON-RPC client for Codex App Server
process_manager.py # Track subprocesses
mcp/
server.py # MCP tools (one server per agent)
Expand Down Expand Up @@ -157,11 +178,11 @@ Each agent subprocess has its own Claude API budget (set via `--budget`). A simp
- No web UI — terminal TUI only.
- No mid-execution message injection (pause/modify/resume requires relaunch).
- Each agent gets its own MCP server process (~200ms startup overhead).
- Claude Code CLI must be installed and authenticated.
- Runtime-specific CLI must be installed and authenticated (`codex`, `claude`, or `amp`).

## Alternative implementations

This repo is one implementation of the Cord protocol. The protocol itself — four primitives, dependency resolution, authority scoping, two-phase lifecycle — is independent of the backing store, transport, and agent runtime. You could implement Cord with Redis pub/sub, Postgres for multi-machine coordination, HTTP/SSE instead of stdio MCP, or non-Claude agents. See [RFC.md](RFC.md) for the full protocol specification.
This repo is one implementation of the Cord protocol. The protocol itself — three core primitives (`goal`, `task`, `ask`) plus optional `serial` sequencing, dependency resolution, authority scoping, two-phase lifecycle — is independent of the backing store, transport, and agent runtime. You could implement Cord with Redis pub/sub, Postgres for multi-machine coordination, HTTP/SSE instead of stdio MCP, or non-Claude agents. See [RFC.md](RFC.md) for the full protocol specification.

## License

Expand Down
2 changes: 1 addition & 1 deletion experiments/behavior_compare.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "src"))

from cord.db import CordDB
from cord.runtime.dispatcher import generate_mcp_config, MCP_TOOLS
from cord.runtime.harness.base import generate_mcp_config, MCP_TOOLS

PROJECT_DIR = Path(__file__).resolve().parent.parent
RESULTS_FILE = Path(__file__).resolve().parent / "RESULTS.md"
Expand Down
5 changes: 4 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
[project]
name = "cord"
version = "0.1.0"
description = "A coordination protocol for trees of Claude Code agents"
description = "A coordination protocol for trees of coding agents"
readme = "README.md"
authors = [
{ name = "June Kim", email = "kimjune01@gmail.com" }
]
requires-python = ">=3.12"
dependencies = ["mcp>=1.26.0"]

[project.optional-dependencies]
amp = []

[project.scripts]
cord = "cord.cli:main"
cord-mcp-server = "cord.mcp.server:main"
Expand Down
124 changes: 105 additions & 19 deletions src/cord/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,45 +6,131 @@
from pathlib import Path

from cord.runtime.engine import Engine
from cord.runtime.harness.registry import (
default_model_for_runtime,
default_runtime,
runtime_names,
)


RUNTIME_FLAG_ALIASES = {
"--amp": "amp",
"--claude": "claude",
"--codex": "codex-app-server",
}


def _parse_run_args(args: list[str]) -> tuple[str, float, str | None, str]:
"""Parse run args in any order."""
goal: str | None = None
budget = 2.0
model: str | None = None
runtime = default_runtime()

i = 0
while i < len(args):
arg = args[i]

if arg == "--budget":
if i + 1 >= len(args):
raise ValueError("--budget requires a numeric value")
budget = float(args[i + 1])
i += 2
continue

if arg == "--model":
if i + 1 >= len(args):
raise ValueError("--model requires a value")
model = args[i + 1]
i += 2
continue

if arg == "--runtime":
if i + 1 >= len(args):
raise ValueError("--runtime requires a value")
runtime = args[i + 1]
i += 2
continue

if arg in RUNTIME_FLAG_ALIASES:
runtime = RUNTIME_FLAG_ALIASES[arg]
i += 1
continue

if arg.startswith("--"):
raise ValueError(f"Unknown option for run: {arg}")

if goal is not None:
raise ValueError(f"Unexpected extra argument: {arg}")
goal = arg
i += 1

if not goal:
raise ValueError("Missing goal. Provide text or a goal file path.")

supported = set(runtime_names())
if runtime not in supported:
raise ValueError(
f"Unknown runtime: {runtime}. Expected one of: {', '.join(runtime_names())}"
)

if model is None:
model = default_model_for_runtime(runtime)

return goal, budget, model, runtime


def main() -> None:
"""CLI entry point: cord run "goal" [--budget <usd>] [--model <model>]."""
"""CLI entry point: cord run "goal" [--budget <usd>] [--model <model>] [--runtime <runtime>]."""
args = sys.argv[1:]

runtime_hint = "|".join(runtime_names())
if not args or args[0] in ("-h", "--help", "help"):
print("Usage:")
print(' cord run "goal description" [--budget <usd>] [--model <model>]')
print(' cord run plan.md [--budget <usd>] [--model <model>]')
print(
f' cord run "goal description" [--budget <usd>] [--model <model>] [--runtime <{runtime_hint}>]'
)
print(
f' cord run plan.md [--budget <usd>] [--model <model>] [--runtime <{runtime_hint}>]'
)
print(" cord --amp run \"goal description\"")
print(" cord --claude run \"goal description\"")
print(" cord --codex run \"goal description\"")
sys.exit(0)

preselected_runtime: str | None = None
while args and args[0] in RUNTIME_FLAG_ALIASES:
preselected_runtime = RUNTIME_FLAG_ALIASES[args[0]]
args = args[1:]

command = args[0]

if command == "run":
if len(args) < 2:
print('Usage: cord run "goal description" [--budget <usd>] [--model <model>]', file=sys.stderr)
try:
goal_arg, budget, model, runtime = _parse_run_args(args[1:])
if preselected_runtime:
runtime = preselected_runtime
if "--model" not in args:
model = default_model_for_runtime(runtime)
except ValueError as exc:
print(
str(exc),
file=sys.stderr,
)
sys.exit(1)

goal_arg = args[1]
goal_path = Path(goal_arg)
if goal_path.exists() and goal_path.is_file():
goal = goal_path.read_text().strip()
else:
goal = goal_arg

budget = 2.0
if "--budget" in args:
idx = args.index("--budget")
if idx + 1 < len(args):
budget = float(args[idx + 1])

model = "sonnet"
if "--model" in args:
idx = args.index("--model")
if idx + 1 < len(args):
model = args[idx + 1]

engine = Engine(goal, max_budget_usd=budget, model=model)
engine = Engine(
goal,
max_budget_usd=budget,
model=model,
runtime=runtime,
)
engine.run()

else:
Expand Down
Loading