Skip to main content
Glama
README.md
# AgentBridge

> **Status: mid-migration, and this README describes the older shape.**
>
> AgentBridge started as a Claude-Code-to-Codex/Antigravity bridge, and
> everything documented below works today. It is being generalised so that any
> supported CLI can be the orchestrator and any other can be a worker, with
> routing driven by a per-user profile instead of hardcoded model names.
>
> For where it is going, read [`docs/architecture.md`](docs/architecture.md) and
> [`docs/roles.md`](docs/roles.md). In-flight: a provider adapter layer
> (`src/providers/`), a routing profile (`src/profile/`), and an
> `agentbridge init` setup flow. The Cursor adapter is present but
> **unverified** — its flags were written from prior knowledge rather than read
> off an installed CLI, and it says so at the top of the file.
>
> Until the migration lands, treat the sections below as accurate for
> Codex and Antigravity workers, and the docs as accurate for the design.

## Orchestrators

AgentBridge serves its `/agentbridge …` command surface as **MCP prompts**, so
any MCP client that supports `prompts/list` gets the same commands from the
server itself — no per-host command files to install or keep in sync.

| Host | Install | Commands |
|---|---|---|
| **Codex CLI** | [`docs/install-codex.md`](docs/install-codex.md) | via MCP prompts, plus [`hosts/codex/AGENTS.md`](hosts/codex/AGENTS.md) |
| **Claude Code** | register the MCP server, then optionally copy [`hosts/claude-code/skills/agentbridge`](hosts/claude-code/skills/agentbridge) into `~/.claude/skills/` | via MCP prompts |

| Command | Does |
|---|---|
| `agentbridge <goal>` | size the work, plan, delegate, verify, report |
| `agentbridge_setup` | detect CLIs, prove each works, write the routing profile |
| `agentbridge_status` | what is installed and how roles map; no inference |
| `agentbridge_doctor` | diagnose with real smoke jobs and say what to fix |
| `agentbridge_profile` | show or re-derive routing, optionally one role |
| `agentbridge_recon` | one bounded repository investigation |
| `agentbridge_review` | independent review by a different model family |
| `agentbridge_resume` | continue an interrupted run |

The doctrine behind those commands lives in [`doctrine/`](doctrine/) and names no
models — it routes by role, and [`.agentbridge/profile.json`](docs/roles.md) maps
roles onto whatever the user actually has installed.

A small local **stdio MCP server** that lets Claude Code delegate work to the
**Codex CLI** and the **Antigravity CLI (`agy`)** as external workers — using
the CLI sessions you are already logged into, with **no API keys**.

Claude stays the orchestrator. AgentBridge is deliberately dumb plumbing: it
turns Claude's structured request into a worker prompt, runs the CLI, and hands
back a compact structured result.

```
User
 └─> Claude Code                      (orchestrator — decides what to delegate)
      └─> AgentBridge MCP tool        (codex_run / antigravity_run)
           └─> codex exec  |  agy --print
                └─> result
           <─ structured MCP result
      <─ Claude inspects the work and continues
```

---

## What it is not

No cloud service, web UI, database, daemon, dashboard, job queue, account
system, or API-key management. It is one Node process that Claude Code starts
over stdio and stops when it exits.

---

## How it works

1. Claude calls `codex_run` or `antigravity_run` with a structured request
   (goal, mode, model, effort, paths, contract, acceptance criteria…).
2. AgentBridge assembles a worker prompt from exactly those fields. **It runs no
   model of its own** — this is string assembly, not inference.
3. It reserves write scopes, snapshots the git working tree, and spawns the CLI
   with an argument array (`shell: false`).
4. It parses the CLI's machine-readable output, extracts the worker's final
   result (never its internal reasoning), diffs the git tree to determine what
   actually changed, and returns a compact JSON result.

One MCP call = **one** worker attempt. AgentBridge never retries. Whether
another attempt is worthwhile is Claude's decision.

---

## Requirements

| | |
|---|---|
| Node.js | ≥ 20.10 (built and verified on 24.14) |
| Codex CLI | on `PATH`, already signed in (`codex login`) — verified against `codex-cli 0.147.0` |
| Antigravity CLI | `agy` on `PATH`, already signed in — verified against `agy 1.1.12` |
| git | optional but strongly recommended; without it `files_changed` / `scope_violations` cannot be computed |

