noumen 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/README.md +846 -51
  2. package/dist/a2a/index.d.ts +148 -0
  3. package/dist/a2a/index.js +579 -0
  4. package/dist/a2a/index.js.map +1 -0
  5. package/dist/acp/index.d.ts +129 -0
  6. package/dist/acp/index.js +498 -0
  7. package/dist/acp/index.js.map +1 -0
  8. package/dist/agent-1nFVUP9E.d.ts +1332 -0
  9. package/dist/cache-DsRqxx6v.d.ts +38 -0
  10. package/dist/chunk-3HEYCV26.js +10 -0
  11. package/dist/chunk-3HEYCV26.js.map +1 -0
  12. package/dist/chunk-3SK5GCI6.js +75 -0
  13. package/dist/chunk-3SK5GCI6.js.map +1 -0
  14. package/dist/chunk-42PHHZUA.js +132 -0
  15. package/dist/chunk-42PHHZUA.js.map +1 -0
  16. package/dist/chunk-4HW6LN6D.js +10365 -0
  17. package/dist/chunk-4HW6LN6D.js.map +1 -0
  18. package/dist/chunk-4SQA2UCV.js +26 -0
  19. package/dist/chunk-4SQA2UCV.js.map +1 -0
  20. package/dist/chunk-5GEX6ZSB.js +179 -0
  21. package/dist/chunk-5GEX6ZSB.js.map +1 -0
  22. package/dist/chunk-5JN4SPI7.js +94 -0
  23. package/dist/chunk-5JN4SPI7.js.map +1 -0
  24. package/dist/chunk-AMYIJSAZ.js +57 -0
  25. package/dist/chunk-AMYIJSAZ.js.map +1 -0
  26. package/dist/chunk-BZSFUEWM.js +43 -0
  27. package/dist/chunk-BZSFUEWM.js.map +1 -0
  28. package/dist/chunk-CS6WNDCF.js +171 -0
  29. package/dist/chunk-CS6WNDCF.js.map +1 -0
  30. package/dist/chunk-D43BWEZA.js +346 -0
  31. package/dist/chunk-D43BWEZA.js.map +1 -0
  32. package/dist/chunk-DGUM43GV.js +11 -0
  33. package/dist/chunk-DGUM43GV.js.map +1 -0
  34. package/dist/chunk-EKOGVTBT.js +472 -0
  35. package/dist/chunk-EKOGVTBT.js.map +1 -0
  36. package/dist/chunk-HEQQQGK5.js +131 -0
  37. package/dist/chunk-HEQQQGK5.js.map +1 -0
  38. package/dist/chunk-HL6JCRZJ.js +3112 -0
  39. package/dist/chunk-HL6JCRZJ.js.map +1 -0
  40. package/dist/chunk-JACGEMTF.js +43 -0
  41. package/dist/chunk-JACGEMTF.js.map +1 -0
  42. package/dist/chunk-JX7CLUCV.js +21 -0
  43. package/dist/chunk-JX7CLUCV.js.map +1 -0
  44. package/dist/chunk-KXDB56YW.js +39 -0
  45. package/dist/chunk-KXDB56YW.js.map +1 -0
  46. package/dist/chunk-L3L3FG5T.js +16 -0
  47. package/dist/chunk-L3L3FG5T.js.map +1 -0
  48. package/dist/chunk-OGXNFXFA.js +196 -0
  49. package/dist/chunk-OGXNFXFA.js.map +1 -0
  50. package/dist/chunk-UVSSQBDY.js +192 -0
  51. package/dist/chunk-UVSSQBDY.js.map +1 -0
  52. package/dist/chunk-Y45R3PQL.js +684 -0
  53. package/dist/chunk-Y45R3PQL.js.map +1 -0
  54. package/dist/cli/index.d.ts +1 -0
  55. package/dist/cli/index.js +874 -0
  56. package/dist/cli/index.js.map +1 -0
  57. package/dist/client/index.d.ts +64 -0
  58. package/dist/client/index.js +409 -0
  59. package/dist/client/index.js.map +1 -0
  60. package/dist/client-CRRO2376.js +10 -0
  61. package/dist/client-CRRO2376.js.map +1 -0
  62. package/dist/headless-FFU2DESQ.js +142 -0
  63. package/dist/headless-FFU2DESQ.js.map +1 -0
  64. package/dist/history-snip-64GYP4ZL.js +12 -0
  65. package/dist/history-snip-64GYP4ZL.js.map +1 -0
  66. package/dist/index.d.ts +1459 -422
  67. package/dist/index.js +398 -1757
  68. package/dist/index.js.map +1 -1
  69. package/dist/jsonrpc/index.d.ts +54 -0
  70. package/dist/jsonrpc/index.js +34 -0
  71. package/dist/jsonrpc/index.js.map +1 -0
  72. package/dist/lsp/index.d.ts +36 -0
  73. package/dist/lsp/index.js +16 -0
  74. package/dist/lsp/index.js.map +1 -0
  75. package/dist/lsp-PS3BWIHC.js +8 -0
  76. package/dist/lsp-PS3BWIHC.js.map +1 -0
  77. package/dist/manager-DLXK63XC.js +8 -0
  78. package/dist/manager-DLXK63XC.js.map +1 -0
  79. package/dist/mcp/index.d.ts +111 -0
  80. package/dist/mcp/index.js +105 -0
  81. package/dist/mcp/index.js.map +1 -0
  82. package/dist/mcp-auth-AEI2R4ZC.js +9 -0
  83. package/dist/mcp-auth-AEI2R4ZC.js.map +1 -0
  84. package/dist/provider-factory-KCLIF34X.js +20 -0
  85. package/dist/provider-factory-KCLIF34X.js.map +1 -0
  86. package/dist/providers/anthropic.d.ts +19 -0
  87. package/dist/providers/anthropic.js +35 -0
  88. package/dist/providers/anthropic.js.map +1 -0
  89. package/dist/providers/bedrock.d.ts +39 -0
  90. package/dist/providers/bedrock.js +56 -0
  91. package/dist/providers/bedrock.js.map +1 -0
  92. package/dist/providers/gemini.d.ts +17 -0
  93. package/dist/providers/gemini.js +262 -0
  94. package/dist/providers/gemini.js.map +1 -0
  95. package/dist/providers/ollama.d.ts +13 -0
  96. package/dist/providers/ollama.js +20 -0
  97. package/dist/providers/ollama.js.map +1 -0
  98. package/dist/providers/openai.d.ts +21 -0
  99. package/dist/providers/openai.js +9 -0
  100. package/dist/providers/openai.js.map +1 -0
  101. package/dist/providers/openrouter.d.ts +16 -0
  102. package/dist/providers/openrouter.js +24 -0
  103. package/dist/providers/openrouter.js.map +1 -0
  104. package/dist/providers/vertex.d.ts +42 -0
  105. package/dist/providers/vertex.js +67 -0
  106. package/dist/providers/vertex.js.map +1 -0
  107. package/dist/render-GRN4ZSSW.js +14 -0
  108. package/dist/render-GRN4ZSSW.js.map +1 -0
  109. package/dist/resolve-4JA2BBDA.js +14 -0
  110. package/dist/resolve-4JA2BBDA.js.map +1 -0
  111. package/dist/server/index.d.ts +143 -0
  112. package/dist/server/index.js +695 -0
  113. package/dist/server/index.js.map +1 -0
  114. package/dist/server-CHMxuWKq.d.ts +96 -0
  115. package/dist/spinner-OJNR6NFO.js +8 -0
  116. package/dist/spinner-OJNR6NFO.js.map +1 -0
  117. package/dist/types-2kTLUCnD.d.ts +107 -0
  118. package/dist/types-CD0rUKKT.d.ts +109 -0
  119. package/dist/types-LrU4LRmX.d.ts +575 -0
  120. package/dist/types-NIyVwQ4h.d.ts +109 -0
  121. package/dist/types-QwfylltH.d.ts +71 -0
  122. package/dist/types-RPKUTu1k.d.ts +645 -0
  123. package/dist/uuid-RVN2T26F.js +8 -0
  124. package/dist/uuid-RVN2T26F.js.map +1 -0
  125. package/dist/zod-7YXKWYMC.js +12 -0
  126. package/dist/zod-7YXKWYMC.js.map +1 -0
  127. package/package.json +141 -7
