Skip to content

Commit 17f9425

Browse files
authored
feat(acp): added codex-acp support (#105)
## What <!-- Brief description of the changes --> - Adds `codex-acp` support and adds a blueprint to the example builds - Switches existing demos to use `agent_mounts` instead of blueprints for every example. - A few extra type guards and minor fixes ## Why <!-- Motivation and context --> ## PR title format <!-- PR titles must follow Conventional Commits: <type>(<scope>): <description> Types: feat | fix | docs | style | refactor | perf | test | build | ci | chore | revert Scopes: sdk | acp | claude | examples | deps | project Examples: feat(sdk): add reconnect support fix(acp): handle timeout on long-running tasks docs(claude): update connection examples deps(sdk): bump @runloop/api-client to 1.5.0 ci(project): add PR title validation workflow --> ## Checklist - [x] PR title follows `<type>(<scope>): <description>` format (see above) - [x] `bun run check` passes (lint + format) - [x] `bun run build` passes - [x] `bun run test` passes - [ ] SDK documentation updated (if applicable)
1 parent 443d927 commit 17f9425

45 files changed

Lines changed: 905 additions & 149 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,18 +280,41 @@ See the [SDK documentation](sdk/README.md#custom-events-via-publish-and-tryparse
280280

281281
See the [SDK documentation](sdk/README.md) for the full API reference, or browse the [hosted API docs](https://runloopai.github.io/agent-axon-client-ts/).
282282

283+
## Getting Agents onto the Devbox
284+
285+
There are two ways to ensure your agent binary is available on the devbox before execution starts:
286+
287+
- **Agent mounts (late-binding)** — Install the agent at devbox creation time via a mount. The agent lands on the box just before the broker mount connects it to Axon. This works with any standard Runloop image, so you can pick or customize the base environment independently from the agent.
288+
- **Blueprints (pre-baked)** — Bake the agent (and any other tooling) directly into a custom devbox image. Subsequent devbox creations skip the install step entirely, giving you the fastest cold-start and a reproducible, versioned environment.
289+
290+
The standalone examples (hello-world, CLI, combined-app) use a pre-baked **blueprint** for the fastest cold-start; the [feature-examples](examples/feature-examples/) default to **agent mounts** so they work with any standard image. Pick whichever fits your workflow — for example, blueprints when you want reproducible images, or mounts when you need to swap agent versions frequently or avoid maintaining custom images.
291+
283292
## Repository Structure
284293

285294
```
286295
sdk/ → @runloop/agent-axon-client (the published npm package)
287296
examples/
297+
blueprint/ → Builds the shared `axon-agents` blueprint (run this first)
288298
acp-hello-world/ → Minimal ACP single-prompt script
289299
acp-cli/ → Interactive ACP REPL
290300
claude-hello-world/ → Minimal Claude single-prompt script
291301
claude-cli/ → Interactive Claude REPL
292302
combined-app/ → Full-stack combined demo (Claude + ACP, Express + React)
303+
feature-examples/ → Runnable SDK recipes (single-prompt, elicitation, etc.)
293304
```
294305

306+
### Running the examples
307+
308+
> **Run the blueprint example first.** Every other example creates a devbox with `blueprint_name: "axon-agents"`. That blueprint is built by [`examples/blueprint`](examples/blueprint/) and must exist on your Runloop account before any other example will succeed.
309+
>
310+
> ```bash
311+
> bun install
312+
> bun run build
313+
> bun run build-blueprint # one-time — builds the axon-agents blueprint
314+
> ```
315+
>
316+
> After the blueprint reports `build_complete`, you can run any of the other examples. See [`examples/blueprint/README.md`](examples/blueprint/README.md) for details and [`examples/README.md`](examples/README.md) for the full example index.
317+
295318
## Development
296319
297320
**Prerequisites:**

bun.lock

Lines changed: 10 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@ Example applications and scripts showing how to use `@runloop/agent-axon-client`
88

99
| Directory | Description |
1010
|-----------|-------------|
11+
| [`blueprint`](blueprint/) | **Run this first.** Builds the shared `axon-agents` Runloop blueprint that every other example depends on |
1112
| [`acp-hello-world`](acp-hello-world/) | Minimal ACP agent script — the simplest possible starting point |
1213
| [`acp-cli`](acp-cli/) | Interactive REPL for ACP-compatible agents (e.g. OpenCode) |
1314
| [`claude-hello-world`](claude-hello-world/) | Minimal Claude agent script — the simplest possible starting point |
1415
| [`claude-cli`](claude-cli/) | Interactive REPL for Claude Code agents |
1516
| [`combined-app`](combined-app/) | Full-stack combined demo (Claude + ACP) with a unified React UI |
17+
| [`feature-examples`](feature-examples/) | Runnable SDK recipes (single-prompt, elicitation, etc.) |
1618

1719
## Prerequisites
1820

@@ -30,6 +32,23 @@ bun install
3032
bun run build
3133
```
3234

35+
### Build the shared blueprint (required, one-time)
36+
37+
All examples create devboxes with `blueprint_name: "axon-agents"`. That blueprint is produced by the [`blueprint`](blueprint/) example and must exist on your Runloop account before any other example will succeed — otherwise devbox creation will fail.
38+
39+
From the monorepo root:
40+
41+
```bash
42+
export RUNLOOP_API_KEY=your_key
43+
bun run build-blueprint
44+
```
45+
46+
Wait for the script to print `Blueprint build complete.`. You only need to do this once per account; re-run it to pick up changes to [`blueprint/Dockerfile`](blueprint/Dockerfile).
47+
48+
See [`blueprint/README.md`](blueprint/README.md) for more detail, including what's baked into the image and alternatives to using a blueprint.
49+
50+
### Run an example
51+
3352
Each example reads `RUNLOOP_API_KEY` (and `ANTHROPIC_API_KEY` where needed) from the environment. Export them, or add a `.env` file in the example directory.
3453

3554
See each example's own README for specific run instructions.

examples/acp-cli/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,13 @@ bun install && bun run build
1717

1818
# Set your API key
1919
export RUNLOOP_API_KEY=your_key
20+
21+
# Build the shared `axon-agents` blueprint (one-time, required)
22+
bun run build-blueprint
2023
```
2124

25+
> This example creates a devbox with `blueprint_name: "axon-agents"`. The blueprint must be built once on your Runloop account before this example will work — otherwise `devbox.create()` will fail. See [`../blueprint`](../blueprint/) for details.
26+
2227
## Running
2328

2429
```bash

examples/acp-cli/acp-cli.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@
44
* Creates a Runloop Devbox + Axon session, connects via the ACP protocol,
55
* and runs a REPL that streams session updates in real time.
66
*
7+
* Prerequisites:
8+
* - RUNLOOP_API_KEY must be set
9+
* - The shared `axon-agents` blueprint must exist on your Runloop account.
10+
* From the repo root, run `bun run build-blueprint` once before running
11+
* this example. See examples/blueprint/README.md for details.
12+
*
713
* Usage:
814
* bun run acp-cli.ts
915
* bun run acp-cli.ts --agent opencode
@@ -46,14 +52,14 @@ const AGENT_BINARY = args.agent ?? "opencode";
4652
const sdk = new RunloopSDK();
4753

4854
console.log(`Starting devbox with agent "${AGENT_BINARY}"...`);
49-
// The runloop/agents blueprint has opencode pre-installed. The broker_mount
55+
// The axon-agents blueprint has agents pre-installed. The broker_mount
5056
// wires the Axon channel to the agent binary via the ACP protocol — the
5157
// broker launches the agent inside the devbox and bridges stdin/stdout to
5258
// the Axon event stream.
5359
const axon = await sdk.axon.create({ name: "acp-transport" });
5460
const devbox = await sdk.devbox.create({
5561
name: "acp-cli",
56-
blueprint_name: "runloop/agents",
62+
blueprint_name: "axon-agents",
5763
mounts: [
5864
{
5965
type: "broker_mount",

examples/acp-hello-world/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,13 @@ bun install && bun run build
1919

2020
# Set your API key
2121
export RUNLOOP_API_KEY=your_key
22+
23+
# Build the shared `axon-agents` blueprint (one-time, required)
24+
bun run build-blueprint
2225
```
2326

27+
> This example creates a devbox with `blueprint_name: "axon-agents"`. The blueprint must be built once on your Runloop account before this example will work — otherwise `devbox.create()` will fail. See [`../blueprint`](../blueprint/) for details.
28+
2429
## Running
2530

2631
```bash

examples/acp-hello-world/acp-hello-world.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@
44
* Creates a Runloop Devbox, connects via the ACP protocol,
55
* sends a single prompt, prints the response, and exits.
66
*
7+
* Prerequisites:
8+
* - RUNLOOP_API_KEY must be set
9+
* - The shared `axon-agents` blueprint must exist on your Runloop account.
10+
* From the repo root, run `bun run build-blueprint` once before running
11+
* this example. See examples/blueprint/README.md for details.
12+
*
713
* Usage:
814
* bun run acp-hello-world.ts
915
* bun run acp-hello-world.ts --agent opencode
@@ -34,14 +40,14 @@ const AGENT_BINARY = args.agent ?? "opencode";
3440
const sdk = new RunloopSDK();
3541

3642
console.log(`Starting devbox with agent "${AGENT_BINARY}"...`);
37-
// The runloop/agents blueprint has opencode pre-installed. The broker_mount
43+
// The axon-agents blueprint has agents pre-installed. The broker_mount
3844
// wires the Axon channel to the agent binary via the ACP protocol — the
3945
// broker launches the agent inside the devbox and bridges stdin/stdout to
4046
// the Axon event stream.
4147
const axon = await sdk.axon.create({ name: "acp-transport" });
4248
const devbox = await sdk.devbox.create({
4349
name: "acp-hello-world",
44-
blueprint_name: "runloop/agents",
50+
blueprint_name: "axon-agents",
4551
mounts: [
4652
{
4753
type: "broker_mount",

examples/blueprint/Dockerfile

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
FROM runloop:runloop/starter-x86_64
2+
3+
RUN npm install -g @zed-industries/codex-acp
4+
5+
USER user
6+
WORKDIR /home/user
7+
8+
RUN curl -fsSL https://claude.ai/install.sh | bash
9+
RUN curl -fsSL https://opencode.ai/install | bash

examples/blueprint/README.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# @runloop/example-blueprint
2+
3+
> **Alpha — subject to change.** This example uses an SDK in early development. APIs and behavior may change without notice between versions.
4+
5+
Builds the shared `axon-agents` Runloop [blueprint](https://docs.runloop.ai/guides/blueprints) used by examples that demonstrate pre-baked agent images. The blueprint bakes the agent binaries (Claude Code, OpenCode, Codex ACP) into a devbox image so subsequent devboxes start quickly and reproducibly.
6+
7+
**You must run this once before any other example will work.** The other examples create devboxes with `blueprint_name: "axon-agents"` — if that blueprint does not exist on your Runloop account, devbox creation will fail.
8+
9+
## Prerequisites
10+
11+
- Node.js 22+ / [Bun](https://bun.sh)
12+
- A [Runloop](https://runloop.ai) API key (`RUNLOOP_API_KEY`)
13+
14+
## Running
15+
16+
From the monorepo root:
17+
18+
```bash
19+
bun install
20+
bun run build-blueprint
21+
```
22+
23+
Or directly from this directory:
24+
25+
```bash
26+
cd examples/blueprint
27+
bun run build-blueprint.ts
28+
```
29+
30+
The script builds the `axon-agents` blueprint and prints its status. A successful build ends with:
31+
32+
```
33+
Blueprint build complete.
34+
Use blueprint_name: "axon-agents" in devbox.create()
35+
```
36+
37+
You only need to do this once per Runloop account. Re-run it to update the image when the `Dockerfile` changes.
38+
39+
## What's in the blueprint
40+
41+
See [`Dockerfile`](Dockerfile) for the exact contents. At the time of writing it installs:
42+
43+
- [Claude Code](https://docs.claude.com/en/docs/claude-code) — for the Claude module examples
44+
- [OpenCode](https://opencode.ai) — for ACP examples using OpenCode
45+
- [Codex ACP](https://www.npmjs.com/package/@zed-industries/codex-acp) — for ACP examples using Codex
46+
47+
## Alternatives
48+
49+
Using a pre-baked blueprint is the fastest path, but not the only option. You can instead attach agents to any standard Runloop image as late-binding mounts. See [Getting Agents onto the Devbox](../../README.md#getting-agents-onto-the-devbox) in the root README for a comparison.
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/**
2+
* Build the shared agent blueprint for all examples.
3+
*
4+
* Usage:
5+
* bun run build-blueprint.ts
6+
*
7+
* Environment:
8+
* RUNLOOP_API_KEY - Required Runloop API key
9+
* RUNLOOP_BASE_URL - Optional API base URL override
10+
*/
11+
12+
import { RunloopSDK } from "@runloop/api-client";
13+
import { readFileSync } from "fs";
14+
import { dirname, join } from "path";
15+
import { fileURLToPath } from "url";
16+
17+
export const BLUEPRINT_NAME = "axon-agents";
18+
19+
const __dirname = dirname(fileURLToPath(import.meta.url));
20+
21+
async function main() {
22+
const apiKey = process.env.RUNLOOP_API_KEY;
23+
if (!apiKey) {
24+
console.error("RUNLOOP_API_KEY not set");
25+
process.exit(1);
26+
}
27+
28+
const baseUrl = process.env.RUNLOOP_BASE_URL;
29+
const sdk = new RunloopSDK({
30+
bearerToken: apiKey,
31+
...(baseUrl ? { baseURL: baseUrl } : {}),
32+
});
33+
34+
const dockerfilePath = join(__dirname, "Dockerfile");
35+
const dockerfile = readFileSync(dockerfilePath, "utf-8");
36+
37+
console.log(`Building blueprint "${BLUEPRINT_NAME}"...`);
38+
console.log("Dockerfile contents:");
39+
console.log("---");
40+
console.log(dockerfile);
41+
console.log("---");
42+
43+
const blueprint = await sdk.blueprint.create({
44+
name: BLUEPRINT_NAME,
45+
dockerfile,
46+
launch_parameters: {
47+
user_parameters: {
48+
uid: 1000,
49+
username: "user",
50+
},
51+
},
52+
});
53+
54+
console.log(`Blueprint created: ${blueprint.id}`);
55+
56+
// Get the final status
57+
const info = await blueprint.getInfo();
58+
console.log(`Name: ${info.name}`);
59+
console.log(`Status: ${info.status}`);
60+
61+
if (info.status === "build_complete") {
62+
console.log("\nBlueprint build complete.");
63+
console.log(`Use blueprint_name: "${BLUEPRINT_NAME}" in devbox.create()`);
64+
} else {
65+
console.error(`\nUnexpected blueprint status: ${info.status}`);
66+
const logs = await blueprint.logs();
67+
if (logs.logs.length > 0) {
68+
console.error("Build logs:");
69+
for (const entry of logs.logs) {
70+
console.error(`[${entry.level}] ${entry.message}`);
71+
}
72+
}
73+
process.exit(1);
74+
}
75+
}
76+
77+
main().catch((err) => {
78+
console.error("Error:", err);
79+
process.exit(1);
80+
});

0 commit comments

Comments
 (0)