AgentBridge never reads, copies, exports, or modifies your Codex or Antigravity
credentials. It invokes the CLIs exactly as a logged-in human would.

---

## Install and build

```bash
npm install
```

```bash
npm run build
```

```bash
npm test
```

The test suite uses mocked processes throughout, so a normal `npm test` consumes
**no model quota**.

---

## Register with Claude Code

Register once at **user scope** so every project can use it:

```bash
claude mcp add --transport stdio --scope user agentbridge -- node D:\Code\Agentbridge\dist\index.js
```

Verify from a terminal:

```bash
claude mcp list
```

```bash
claude mcp get agentbridge
```

Then verify from **inside** Claude Code by running `/mcp`. You should see
`agentbridge` listed as connected, with three tools: `codex_run`,
`antigravity_run`, `bridge_status`. Ask Claude to call `bridge_status` for a
full health report.

If you rebuild AgentBridge, restart Claude Code (or reconnect the server from
`/mcp`) so it picks up the new `dist/`.

---

## Project directory

Workers run in, and are scoped to, a single project directory, resolved in this
order:

1. `AGENTBRIDGE_PROJECT_DIR`, then the legacy `CLAUDE_PROJECT_DIR` (which Claude Code exports).
2. The first `file://` root the MCP client advertises.
3. The server process's working directory.

`bridge_status` reports which one was used.

---

## Tools

### `codex_run`

| Field | Type | Notes |
|---|---|---|
| `goal` | string | **required** |
| `mode` | `analyze` \| `implement` \| `review` | **required** |
| `model` | `gpt-5.6-sol` \| `gpt-5.6-terra` \| `gpt-5.6-luna` | **required** |
| `effort` | `light` \| `medium` \| `high` \| `extra high` \| `max` | **required** |
| `relevant_files` | string[] | files to look at first |
| `allowed_paths` | string[] | **required in `implement` mode** |
| `no_touch` | string[] | paths the worker must not modify |
| `context` | string | background the repo does not contain |
| `contract` | string | interfaces/types to conform to exactly |
| `acceptance_criteria` | string[] | |
| `tests` | string[] | context for the worker — AgentBridge never runs these itself |
| `timeout_seconds` | number | default 900, clamped to 30–3600 |

**Models.** Three slugs are addressable, and each is passed to the CLI verbatim:

| `model` | What it is |
|---|---|
| `gpt-5.6-sol` | strongest general engineering model |
| `gpt-5.6-terra` | deep brownfield / existing-codebase specialist |
| `gpt-5.6-luna` | economical high-volume workhorse |

**Effort mapping** (orchestrator label → Codex `model_reasoning_effort`):

| `light` | `medium` | `high` | `extra high` | `max` |
|---|---|---|---|---|
| `low` | `medium` | `high` | `xhigh` | `max` |

The mapping is total and deterministic. Codex's `ultra` tier is deliberately not
exposed: only some models offer it, and a sixth label that works for two of three
models would make routing model-dependent.

**Availability.** Before launching, AgentBridge checks the requested model and
effort against the Codex CLI's own model catalogue (`models_cache.json` in
`CODEX_HOME`) — a file read, no inference.

- model listed, effort listed → the job runs
- model listed, effort not → `invalid_effort`, with `details.supported_efforts`
- model not listed → `requested_model_unavailable`, with `details.available_models`
- no readable catalogue → the job runs anyway, with a warning that availability
  was `unverified`

Nothing is ever substituted for anything else. A routing decision that cannot be
honoured comes back as a structured error carrying enough metadata to reroute in
one step, rather than as a quietly weaker model.

The invocation built is:

```
codex exec --json --skip-git-repo-check -m <MODEL> -c model_reasoning_effort="<EFFORT>" \
           -s <read-only|workspace-write> -C <PROJECT> -o <tmpfile> --color never
```

with the prompt streamed over **stdin**. Model, effort and sandbox are passed on
every call, so a run never inherits whatever happens to be set in
`~/.codex/config.toml`.