package/README.md CHANGED
@@ -1,8 +1,12 @@
1
- # noumen
1
+ # noumen 🐍
2
2
 
3
- Programmatic AI coding agent library with pluggable providers and virtual infrastructure.
3
+ The agent runtime you `npm install`.
4
4
 
5
- `noumen` gives you a headless, API-only coding agent that can read, write, edit files, run shell commands, and search codebasesall backed by swappable AI providers (OpenAI, Anthropic, Google Gemini) and virtual filesystems/computers (local Node.js, [sprites.dev](https://sprites.dev) containers).
5
+ `noumen` gives you the full agentic loop tool execution, file editing, shell commands, context compaction, and session managementwith sandboxed virtual infrastructure that isolates your agent from the host machine. Built for coding agents. Ready for any agent that uses a computer.
6
+
7
+ Any provider. Any sandbox. One package.
8
+
9
+ **[Documentation](https://noumen.dev)** · **[npm](https://www.npmjs.com/package/noumen)** · **[GitHub](https://github.com/UpstreetAI/noumen)**
6
10
 
7
11
  ## Install
8
12
 
@@ -10,25 +14,55 @@ Programmatic AI coding agent library with pluggable providers and virtual infras
10
14
  pnpm add noumen
11
15
  ```
12
16
 
17
+ Then install the provider SDK you need:
18
+
19
+ ```bash
20
+ pnpm add openai # for OpenAI / OpenRouter / Ollama
21
+ pnpm add @anthropic-ai/sdk # for Anthropic
22
+ pnpm add @google/genai # for Gemini
23
+ # Ollama requires no SDK — just install https://ollama.com
24
+ ```
25
+
13
26
  ## Quick Start
14
27
 
15
28
  ```typescript
16
- import {
17
- Code,
18
- OpenAIProvider,
19
- LocalFs,
20
- LocalComputer,
21
- } from "noumen";
29
+ import { Agent } from "noumen";
30
+
31
+ const agent = new Agent({ provider: "anthropic", cwd: "." });
32
+
33
+ for await (const event of agent.run("Add a health-check endpoint to server.ts")) {
34
+ if (event.type === "text_delta") process.stdout.write(event.text);
35
+ }
36
+ ```
37
+
38
+ Three lines to a working agent. The string provider auto-detects your `ANTHROPIC_API_KEY` from the environment, and `cwd` defaults to a local sandbox.
39
+
40
+ ### Execute (run to completion)
41
+
42
+ ```typescript
43
+ const result = await agent.execute("Fix the auth bug", {
44
+ onText: (text) => process.stdout.write(text),
45
+ onToolUse: (name) => console.log(`Using ${name}`),
46
+ });
47
+ console.log(`Done — ${result.toolCalls} tool calls`);
48
+ ```
49
+
50
+ `agent.run()` streams events via an async generator. `agent.execute()` runs to completion and returns a `RunResult` — callbacks are optional event listeners along the way.
22
51
 
23
- const code = new Code({
24
- aiProvider: new OpenAIProvider({ apiKey: process.env.OPENAI_API_KEY }),
25
- virtualFs: new LocalFs({ basePath: "/my/project" }),
26
- virtualComputer: new LocalComputer({ defaultCwd: "/my/project" }),
52
+ ### Full control
53
+
54
+ ```typescript
55
+ import { Agent, LocalSandbox } from "noumen";
56
+ import { OpenAIProvider } from "noumen/openai";
57
+
58
+ const agent = new Agent({
59
+ provider: new OpenAIProvider({ apiKey: process.env.OPENAI_API_KEY }),
60
+ sandbox: LocalSandbox({ cwd: "/my/project" }),
27
61
  });
28
62
 
29
- const thread = code.createThread();
63
+ const thread = agent.createThread();
30
64
 
31
- for await (const event of thread.run("Add a health-check endpoint to server.ts")) {
65
+ for await (const event of thread.run("Refactor the auth module")) {
32
66
  switch (event.type) {
33
67
  case "text_delta":
34
68
  process.stdout.write(event.text);
@@ -43,12 +77,182 @@ for await (const event of thread.run("Add a health-check endpoint to server.ts")
43
77
  }
44
78
  ```
45
79
 
80
+ ## Presets
81
+
82
+ For zero-config setup, use a preset that configures everything for you:
83
+
84
+ ```typescript
85
+ import { codingAgent } from "noumen";
86
+ import { OpenAIProvider } from "noumen/openai";
87
+
88
+ const agent = codingAgent({
89
+ provider: new OpenAIProvider({ apiKey: process.env.OPENAI_API_KEY! }),
90
+ cwd: "/my/project",
91
+ });
92
+
93
+ await agent.init();
94
+ const thread = agent.createThread();
95
+
96
+ for await (const event of thread.run("Refactor the auth module")) {
97
+ if (event.type === "text_delta") process.stdout.write(event.text);
98
+ }
99
+
100
+ await agent.close();
101
+ ```
102
+
103
+ Three presets are available:
104
+
105
+ | Preset | Mode | Includes |
106
+ |--------|------|----------|
107
+ | `codingAgent` | `default` | Subagents, tasks, plan mode, auto-compact, retry, cost tracking, project context |
108
+ | `planningAgent` | `plan` | Read-only exploration, plan mode enabled |
109
+ | `reviewAgent` | `plan` | Read-only + web search for documentation lookups |
110
+
111
+ ## CLI
112
+
113
+ noumen ships a CLI for using the agent directly from the terminal, with any provider.
114
+
115
+ ```bash
116
+ # Interactive mode — auto-detects provider from env vars
117
+ npx noumen
118
+
119
+ # One-shot with a specific provider
120
+ npx noumen -p anthropic "Add error handling to server.ts"
121
+
122
+ # Pipe input
123
+ cat plan.md | npx noumen -p openai
124
+
125
+ # JSONL output for scripting
126
+ npx noumen --json -c "List all TODO comments" > events.jsonl
127
+ ```
128
+
129
+ ### Setup
130
+
131
+ ```bash
132
+ noumen init
133
+ ```
134
+
135
+ This creates `.noumen/config.json` with your provider and model choice. The CLI also reads `NOUMEN.md` files for project instructions (see [Project Context](#project-context)).
136
+
137
+ ### Config file
138
+
139
+ ```json
140
+ {
141
+ "provider": "anthropic",
142
+ "model": "claude-sonnet-4",
143
+ "permissions": "acceptEdits"
144
+ }
145
+ ```
146
+
147
+ Place in `.noumen/config.json` at your project root. The CLI walks up from the working directory to find it.
148
+
149
+ ### Flags
150
+
151
+ | Flag | Description |
152
+ |------|-------------|
153
+ | `-p, --provider` | `openai`, `anthropic`, `gemini`, `openrouter`, `bedrock`, `vertex`, `ollama` |
154
+ | `-m, --model` | Model name (provider-specific default if omitted) |
155
+ | `--api-key` | Override API key |
156
+ | `--base-url` | Override provider base URL |
157
+ | `-c, --prompt` | One-shot prompt (non-interactive) |
158
+ | `--permission` | Permission mode: `default`, `plan`, `acceptEdits`, `auto`, `bypassPermissions`, `dontAsk` |
159
+ | `--thinking` | Thinking level: `off`, `low`, `medium`, `high` |
160
+ | `--max-turns` | Max agent turns before stopping |
161
+ | `--json` | Emit JSONL stream events to stdout |
162
+ | `--quiet` | Only output final text |
163
+ | `--verbose` | Show tool calls and thinking |
164
+ | `--cwd` | Working directory |
165
+
166
+ ### API key resolution
167
+
168
+ 1. `--api-key` flag
169
+ 2. Provider-specific env var (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GEMINI_API_KEY`, `OPENROUTER_API_KEY`)
170
+ 3. `NOUMEN_API_KEY` generic env var
171
+ 4. `.noumen/config.json` `apiKey` field
172
+
173
+ Ollama, Bedrock, and Vertex do not require an API key.
174
+
175
+ ### Commands
176
+
177
+ | Command | Description |
178
+ |---------|-------------|
179
+ | `noumen init` | Create `.noumen/config.json` |
180
+ | `noumen sessions` | List past sessions |
181
+ | `noumen resume <id>` | Resume a previous session (prefix match) |
182
+
183
+ ## Embedding
184
+
185
+ noumen is a library first. Six integration patterns:
186
+
187
+ **In-process** — `Agent` + `Thread.run()` async iterator, direct import:
188
+
189
+ ```typescript
190
+ const thread = agent.createThread();
191
+ for await (const event of thread.run("Fix the bug")) {
192
+ if (event.type === "text_delta") process.stdout.write(event.text);
193
+ }
194
+ ```
195
+
196
+ **HTTP/SSE server** — expose the agent over HTTP:
197
+
198
+ ```typescript
199
+ import { createServer } from "noumen/server";
200
+ const server = createServer(agent, { port: 3001, auth: { type: "bearer", token: "..." } });
201
+ await server.start();
202
+ ```
203
+
204
+ **Middleware** — mount on Express, Fastify, or Hono:
205
+
206
+ ```typescript
207
+ import { createRequestHandler } from "noumen/server";
208
+ app.use("/agent", createRequestHandler(agent, { auth: { type: "bearer", token: "..." } }));
209
+ ```
210
+
211
+ **WebSocket** — bidirectional with permission handling:
212
+
213
+ ```typescript
214
+ import { NoumenClient } from "noumen/client";
215
+ const client = new NoumenClient({ baseUrl: "http://localhost:3001", transport: "ws" });
216
+ for await (const event of client.run("Deploy to staging")) { /* ... */ }
217
+ ```
218
+
219
+ **Headless CLI** — NDJSON subprocess control from any language:
220
+
221
+ ```bash
222
+ npx noumen --headless -p anthropic <<< '{"type":"prompt","text":"Fix the bug"}'
223
+ ```
224
+
225
+ **Frameworks** — Next.js API routes, Electron IPC, VS Code extensions. See the [full embedding guide](https://noumen.dev/docs/embedding) and [Server API Reference](https://noumen.dev/docs/server-api).
226
+
227
+ **Health checks** — verify all integrations work before running:
228
+
229
+ ```typescript
230
+ const result = await agent.diagnose();
231
+ // {
232
+ // overall: true,
233
+ // provider: { ok: true, latencyMs: 342, model: "claude-sonnet-4" },
234
+ // sandbox: {
235
+ // fs: { ok: true, latencyMs: 2 },
236
+ // computer: { ok: true, latencyMs: 45 },
237
+ // },
238
+ // mcp: { filesystem: { ok: true, latencyMs: 0, status: "connected", toolCount: 5 } },
239
+ // lsp: {},
240
+ // timestamp: "2026-04-04T12:00:00.000Z",
241
+ // }
242
+ ```
243
+
244
+ Or from the CLI:
245
+
246
+ ```bash
247
+ npx noumen doctor
248
+ ```
249
+
46
250
  ## Providers
47
251
 
48
252
  ### OpenAI
49
253
 
50
254
  ```typescript
51
- import { OpenAIProvider } from "noumen";
255
+ import { OpenAIProvider } from "noumen/openai";
52
256
 
53
257
  const provider = new OpenAIProvider({
54
258
  apiKey: "sk-...",
@@ -60,18 +264,18 @@ const provider = new OpenAIProvider({
60
264
  ### Anthropic
61
265
 
62
266
  ```typescript
63
- import { AnthropicProvider } from "noumen";
267
+ import { AnthropicProvider } from "noumen/anthropic";
64
268
 
65
269
  const provider = new AnthropicProvider({
66
270
  apiKey: "sk-ant-...",
67
- model: "claude-sonnet-4-20250514", // default
271
+ model: "claude-sonnet-4", // default
68
272
  });
69
273
  ```
70
274
 
71
275
  ### Google Gemini
72
276
 
73
277
  ```typescript
74
- import { GeminiProvider } from "noumen";
278
+ import { GeminiProvider } from "noumen/gemini";
75
279
 
76
280
  const provider = new GeminiProvider({
77
281
  apiKey: "...", // Google AI Studio API key
@@ -79,54 +283,327 @@ const provider = new GeminiProvider({
79
283
  });
80
284
  ```
81
285
 
82
- ## Virtual Infrastructure
286
+ ### OpenRouter
287
+
288
+ ```typescript
289
+ import { OpenRouterProvider } from "noumen/openrouter";
290
+
291
+ const provider = new OpenRouterProvider({
292
+ apiKey: "sk-or-...",
293
+ model: "anthropic/claude-sonnet-4", // default
294
+ appName: "My Agent", // optional, for openrouter.ai rankings
295
+ appUrl: "https://myapp.com", // optional
296
+ });
297
+ ```
298
+
299
+ ### AWS Bedrock (Anthropic)
83
300
 
84
- ### Local (Node.js)
301
+ Route Anthropic models through AWS Bedrock. Requires `@anthropic-ai/bedrock-sdk`:
85
302
 
86
- Backed by `fs/promises` and `child_process`:
303
+ ```bash
304
+ pnpm add @anthropic-ai/bedrock-sdk
305
+ ```
87
306
 
88
307
  ```typescript
89
- import { LocalFs, LocalComputer } from "noumen";
308
+ import { BedrockAnthropicProvider } from "noumen/bedrock";
309
+
310
+ const provider = new BedrockAnthropicProvider({
311
+ region: "us-west-2", // default: us-east-1
312
+ model: "us.anthropic.claude-sonnet-4-v1:0", // default
313
+ credentials: { // optional, falls back to default chain
314
+ accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
315
+ secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
316
+ sessionToken: process.env.AWS_SESSION_TOKEN,
317
+ },
318
+ cacheControl: { enabled: true }, // optional prompt caching
319
+ });
320
+ ```
90
321
 
91
- const fs = new LocalFs({ basePath: "/my/project" });
92
- const computer = new LocalComputer({ defaultCwd: "/my/project" });
322
+ When `credentials` is omitted, the SDK uses the standard AWS credential chain (env vars, `~/.aws/credentials`, IAM roles, etc.).
323
+
324
+ ### Google Vertex AI (Anthropic)
325
+
326
+ Route Anthropic models through Google Cloud Vertex AI. Requires `@anthropic-ai/vertex-sdk` and `google-auth-library`:
327
+
328
+ ```bash
329
+ pnpm add @anthropic-ai/vertex-sdk google-auth-library
93
330
  ```
94
331
 
95
- ### sprites.dev
332
+ ```typescript
333
+ import { VertexAnthropicProvider } from "noumen/vertex";
96
334
 
97
- Run inside a remote [sprites.dev](https://docs.sprites.dev) container:
335
+ const provider = new VertexAnthropicProvider({
336
+ projectId: "my-gcp-project",
337
+ region: "us-east5", // default
338
+ model: "claude-sonnet-4", // default
339
+ cacheControl: { enabled: true }, // optional prompt caching
340
+ });
341
+ ```
342
+
343
+ When `googleAuth` is omitted, the provider creates a `GoogleAuth` instance using application default credentials. You can pass your own `googleAuth` instance for custom authentication:
98
344
 
99
345
  ```typescript
100
- import { SpritesFs, SpritesComputer } from "noumen";
346
+ import { GoogleAuth } from "google-auth-library";
101
347
 
102
- const fs = new SpritesFs({
103
- token: process.env.SPRITE_TOKEN,
104
- spriteName: "my-sprite",
348
+ const provider = new VertexAnthropicProvider({
349
+ projectId: "my-project",
350
+ googleAuth: new GoogleAuth({ keyFile: "/path/to/service-account.json" }),
105
351
  });
352
+ ```
353
+
354
+ ### Ollama (Local)
355
+
356
+ Run models locally with [Ollama](https://ollama.com). No API key needed — just install Ollama and pull a model:
357
+
358
+ ```bash
359
+ ollama pull qwen2.5-coder:32b
360
+ ollama serve
361
+ ```
362
+
363
+ ```typescript
364
+ import { OllamaProvider } from "noumen/ollama";
365
+
366
+ const provider = new OllamaProvider({
367
+ model: "qwen2.5-coder:32b", // default
368
+ baseURL: "http://localhost:11434/v1", // default
369
+ });
370
+ ```
371
+
372
+ The CLI auto-detects a running Ollama server when no cloud API keys are set, so you can simply run `noumen` with Ollama serving in the background.
373
+
374
+ ## Sandboxes
375
+
376
+ A `Sandbox` bundles a `VirtualFs` (filesystem) and `VirtualComputer` (shell execution) into one object. Every file read/write and shell command the agent executes goes through these interfaces — swap the sandbox to control what the agent can access.
377
+
378
+ ### Local — OS-level sandboxing
379
+
380
+ Backed by `@anthropic-ai/sandbox-runtime`. Uses macOS Seatbelt or Linux bubblewrap to restrict filesystem and network access at the OS level — no containers needed:
106
381
 
107
- const computer = new SpritesComputer({
382
+ ```bash
383
+ pnpm add @anthropic-ai/sandbox-runtime
384
+ ```
385
+
386
+ ```typescript
387
+ import { LocalSandbox } from "noumen";
388
+
389
+ const sandbox = LocalSandbox({ cwd: "/my/project" });
390
+
391
+ // Customize restrictions:
392
+ const restricted = LocalSandbox({
393
+ cwd: "/my/project",
394
+ sandbox: {
395
+ filesystem: { denyRead: ["/etc/shadow"] },
396
+ network: { allowedDomains: ["api.openai.com"] },
397
+ },
398
+ });
399
+ ```
400
+
401
+ Defaults: writes allowed only in `cwd`, reads allowed everywhere, network unrestricted.
402
+
403
+ ### UnsandboxedLocal — no isolation
404
+
405
+ Backed by `fs/promises` and `child_process` with no OS-level restrictions. Use for development or trusted environments:
406
+
407
+ ```typescript
408
+ import { UnsandboxedLocal } from "noumen";
409
+
410
+ const sandbox = UnsandboxedLocal({ cwd: "/my/project" });
411
+ ```
412
+
413
+ ### sprites.dev — full sandbox
414
+
415
+ Run inside a remote [sprites.dev](https://docs.sprites.dev) container. The agent has no access to the host machine.
416
+
417
+ **Auto-create** — omit `spriteName` and the sprite is provisioned on first use. The sandbox ID is persisted so sessions can reconnect on resume. `Agent.close()` tears the sprite down automatically:
418
+
419
+ ```typescript
420
+ import { SpritesSandbox } from "noumen";
421
+
422
+ const sandbox = SpritesSandbox({ token: process.env.SPRITE_TOKEN });
423
+ ```
424
+
425
+ **Explicit** — pass `spriteName` to attach to a pre-existing sprite. The caller owns the sprite's lifecycle:
426
+
427
+ ```typescript
428
+ const sandbox = SpritesSandbox({
108
429
  token: process.env.SPRITE_TOKEN,
109
430
  spriteName: "my-sprite",
110
431
  });
111
432
  ```
112
433
 
434
+ ### Docker — container isolation
435
+
436
+ Run the agent inside a Docker container. Requires `dockerode` as an optional peer dependency:
437
+
438
+ ```bash
439
+ pnpm add dockerode
440
+ ```
441
+
442
+ **Auto-create** — pass `image` instead of `container` and the container is created and started on first use. `Agent.close()` stops and removes it:
443
+
444
+ ```typescript
445
+ import { DockerSandbox } from "noumen";
446
+
447
+ const sandbox = DockerSandbox({ image: "node:22", cwd: "/workspace" });
448
+ const agent = new Agent({ provider, sandbox });
449
+
450
+ // Container auto-created on first thread. Cleaned up by:
451
+ await agent.close();
452
+ ```
453
+
454
+ **Explicit** — pass a pre-existing dockerode `Container`. The caller owns its lifecycle:
455
+
456
+ ```typescript
457
+ import Docker from "dockerode";
458
+ import { DockerSandbox } from "noumen";
459
+
460
+ const docker = new Docker();
461
+ const container = await docker.createContainer({
462
+ Image: "node:22",
463
+ Cmd: ["sleep", "infinity"],
464
+ Tty: false,
465
+ });
466
+ await container.start();
467
+
468
+ const sandbox = DockerSandbox({ container, cwd: "/workspace" });
469
+ const agent = new Agent({ provider, sandbox });
470
+
471
+ await container.stop();
472
+ await container.remove();
473
+ ```
474
+
475
+ ### E2B — cloud sandbox
476
+
477
+ Run the agent inside an [E2B](https://e2b.dev) cloud sandbox. Requires `e2b` as an optional peer dependency:
478
+
479
+ ```bash
480
+ pnpm add e2b
481
+ ```
482
+
483
+ **Auto-create** — omit `sandbox` and the E2B sandbox is provisioned on first use via the `e2b` SDK. `Agent.close()` kills it:
484
+
485
+ ```typescript
486
+ import { E2BSandbox } from "noumen";
487
+
488
+ const sandbox = E2BSandbox({ template: "base" });
489
+ const agent = new Agent({ provider, sandbox });
490
+
491
+ await agent.close(); // kills the E2B sandbox
492
+ ```
493
+
494
+ **Explicit** — pass a pre-existing `Sandbox` instance. The caller owns its lifecycle:
495
+
496
+ ```typescript
497
+ import { Sandbox as E2BSandboxSDK } from "e2b";
498
+ import { E2BSandbox } from "noumen";
499
+
500
+ const e2b = await E2BSandboxSDK.create();
501
+
502
+ const sandbox = E2BSandbox({
503
+ sandbox: e2b,
504
+ cwd: "/home/user",
505
+ });
506
+
507
+ const agent = new Agent({ provider, sandbox });
508
+
509
+ await e2b.close();
510
+ ```
511
+
512
+ ### Freestyle — cloud VMs
513
+
514
+ Run the agent inside a [Freestyle](https://freestyle.sh) VM. Full Linux VMs with sub-second startup, instant pause/resume, and optional forking. Requires `freestyle-sandboxes` as an optional peer dependency:
515
+
516
+ ```bash
517
+ pnpm add freestyle-sandboxes
518
+ ```
519
+
520
+ **Auto-create** — omit `vm` and a Freestyle VM is provisioned on first use. `Agent.close()` **suspends** (not deletes) the VM so it can resume instantly later:
521
+
522
+ ```typescript
523
+ import { FreestyleSandbox } from "noumen";
524
+
525
+ const sandbox = FreestyleSandbox({ cwd: "/workspace" });
526
+ const agent = new Agent({ provider, sandbox });
527
+
528
+ await agent.close(); // suspends the VM (preserves full memory state)
529
+ ```
530
+
531
+ **From a snapshot** — start from a cached environment:
532
+
533
+ ```typescript
534
+ const sandbox = FreestyleSandbox({
535
+ snapshotId: "abc123",
536
+ cwd: "/workspace",
537
+ });
538
+ ```
539
+
540
+ **Explicit** — pass a pre-existing VM instance. The caller owns its lifecycle:
541
+
542
+ ```typescript
543
+ import { freestyle } from "freestyle-sandboxes";
544
+ import { FreestyleSandbox } from "noumen";
545
+
546
+ const { vm } = await freestyle.vms.create({ workdir: "/workspace" });
547
+
548
+ const sandbox = FreestyleSandbox({ vm, cwd: "/workspace" });
549
+ const agent = new Agent({ provider, sandbox });
550
+ ```
551
+
552
+ ### Sandbox auto-creation lifecycle
553
+
554
+ All four remote backends (Sprites, Docker, E2B, Freestyle) support on-demand provisioning. When you omit the container/instance and let the factory auto-create:
555
+
556
+ 1. **First `createThread()`** calls `sandbox.init()` which provisions the resource
557
+ 2. The sandbox ID is persisted locally (`.noumen/sessions/.sandbox-index.json`) so `resumeThread()` can reconnect to the same resource
558
+ 3. **`Agent.close()`** calls `sandbox.dispose()` which tears down auto-created resources
559
+ 4. Resources created by the user (explicit IDs) are never torn down by `dispose()`
560
+
561
+ `init()` is idempotent — multiple `createThread()` calls reuse the same provisioned resource.
562
+
563
+ ### Custom sandboxes
564
+
565
+ Implement `VirtualFs` and `VirtualComputer` to target any execution environment — Daytona, cloud VMs, or an in-memory test harness. A custom `Sandbox` is any object with `{ fs, computer }`:
566
+
567
+ ```typescript
568
+ import type { Sandbox } from "noumen";
569
+
570
+ const sandbox: Sandbox = {
571
+ fs: new MyCustomFs(),
572
+ computer: new MyCustomComputer(),
573
+ // Optional lazy provisioning:
574
+ init: async (reconnectId) => { /* create or reconnect */ },
575
+ sandboxId: () => "my-resource-id",
576
+ dispose: async () => { /* tear down */ },
577
+ };
578
+ ```
579
+
580
+ The interfaces are intentionally minimal (one method for shell, eight for filesystem) so adapters are straightforward to write. The optional `init()`, `sandboxId()`, and `dispose()` methods enable auto-creation and session-aware lifecycle management.
581
+
113
582
  ## Options
114
583
 
115
584
  ```typescript
116
- const code = new Code({
117
- aiProvider,
118
- virtualFs,
119
- virtualComputer,
585
+ const agent = new Agent({
586
+ provider: "anthropic",
587
+ cwd: "/my/project",
120
588
  options: {
121
589
  sessionDir: ".noumen/sessions", // JSONL transcript storage path
122
- model: "gpt-4o", // default model
123
- maxTokens: 8192, // max output tokens per turn
124
- autoCompact: true, // auto-compact when context is large
125
- autoCompactThreshold: 100_000, // token threshold for auto-compact
126
- systemPrompt: "...", // override the built-in system prompt
127
- cwd: "/working/dir", // working directory for tools
590
+ model: "claude-sonnet-4", // default model
591
+ maxTokens: 8192, // max output tokens per turn
592
+ autoCompact: true, // auto-compact when context is large
593
+ autoCompactThreshold: 100_000, // token threshold for auto-compact
594
+ systemPrompt: "...", // override the built-in system prompt
128
595
  skills: [{ name: "...", content: "..." }],
129
- skillsPaths: [".claude/skills"], // paths to SKILL.md files on virtualFs
596
+ skillsPaths: [".claude/skills"], // paths to SKILL.md files on the sandbox filesystem
597
+ projectContext: true, // load NOUMEN.md / CLAUDE.md from project
598
+
599
+ // Extended thinking / reasoning (see below)
600
+ thinking: { type: "enabled", budgetTokens: 10000 },
601
+
602
+ // Retry / error resilience (see below)
603
+ retry: true, // use defaults, or pass a RetryConfig
604
+
605
+ // Cost tracking (see below)
606
+ costTracking: { enabled: true },
130
607
  },
131
608
  });
132
609
  ```
@@ -135,10 +612,10 @@ const code = new Code({
135
612
 
136
613
  ```typescript
137
614
  // New thread
138
- const thread = code.createThread();
615
+ const thread = agent.createThread();
139
616
 
140
617
  // Resume an existing session
141
- const thread = code.createThread({ sessionId: "abc-123", resume: true });
618
+ const thread = agent.createThread({ sessionId: "abc-123", resume: true });
142
619
 
143
620
  // Run a prompt (returns an async iterable of stream events)
144
621
  for await (const event of thread.run("Fix the failing test")) {
@@ -160,16 +637,45 @@ thread.abort();
160
637
  | Event | Fields | Description |
161
638
  |-------|--------|-------------|
162
639
  | `text_delta` | `text` | Incremental text from the model |
640
+ | `thinking_delta` | `text` | Incremental thinking/reasoning text from the model |
163
641
  | `tool_use_start` | `toolName`, `toolUseId` | Model is calling a tool |
164
642
  | `tool_use_delta` | `input` | Incremental tool call arguments |
165
643
  | `tool_result` | `toolUseId`, `toolName`, `result` | Tool execution result |
166
644
  | `message_complete` | `message` | Full assistant message |
645
+ | `usage` | `usage`, `model` | Token usage for a single model call |
646
+ | `cost_update` | `summary` | Updated cost summary after each model call |
647
+ | `turn_complete` | `usage`, `model`, `callCount` | Accumulated usage for the full agent turn |
648
+ | `retry_attempt` | `attempt`, `maxRetries`, `delayMs`, `error` | A retryable error occurred; waiting before retry |
649
+ | `retry_exhausted` | `attempts`, `error` | All retries exhausted |
167
650
  | `compact_start` | | Auto-compaction started |
168
651
  | `compact_complete` | | Auto-compaction finished |
652
+ | `microcompact_complete` | `tokensFreed` | Microcompaction freed tokens from tool results |
653
+ | `tool_result_truncated` | `toolCallId`, `originalChars`, `truncatedChars` | A tool result was truncated by the budget system |
654
+ | `permission_request` | `toolName`, `input`, `message` | Tool call requires user approval |
655
+ | `permission_granted` | `toolName`, `input` | Permission was granted for a tool call |
656
+ | `permission_denied` | `toolName`, `input`, `message` | Permission was denied for a tool call |
657
+ | `denial_limit_exceeded` | `consecutiveDenials`, `totalDenials` | Denial tracking limits hit |
658
+ | `user_input_request` | `toolUseId`, `question` | The agent is asking the user a question |
659
+ | `subagent_start` | `toolUseId`, `prompt` | A subagent is being spawned |
660
+ | `subagent_end` | `toolUseId`, `result` | A subagent finished |
661
+ | `session_resumed` | `sessionId`, `messageCount` | A previous session was restored |
662
+ | `checkpoint_snapshot` | `messageId` | A file checkpoint was taken before edits |
663
+ | `recovery_filtered` | `filterName`, `removedCount` | Corrupt entries were filtered during session restore |
664
+ | `interrupted_turn_detected` | `kind` | A previous turn was interrupted (`interrupted_tool` or `interrupted_prompt`) |
665
+ | `memory_update` | `created`, `updated`, `deleted` | Memories were extracted from the conversation |
666
+ | `span_start` | `name`, `spanId` | An OpenTelemetry-compatible span started |
667
+ | `span_end` | `name`, `spanId`, `durationMs`, `error?` | A span ended |
668
+ | `git_operation` | `operation`, `details` | A git operation was detected |
669
+ | `structured_output` | `data`, `schema` | Structured output was produced |
670
+ | `max_turns_reached` | `maxTurns`, `turnCount` | The agent hit the maxTurns limit |
169
671
  | `error` | `error` | An error occurred |
170
672
 
673
+ See **[noumen.dev/docs/stream-events](https://noumen.dev/docs/stream-events)** for the full event reference.
674
+
171
675
  ## Built-in Tools
172
676
 
677
+ ### Core tools (always available)
678
+
173
679
  | Tool | Description |
174
680
  |------|-------------|
175
681
  | **ReadFile** | Read files with line numbers, offset/limit support |
@@ -178,16 +684,148 @@ thread.abort();
178
684
  | **Bash** | Execute shell commands |
179
685
  | **Glob** | Find files by glob pattern (via ripgrep) |
180
686
  | **Grep** | Search file contents by regex (via ripgrep) |
687
+ | **WebFetch** | Fetch a URL and return contents as markdown |
688
+ | **NotebookEdit** | Edit Jupyter notebook cells (replace, insert, delete) |
689
+ | **AskUser** | Ask the user a question and wait for a response |
690
+
691
+ ### Optional tools (enabled via Agent options)
692
+
693
+ | Tool | Requires | Description |
694
+ |------|----------|-------------|
695
+ | **Agent** | `enableSubagents` | Spawn an isolated subagent for focused subtasks |
696
+ | **Skill** | `skills` / `skillsPaths` | Invoke a named skill with arguments |
697
+ | **TaskCreate** | `enableTasks` | Create a work item for tracking |
698
+ | **TaskList** | `enableTasks` | List all tasks with status |
699
+ | **TaskGet** | `enableTasks` | Get task details by ID |
700
+ | **TaskUpdate** | `enableTasks` | Update task status/description |
701
+ | **EnterPlanMode** | `enablePlanMode` | Switch to read-only exploration mode |
702
+ | **ExitPlanMode** | `enablePlanMode` | Return to normal mode with optional plan |
703
+ | **EnterWorktree** | `enableWorktrees` | Create an isolated git worktree |
704
+ | **ExitWorktree** | `enableWorktrees` | Leave and optionally clean up worktree |
705
+ | **LSP** | `lsp` config | Query language servers (definitions, references, hover) |
706
+ | **WebSearch** | `webSearch` config | Search the web via a user-provided backend |
707
+ | **ToolSearch** | `toolSearch` | Discover deferred tools on demand (reduces context usage) |
708
+
709
+ ## Extended Thinking
710
+
711
+ Enable model reasoning/thinking for supported providers. Each provider maps the config to its native format:
712
+
713
+ - **Anthropic**: Sets `thinking.budget_tokens` on the API call
714
+ - **OpenAI**: Maps to `reasoning_effort: "high"` for o-series models
715
+ - **Gemini**: Sets `thinkingConfig.thinkingBudget`
716
+
717
+ ```typescript
718
+ const agent = new Agent({
719
+ provider: "anthropic",
720
+ cwd: ".",
721
+ options: {
722
+ thinking: { type: "enabled", budgetTokens: 10000 },
723
+ },
724
+ });
725
+
726
+ for await (const event of thread.run("Solve this complex problem")) {
727
+ if (event.type === "thinking_delta") {
728
+ process.stderr.write(event.text); // reasoning trace
729
+ }
730
+ if (event.type === "text_delta") {
731
+ process.stdout.write(event.text); // final answer
732
+ }
733
+ }
734
+ ```
735
+
736
+ Disable explicitly with `{ type: "disabled" }`, or omit the option entirely for default behavior.
737
+
738
+ ## Retry / Error Resilience
739
+
740
+ Automatic retries with exponential backoff, Retry-After header support, context overflow recovery, and model fallback. Handles 429 (rate limit), 529 (overloaded), 500/502/503 (server errors), and connection failures.
741
+
742
+ ```typescript
743
+ const agent = new Agent({
744
+ provider: "anthropic",
745
+ cwd: ".",
746
+ options: {
747
+ retry: true, // use sensible defaults
748
+ },
749
+ });
750
+
751
+ // Or customize:
752
+ const agent2 = new Agent({
753
+ provider: "anthropic",
754
+ cwd: ".",
755
+ options: {
756
+ retry: {
757
+ maxRetries: 10,
758
+ baseDelayMs: 500,
759
+ maxDelayMs: 32000,
760
+ retryableStatuses: [408, 429, 500, 502, 503, 529],
761
+ fallbackModel: "gpt-4o-mini", // switch model after repeated 529s
762
+ maxConsecutiveOverloaded: 3,
763
+ onRetry: (attempt, error, delayMs) => {
764
+ console.log(`Retry ${attempt}, waiting ${delayMs}ms: ${error.message}`);
765
+ },
766
+ },
767
+ },
768
+ });
769
+ ```
770
+
771
+ On context overflow (input + max_tokens > context limit), the engine automatically reduces `max_tokens` and retries — no manual intervention needed.
772
+
773
+ ## Cost Tracking
774
+
775
+ Track token usage and estimate USD costs across all model calls. Includes built-in pricing for Claude, GPT-4o, Gemini, and o-series models.
776
+
777
+ ```typescript
778
+ const agent = new Agent({
779
+ provider: "anthropic",
780
+ cwd: ".",
781
+ options: {
782
+ costTracking: { enabled: true },
783
+ },
784
+ });
785
+
786
+ const thread = agent.createThread();
787
+
788
+ for await (const event of thread.run("Refactor the auth module")) {
789
+ if (event.type === "cost_update") {
790
+ console.log(`Running cost: $${event.summary.totalCostUSD.toFixed(4)}`);
791
+ }
792
+ }
793
+
794
+ // Or get the summary at any time
795
+ const summary = agent.getCostSummary();
796
+ console.log(`Total: $${summary.totalCostUSD.toFixed(4)}`);
797
+ console.log(`Input tokens: ${summary.totalInputTokens}`);
798
+ console.log(`Output tokens: ${summary.totalOutputTokens}`);
799
+ ```
800
+
801
+ Supply custom pricing for unlisted models:
802
+
803
+ ```typescript
804
+ const agent = new Agent({
805
+ provider: "anthropic",
806
+ cwd: ".",
807
+ options: {
808
+ costTracking: {
809
+ enabled: true,
810
+ pricing: {
811
+ "my-custom-model": {
812
+ inputTokens: 1, // USD per 1M tokens
813
+ outputTokens: 3,
814
+ },
815
+ },
816
+ },
817
+ },
818
+ });
819
+ ```
181
820
 
182
821
  ## Skills
183
822
 
184
823
  Skills are markdown instructions injected into the system prompt. Provide them inline or load from `SKILL.md` files on the virtual filesystem:
185
824
 
186
825
  ```typescript
187
- const code = new Code({
188
- aiProvider,
189
- virtualFs,
190
- virtualComputer,
826
+ const agent = new Agent({
827
+ provider: "anthropic",
828
+ cwd: ".",
191
829
  options: {
192
830
  skills: [
193
831
  { name: "Testing", content: "Always write vitest tests for new code." },
@@ -197,19 +835,176 @@ const code = new Code({
197
835
  });
198
836
 
199
837
  // If using skillsPaths, call init() to pre-load them
200
- await code.init();
838
+ await agent.init();
839
+ ```
840
+
841
+ ## Project Context (NOUMEN.md / CLAUDE.md)
842
+
843
+ Drop a `NOUMEN.md` or `CLAUDE.md` in your project root to give the agent persistent instructions:
844
+
845
+ ```markdown
846
+ # Project instructions
847
+
848
+ This is a TypeScript monorepo. Use strict mode. Write vitest tests for all new code.
201
849
  ```
202
850
 
851
+ Enable it with `projectContext: true` in your `Agent` options. The loader discovers context files from four layers — managed (enterprise), user (`~/.noumen/`), project (repo ancestors), and local (`.local.md`, gitignored) — so you can scope instructions at any level.
852
+
853
+ This is fully compatible with `CLAUDE.md`. If your project already has one, noumen picks it up automatically. Both `NOUMEN.md` and `CLAUDE.md` can coexist in the same directory. The format supports `@path` includes, conditional rules via `paths:` frontmatter in `.noumen/rules/` directories, and hierarchical overriding.
854
+
855
+ See **[noumen.dev/docs/context](https://noumen.dev/docs/context)** for full configuration options.
856
+
203
857
  ## Sessions
204
858
 
205
859
  Conversations are persisted as JSONL files on the virtual filesystem. Each line is a serialized message entry. Compaction writes a boundary marker followed by a summary, so resumed sessions only load post-boundary messages.
206
860
 
207
861
  ```typescript
208
862
  // List all saved sessions
209
- const sessions = await code.listSessions();
863
+ const sessions = await agent.listSessions();
210
864
  // [{ sessionId, createdAt, lastMessageAt, title?, messageCount }]
211
865
  ```
212
866
 
867
+ ## Hooks
868
+
869
+ 18 hook events across six categories — intercept tool calls, session lifecycle, permissions, file writes, model switches, compaction, retry, memory, and errors:
870
+
871
+ ```typescript
872
+ const agent = new Agent({
873
+ provider: "anthropic", cwd: ".",
874
+ options: {
875
+ hooks: [
876
+ {
877
+ event: "SessionStart",
878
+ handler: async (input) => {
879
+ console.log(`Session ${input.sessionId} started (resume: ${input.isResume})`);
880
+ },
881
+ },
882
+ {
883
+ event: "PreToolUse",
884
+ matcher: "Bash",
885
+ handler: async (input) => {
886
+ console.log(`Bash: ${input.toolInput.command}`);
887
+ return { decision: "allow" };
888
+ },
889
+ },
890
+ {
891
+ event: "FileWrite",
892
+ handler: async (input) => {
893
+ console.log(`${input.toolName} wrote ${input.filePath}`);
894
+ },
895
+ },
896
+ {
897
+ event: "PermissionDenied",
898
+ handler: async (input) => {
899
+ console.log(`Denied ${input.toolName}: ${input.reason}`);
900
+ },
901
+ },
902
+ ],
903
+ },
904
+ });
905
+ ```
906
+
907
+ | Category | Events |
908
+ |----------|--------|
909
+ | Session lifecycle | `SessionStart`, `SessionEnd`, `TurnStart`, `TurnEnd`, `Error` |
910
+ | Tool execution | `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `FileWrite` |
911
+ | Permissions | `PermissionRequest`, `PermissionDenied` |
912
+ | Subagents | `SubagentStart`, `SubagentStop` |
913
+ | Compaction | `PreCompact`, `PostCompact` |
914
+ | System | `ModelSwitch`, `RetryAttempt`, `MemoryUpdate` |
915
+
916
+ See the [hooks documentation](https://noumen.dev/docs/hooks) for full details on each event.
917
+
918
+ ## Permissions
919
+
920
+ Control what tools the agent can use with modes and rules:
921
+
922
+ ```typescript
923
+ options: {
924
+ permissions: {
925
+ mode: "default", // or "plan", "acceptEdits", "auto", "bypassPermissions", "dontAsk"
926
+ rules: [
927
+ { toolName: "Bash", behavior: "ask", source: "project" },
928
+ { toolName: "ReadFile", behavior: "allow", source: "user" },
929
+ ],
930
+ handler: async (request) => ({ allow: true }),
931
+ },
932
+ }
933
+ ```
934
+
935
+ ## Multi-Agent Swarm
936
+
937
+ Run multiple agents in parallel with message passing:
938
+
939
+ ```typescript
940
+ import { SwarmManager, InProcessBackend } from "noumen";
941
+
942
+ const backend = new InProcessBackend(agent);
943
+ const swarm = new SwarmManager(backend, { maxConcurrent: 3 });
944
+
945
+ await swarm.spawn({ name: "researcher", prompt: "Find all TODOs" });
946
+ await swarm.spawn({ name: "writer", prompt: "Write tests for auth" });
947
+ await swarm.waitForAll();
948
+ ```
949
+
950
+ ## Memory
951
+
952
+ Persist knowledge across sessions:
953
+
954
+ ```typescript
955
+ import { FileMemoryProvider, LocalFs } from "noumen";
956
+
957
+ options: {
958
+ memory: {
959
+ provider: new FileMemoryProvider(new LocalFs({ basePath: ".noumen/memory" })),
960
+ autoExtract: true,
961
+ injectIntoSystemPrompt: true,
962
+ },
963
+ }
964
+ ```
965
+
966
+ ## MCP (Model Context Protocol)
967
+
968
+ Connect to MCP servers to discover and use external tools:
969
+
970
+ ```typescript
971
+ options: {
972
+ mcpServers: {
973
+ filesystem: { command: "npx", args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"] },
974
+ remote: { type: "http", url: "http://localhost:3001/mcp" },
975
+ },
976
+ }
977
+ ```
978
+
979
+ Or expose noumen's tools as an MCP server (requires `@modelcontextprotocol/sdk`):
980
+
981
+ ```bash
982
+ pnpm add @modelcontextprotocol/sdk
983
+ ```
984
+
985
+ ```typescript
986
+ import { createMcpServer } from "noumen/mcp";
987
+ const server = createMcpServer({ tools: registry.listTools() });
988
+ ```
989
+
990
+ ## Tracing
991
+
992
+ Instrument agent runs with OpenTelemetry:
993
+
994
+ ```typescript
995
+ import { OTelTracer } from "noumen";
996
+
997
+ options: {
998
+ tracing: { tracer: await OTelTracer.create("my-agent") },
999
+ }
1000
+ ```
1001
+
1002
+ Falls back to no-op if `@opentelemetry/api` is not installed.
1003
+
1004
+ ## Full Documentation
1005
+
1006
+ See **[noumen.dev](https://noumen.dev)** for complete documentation on all features including hooks, permissions, compaction strategies, LSP integration, task management, worktrees, plan mode, and more.
1007
+
213
1008
  ## License
214
1009
 
215
1010
  MIT