Skip to content

Commit 649649b

Browse files
committed
chore: readme docs
1 parent a84a525 commit 649649b

1 file changed

Lines changed: 306 additions & 1 deletion

File tree

README.md

Lines changed: 306 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,306 @@
1-
# agent-container
1+
<h3 align="center">agent-container</h3>
2+
3+
<p align="center">
4+
Give your agents tiny boxes, powered by <a href="https://github.com/cloudflare/workerd">workerd</a><br />
5+
Agent Container is a workerd-based sandbox designed to give coding agents isolated execution environments, providing structured bindings (workspace, exec, env) scoped to a single repository rather than the host system. It's ideal for AI coding tools, agent frameworks, and platforms that need to run untrusted code with fine-grained capability control.
6+
</p>
7+
8+
<p align="center">
9+
<a href="#quick-start">Quick Start</a> &middot;
10+
<a href="#why">Why</a> &middot;
11+
<a href="#architecture">Architecture</a> &middot;
12+
<a href="#api">API</a> &middot;
13+
<a href="#development">Development</a>
14+
</p>
15+
16+
---
17+
18+
## Quick Start
19+
20+
```ts
21+
import { createAgentContainer } from "agent-container";
22+
23+
const container = await createAgentContainer({
24+
workspace: {
25+
root: process.cwd(),
26+
mode: "shadow", // disposable copy of your repo
27+
},
28+
env: {
29+
include: ["PUBLIC_*", "APP_*"],
30+
},
31+
exec: {
32+
allowedCommands: ["node", "git"],
33+
},
34+
});
35+
36+
await container.start();
37+
38+
const session = await container.createWorkerdSession();
39+
await session.start();
40+
41+
const { result } = await session.run({
42+
code: `
43+
const pkg = await WORKSPACE.readText("package.json");
44+
const { stdout } = await EXEC.run({ command: "node", args: ["--version"] });
45+
return { name: JSON.parse(pkg).name, node: stdout.trim() };
46+
`,
47+
});
48+
49+
console.log(result);
50+
// { name: "my-project", node: "v22.0.0" }
51+
52+
await session.stop();
53+
await container.stop();
54+
```
55+
56+
The code inside `workerd` cannot access `fs`, `process`, or `child_process` directly. It operates through explicit capability bindings that the host controls.
57+
58+
59+
## Why
60+
61+
Coding agents need to work inside real projects: reading files, writing code, running scripts, using environment variables. But giving an agent unrestricted access to your machine is dangerous, and dropping it into a fake environment breaks too many real-world workflows.
62+
63+
**agent-container** solves this by running agent code inside [workerd](https://github.com/cloudflare/workerd) while keeping all real authority in a Node.js host process. The agent gets a natural development experience. You keep control.
64+
65+
| Problem | Solution |
66+
|---------|----------|
67+
| Agent sees `~`, `.ssh`, unrelated directories | Workspace-scoped filesystem with explicit mounts |
68+
| Agent reads raw `.env` files | Env resolution with secret classification |
69+
| Agent spawns arbitrary processes | Allowlisted command execution with timeouts |
70+
| Agent has ambient network access | Controlled fetch with origin restrictions |
71+
| Hard to audit agent actions | Structured observability events |
72+
73+
74+
## Architecture
75+
76+
```
77+
┌─────────────────────────────────────────────────────────────────────────────┐
78+
│ HOST (Node.js) │
79+
│ │
80+
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
81+
│ │ Workspace │ │ Env │ │ Exec │ │
82+
│ │ Controller │ │ Resolver │ │ Controller │ │
83+
│ │ │ │ │ │ │ │
84+
│ │ • live/shadow │ │ • .env files │ │ • allowlist │ │
85+
│ │ • mounts │ │ • process.env │ │ • timeouts │ │
86+
│ │ • read/write │ │ • secrets │ │ • shell policy │ │
87+
│ └────────┬────────┘ └────────┬────────┘ └────────┬────────┘ │
88+
│ │ │ │ │
89+
│ └────────────────────┼────────────────────┘ │
90+
│ │ │
91+
│ ┌───────────▼───────────┐ │
92+
│ │ Capability Bridge │ │
93+
│ │ (localhost HTTP) │ │
94+
│ └───────────┬───────────┘ │
95+
└────────────────────────────────┼────────────────────────────────────────────┘
96+
97+
┌────────────────────────────────┼────────────────────────────────────────────┐
98+
│ GUEST (workerd) │ │
99+
│ ▼ │
100+
│ ┌──────────────────────────────────────────────────────────────────────┐ │
101+
│ │ Capability Bindings │ │
102+
│ │ │ │
103+
│ │ WORKSPACE EXEC ENV SECRETS OBSERVE │ │
104+
│ │ read/write run/shell get/keys get/keys emit │ │
105+
│ │ glob/grep (allowlist) (public) (secret) │ │
106+
│ │ list/stat │ │
107+
│ │ │ │
108+
│ └──────────────────────────────────────────────────────────────────────┘ │
109+
│ │
110+
│ No direct fs, process, or child_process access │
111+
└─────────────────────────────────────────────────────────────────────────────┘
112+
```
113+
114+
**Two runtimes, one boundary:**
115+
116+
- **Host (Node.js)** owns all real authority: filesystem, environment, subprocesses, network policy, observability
117+
- **Guest (workerd)** runs agent code with only the capabilities explicitly granted through bindings
118+
119+
The capability bridge is a session-local HTTP server that maps guest-side binding calls to host-side controllers. Each request is validated, policy-checked, and logged before execution.
120+
121+
122+
## Capabilities
123+
124+
### WORKSPACE
125+
126+
Scoped filesystem access with optional mounts.
127+
128+
```ts
129+
// Read and write files
130+
const content = await WORKSPACE.readText("src/index.ts");
131+
await WORKSPACE.writeText("output/result.json", JSON.stringify(data));
132+
133+
// Search
134+
const files = await WORKSPACE.glob("**/*.ts");
135+
const matches = await WORKSPACE.grep("TODO", { include: "**/*.ts" });
136+
137+
// Inspect
138+
const entries = await WORKSPACE.list("src");
139+
const info = await WORKSPACE.stat("package.json");
140+
```
141+
142+
**Modes:**
143+
- `live` — operate directly on the repo (default)
144+
- `shadow` — operate on a disposable copy
145+
146+
**Mounts:** attach additional paths with `ro` or `rw` access.
147+
148+
### EXEC
149+
150+
Controlled subprocess execution.
151+
152+
```ts
153+
const { stdout, exitCode } = await EXEC.run({
154+
command: "node",
155+
args: ["--version"],
156+
timeoutMs: 5000,
157+
});
158+
159+
// Shell execution (requires allowShell: true)
160+
const result = await EXEC.shell({
161+
script: "ls -la | head -5",
162+
});
163+
```
164+
165+
**Policy options:**
166+
- `allowedCommands` — whitelist of executables
167+
- `allowShell` — enable/disable shell scripts
168+
- `defaultTimeoutMs` — execution timeout
169+
170+
### ENV / SECRETS
171+
172+
Separated environment variable access.
173+
174+
```ts
175+
// Public config
176+
const apiUrl = await ENV.get("API_URL");
177+
const publicKeys = await ENV.keys();
178+
179+
// Secrets (classified by pattern matching)
180+
const apiKey = await SECRETS.get("API_KEY");
181+
```
182+
183+
**Classification:** variables matching patterns like `*_KEY`, `*_TOKEN`, `*_SECRET` are automatically classified as secrets and isolated from `ENV`.
184+
185+
### OBSERVE
186+
187+
Structured event emission for audit trails.
188+
189+
```ts
190+
await OBSERVE.emit({
191+
scope: "workspace",
192+
action: "custom-operation",
193+
outcome: "success",
194+
detail: "processed 42 files",
195+
});
196+
```
197+
198+
199+
## API
200+
201+
### createAgentContainer(options)
202+
203+
Creates a container with configured policies.
204+
205+
```ts
206+
interface AgentContainerOptions {
207+
workspace: {
208+
root: string;
209+
mode?: "live" | "shadow";
210+
mounts?: readonly {
211+
mountPath: string;
212+
sourcePath: string;
213+
mode: "ro" | "rw";
214+
}[];
215+
};
216+
env?: {
217+
sources?: readonly EnvSource[];
218+
include?: readonly string[];
219+
exclude?: readonly string[];
220+
secretPatterns?: readonly string[];
221+
processEnv?: "none" | "allow-matching" | "all";
222+
};
223+
exec?: {
224+
allowedCommands?: readonly string[];
225+
allowShell?: boolean;
226+
defaultTimeoutMs?: number;
227+
};
228+
network?: {
229+
allowFetch?: boolean;
230+
allowedFetchOrigins?: readonly string[];
231+
};
232+
observability?: {
233+
emit(event: ObservabilityEvent): void | Promise<void>;
234+
};
235+
}
236+
```
237+
238+
### container.createWorkerdSession(options?)
239+
240+
Creates a workerd session for running code.
241+
242+
```ts
243+
const session = await container.createWorkerdSession({
244+
startupTimeoutMs: 30000,
245+
compatibilityDate: "2026-01-01",
246+
});
247+
248+
await session.start();
249+
const { result, logs, durationMs } = await session.run({ code: "..." });
250+
await session.stop();
251+
```
252+
253+
### defineAgentContainerPlugin(options)
254+
255+
Defines a plugin for integration with agent harnesses.
256+
257+
```ts
258+
const plugin = defineAgentContainerPlugin({
259+
name: "my-agent",
260+
container: { workspace: { root: "." } },
261+
tools: {
262+
read: "WORKSPACE.readText",
263+
bash: "EXEC.run",
264+
},
265+
});
266+
```
267+
268+
269+
## Project Structure
270+
271+
```
272+
packages/
273+
├── agent-container/ # Core runtime
274+
├── types/ # Shared TypeScript types
275+
├── cli/ # CLI tools
276+
└── test-utils/ # Test helpers
277+
278+
apps/
279+
├── e2e/ # Integration tests
280+
├── playground/ # Development playground (WIP)
281+
└── docs/ # Documentation (WIP)
282+
```
283+
284+
285+
## Development
286+
287+
```sh
288+
pnpm install
289+
pnpm build
290+
pnpm typecheck
291+
pnpm test
292+
pnpm test:e2e
293+
```
294+
295+
### CLI (WIP)
296+
297+
```sh
298+
# Print container description for current directory
299+
agent-container describe
300+
```
301+
302+
---
303+
304+
## License
305+
306+
[Apache-2.0](LICENSE)

0 commit comments

Comments
 (0)