`analyze` and `review` use Codex's real `read-only` sandbox — writes are
genuinely prevented, not merely discouraged.

### `antigravity_run`

Same schema, with:

| Field | Type | Notes |
|---|---|---|
| `model` | `Gemini Flash 3.7` | **required** |
| `effort` | `light` / `medium` / `high` | optional, defaults to `medium` |

Logical names are resolved at run time against the live `agy models` list:

| Label | Effort | Resolves to (on this machine) |
|---|---|---|
| `Gemini Flash 3.7` | `light` | `gemini-3.7-flash-low` |
| `Gemini Flash 3.7` | `medium` | `gemini-3.7-flash-medium` |
| `Gemini Flash 3.7` | `high` | `gemini-3.7-flash-high` |

Antigravity encodes the reasoning tier in the model id, so `model` + `effort`
resolve to a single id and no separate `--effort` flag is sent — the two can
never disagree. Flash has only these three tiers: `extra high` and `max` return
`invalid_effort` rather than quietly running a weaker tier than you asked for.

Antigravity is intentionally scoped to the cheap Gemini tier. The Claude models
that `agy` also offers are **not** exposed as routable labels — for Claude-class
reasoning, use Claude Code itself or Codex, which have real sandboxing and
(for Codex) selectable effort.

The label has an ordered candidate list of ids for the **same** model. If none
of them are offered by the installed CLI, the call fails with
`requested_model_unavailable` and the full available-model list. **It never
silently falls back to a different model.** The resolved id is echoed back in
`warnings` on every run.

The invocation built is:

```
agy --print <PROMPT> --model <RESOLVED_ID> --output-format json \
    --mode <plan|accept-edits> --add-dir <PROJECT> \
    --dangerously-skip-permissions --print-timeout <N>s
```

`--disable-slash-commands` is added only for `implement` runs: the CLI ignores
`--mode plan` whenever slash-command expansion is disabled, so sending both would
silently discard the only no-write behaviour Antigravity offers.

### `bridge_status`

No arguments. Consumes **no model inference** — it only runs `--version` probes,
`agy models`, one `git rev-parse`, and a read of the Codex CLI's model cache.

Returns:

- AgentBridge version, project directory (and how it was resolved), node/platform
- `git_verification` — whether git-based `files_changed` / `scope_violations` will
  work here at all. When it will not, an empty `scope_violations` is silence, not
  a clean bill of health, and the warnings say so
- `codex.installed` / `version` / `path` / `auth`
- `codex.model_status` — per model: `available` | `unavailable` | `unverified`,
  the AgentBridge effort labels it accepts, and the CLI's raw reasoning levels
- `codex.model_source` — where availability came from and how fresh it is
- `antigravity.installed` / `version` / `path` / `auth`, detected models, how each
  logical label + tier resolved, and `model_status` in the same three-state shape
- `antigravity.unsupported_efforts` — the orchestrator labels Flash cannot take
- active jobs and their write scopes
- warnings

Availability is never guessed. When the installed CLI offers no cheap proof, the
status is `unverified` rather than a claim in either direction.

It never returns credentials or environment variables.

---

## Result format

```json
{
  "status": "success",
  "provider": "codex",
  "model": "gpt-5.6-luna",
  "effort": "high",
  "mode": "implement",
  "duration_ms": 12345,
  "exit_code": 0,
  "summary": "...",
  "files_changed": ["src/upload.ts"],
  "scope_violations": [],
  "tests_or_checks_run": ["npm test -- upload"],
  "test_results": "12 passed",
  "concerns_or_blockers": [],
  "stderr_tail": "",
  "warnings": []
}
```

`effort` is present for both providers — the Codex reasoning level, or the Flash tier the model id encodes. `files_changed` and `scope_violations` are
computed by AgentBridge from git, not taken from the worker's word. Internal
reasoning is dropped. `summary` is capped at 16 000 characters, keeping the head
and tail and setting `summary_truncated: true` when it clips.

### Errors

Failures return the same envelope shape with `status: "failed"`, an `error`
category, an actionable `message`, and — where available — `exit_code`,
`stderr_tail`, and a `details` object.

| Category | Meaning |
|---|---|
| `executable_not_found` | `codex` / `agy` not on `PATH` |
| `authentication_required` | the CLI reported a login problem — sign in again yourself |
| `requested_model_unavailable` | the requested model is not offered; no substitution was made |
| `invalid_effort` | effort label outside the five supported values |
| `invalid_arguments` | e.g. `implement` mode without `allowed_paths` |
| `scope_conflict` | another active worker already owns overlapping write paths |
| `scope_violation` | a supplied path escaped the project root, or the worker wrote outside its scope |
| `timeout` | the worker overran; its process tree was terminated |
| `process_failed` | non-zero exit, or a non-`SUCCESS` CLI envelope |
| `output_parse_failed` | the CLI's machine-readable output could not be parsed |

---

## Examples of Claude calling the workers

Cheap, fast analysis:

```json
{ "tool": "codex_run",
  "goal": "Explain how session refresh works and where it can race.",
  "mode": "analyze", "model": "gpt-5.6-luna", "effort": "light",
  "relevant_files": ["src/auth/session.ts"] }
```

Hard implementation, maximum reasoning, tightly scoped:

```json
{ "tool": "codex_run",
  "goal": "Make the uploader retry 502s with exponential backoff.",
  "mode": "implement", "model": "gpt-5.6-sol", "effort": "max",
  "allowed_paths": ["src/upload.ts", "tests/upload.test.ts"],
  "no_touch": ["src/auth"],
  "contract": "export function upload(f: File): Promise<Result>",
  "acceptance_criteria": ["Retries up to 3 times", "Existing callers unchanged"],
  "tests": ["npm test -- upload"] }
```

Two Codex workers on disjoint scopes — these run **concurrently**:

```json
{ "tool": "codex_run", "mode": "implement", "model": "gpt-5.6-sol",
  "effort": "high", "allowed_paths": ["src/api"], "goal": "..." }
{ "tool": "codex_run", "mode": "implement", "model": "gpt-5.6-luna",
  "effort": "medium", "allowed_paths": ["src/ui"], "goal": "..." }
```

Antigravity for cheap mechanical work:

```json
{ "tool": "antigravity_run", "goal": "Summarise every exported symbol in src/lib.",
  "mode": "analyze", "model": "Gemini Flash 3.7", "effort": "light" }

{ "tool": "antigravity_run", "goal": "Build the settings page from design.png.",
  "mode": "implement", "model": "Gemini Flash 3.7", "effort": "high",
  "allowed_paths": ["src/pages/settings"] }
```

---

## Concurrency and file scope

- `implement` **requires** `allowed_paths`. Paths are normalised against the
  project root; anything escaping it (`..`, other drives, absolute paths
  elsewhere) is rejected before a process starts.
- Active `implement` jobs hold their write scopes in memory. A new job whose
  scopes overlap an active one is refused with `scope_conflict`. **Disjoint
  scopes run in parallel** — unrelated work is never serialised.
- `analyze` and `review` reserve nothing: they never block, and are never
  blocked.

### V1 limitation: detection, not isolation

File-scope protection **detects and reports** violations; it does not sandbox
each worker into its own tree. A Codex `implement` worker runs with
`workspace-write` across the project, so it *can* write outside its
`allowed_paths` — AgentBridge will list every such file in `scope_violations`,
downgrade the status from `success`, and tell you plainly.

**Nothing is ever reverted automatically.** Reverting a file that was already
modified before the worker started would destroy your (or Claude's) existing
work. Detection plus honest reporting is the V1 contract; git-worktree isolation
is deliberately out of scope.

Attribution is careful about pre-existing state: files that were already dirty
before a run and are byte-identical afterwards are never blamed on the worker.
Comparison uses porcelain status plus content hashing, and committed files are
picked up via a HEAD-move diff.

---

## Security

- Processes are spawned with **argument arrays and `shell: false`** — never an
  interpolated command string. Shell metacharacters in arguments stay literal.
- Windows `.cmd`/`.ps1` launchers (which Node refuses to spawn without a shell)
  are resolved to their real Node entry script and run as `node <script>`, so
  `shell: true` is never needed.
- Prompts go to Codex over **stdin**; oversized Antigravity prompts are written
  to a temp file and referenced by path. Neither can hit the Windows
  32 767-character command-line limit.
- `tests` is *context for the worker*. AgentBridge never executes it, and there
  is no MCP argument anywhere that becomes a command AgentBridge runs.
- Path traversal outside the project root is rejected.
- Timeouts kill the **whole process tree**: `taskkill /T /F` on Windows, and on
  POSIX a SIGTERM to the process group escalating to SIGKILL after a 3-second
  grace period. The escalation deliberately survives the direct child's exit,
  since that is exactly when descendants may still be running. This is
  best-effort — Node exposes no Windows Job Object, so if `taskkill` itself
  cannot start, only the direct child can be reached.
- Output buffers are bounded (8 MB per stream) and UTF-8 is decoded only after
  reassembly, so multi-byte characters are never split.
- Logs record job metadata only. Never tokens, never the environment, and
  prompts/outputs only when `AGENTBRIDGE_DEBUG=1`.
- **What AgentBridge does not protect you from:** the worker is a real coding
  agent running with your permissions. In `implement` mode it can run repository
  commands through its own tooling. Scope your `allowed_paths` accordingly.


---

## Verification

AgentBridge runs the project's own checks itself after a worker exits, outside
any sandbox, and returns the real output in `verification`:

```json
"verification": [
  { "command": "npm run typecheck", "ok": true,  "exit_code": 0, "timed_out": false, "duration_ms": 4120, "output_tail": "..." },
  { "command": "npm run test",      "ok": false, "exit_code": 1, "timed_out": false, "duration_ms": 8830, "output_tail": "..." }
]
```

- `verify_commands` — run exactly these, in any mode.
- Omitted in implement mode — AgentBridge reads `package.json` and runs the
  `typecheck` and `test` scripts if they exist. It never runs `build`, `dev`, or
  `start` on its own initiative; ask for those explicitly.
- `skip_verification: true` — run nothing, and rely on the worker's account.

Commands run without a shell, so `|`, `&&`, `;`, `>` and backticks outside
quotes are refused rather than half-executed. A failing check sets
`error: "verification_failed"` and downgrades a `success` to `partial`, so a
worker claiming green tests cannot outrank the tests themselves.

`tests_or_checks_run` and `test_results` report what AgentBridge actually ran
whenever verification happened; they fall back to the worker's own claims only
when it did not.

Because the commands come from the repository, a brief that names the wrong
runner cannot make AgentBridge run it: a repo whose test script is
`node --import tsx --test` is checked with that, whatever the brief said.

---

## Environment variables

| Variable | Purpose |
|---|---|
| `AGENTBRIDGE_PROJECT_DIR` | project directory; host-neutral, preferred |
| `CLAUDE_PROJECT_DIR` | legacy name, still honoured; set by Claude Code |
| `AGENTBRIDGE_DEBUG=1` | log sanitized prompts and outputs |
| `AGENTBRIDGE_LOG_DIR` | log location (default `~/.agentbridge/logs`) |
| `AGENTBRIDGE_CODEX_BIN` | explicit path to the Codex CLI |
| `AGENTBRIDGE_AGY_BIN` | explicit path to the `agy` CLI |
| `AGENTBRIDGE_CODEX_SANDBOX` | pin the Codex sandbox policy: `read-only`, `workspace-write`, `danger-full-access`, or `auto` (default) |
| `AGENTBRIDGE_CODEX_WINDOWS_SANDBOX` | Windows sandbox backend (default `unelevated`) |

`AGENTBRIDGE_CODEX_SANDBOX=auto` uses `read-only` for analyze/review and
`workspace-write` for implement — except that on Windows a sandboxed Codex
worker cannot capture a subprocess's output (`spawn EPERM` on piped stdio),
which is what npm scripts, test runners, and bundlers all do — so it can read
and edit but can never run a test runner, type checker, or build. That is a Codex
limitation with no configuration knob, so implement jobs fall back to
`danger-full-access` once a one-shot probe confirms it, and say so in
`warnings`. Analyze and review keep their enforced `read-only` boundary. On Windows, if a run fails with the broken
sandbox helper (`helper_unknown_error: apply deny-read ACLs`), AgentBridge
retries that job once with `danger-full-access`, remembers the verdict for the
rest of the process, and attaches a warning to every affected result saying the
boundary was not enforced. Nothing else triggers that fallback, and restarting
AgentBridge re-tries the real sandbox — so a fixed Codex release restores it
automatically. macOS and Linux never probe, retry, or fall back.

Logs are one JSON line per job: timestamp, job id, provider, model, effort,
mode, project, duration, exit code, error category.

---

## Troubleshooting

**`executable_not_found`** — the CLI is not on the `PATH` that Claude Code
exports to child processes. Confirm with `codex --version` / `agy --version` in
the same shell, or set `AGENTBRIDGE_CODEX_BIN` / `AGENTBRIDGE_AGY_BIN`.

**`authentication_required`** — your CLI session lapsed. Fix it in a terminal:
`codex login` for Codex, or sign in again with `agy`. AgentBridge deliberately
has no way to repair this: it does not touch credentials.

**`requested_model_unavailable`** — the model list changed, or the installed CLI
never offered that model. Run `bridge_status` (or `agy models`) to see what is
actually offered; the error's `details` already carry it. AgentBridge fails here on
purpose rather than quietly running a different model. If a provider renamed a
model id, add the new id to that label's candidate list in
`src/models/antigravity.ts` (or to `CODEX_MODELS` in `src/models/codex.ts`),
then rebuild.

**`output_parse_failed`** — the CLI changed its machine-readable output shape.
Check `details.stdout_head` in the result, then compare against
`parseCodexEvents` / `parseAgyOutput`.

**After a CLI update** — re-run the tests, then the live smoke check:

```bash
SMOKE_LIVE=1 node scripts/smoke.mjs
```

That drives a real MCP session and runs the cheapest configuration of each
worker. Without `SMOKE_LIVE=1` it only performs the handshake and
`bridge_status`, consuming nothing.

**`files_changed` is always empty** — the project directory is not inside a git
working tree. The warning in the result says so. Change verification needs git.

**Nothing appears under `/mcp`** — check the registered path points at
`dist/index.js` (built, not `src/`), and restart Claude Code after a rebuild.

---

## Layout

```
src/
  index.ts               stdio entry point
  server.ts              MCP server, tool schemas, dispatch
  config.ts              project-dir resolution, timeout clamping
  logging.ts             JSONL job log (stderr only, never stdout)
  parse.ts               worker-envelope parsing, summary capping
  types.ts
  cli/
    resolve.ts           PATH lookup + Windows shim unwrapping
    detect.ts            version probes, agy model listing, auth heuristics
    codex-catalogue.ts   zero-inference Codex model availability + effort capability
    codex-sandbox.ts     sandbox policy decision and helper-failure detection
  models/
    codex.ts             effort map, argv construction
    antigravity.ts       logical→real model resolution, argv construction
  process/
    runner.ts            shell-free spawn, bounded output, tree kill
  prompts/
    worker-prompt.ts     structured request → worker prompt
  scope/
    paths.ts             normalisation, traversal rejection, overlap
    locks.ts             in-memory write-scope registry
    git-state.ts         snapshot/diff file attribution
  tools/
    codex.ts  antigravity.ts  status.ts  common.ts
tests/                   252 tests, mocked processes, no quota used
scripts/smoke.mjs        real MCP end-to-end check
```

TDQS

A4.1/5.0

Scored across 3 tools

Disambiguation5/5

Each tool has a clearly distinct purpose: codex_run and antigravity_run target different external CLIs, and bridge_status is the health/status reporter. There is no meaningful overlap or ambiguity between them.

Naming Consistency4/5

The two worker tools follow a consistent <cli>_run pattern, while bridge_status deviates by using a noun instead of a verb. This is a minor inconsistency in an otherwise predictable naming scheme.

Tool Count5/5

Three tools is well-scoped for a bridge server: one for each external worker and one for status. Every tool earns its place and the count feels appropriate for the server's purpose.

Completeness4/5

The core workflows of delegating to either CLI and checking bridge health are covered. Minor gaps exist around job lifecycle management (e.g., listing or canceling active jobs), but these are not critical to the stated purpose.

Maintenance

ActivitySlowing
ResponsivenessNo issues