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.
- package/README.md +846 -51
- package/dist/a2a/index.d.ts +148 -0
- package/dist/a2a/index.js +579 -0
- package/dist/a2a/index.js.map +1 -0
- package/dist/acp/index.d.ts +129 -0
- package/dist/acp/index.js +498 -0
- package/dist/acp/index.js.map +1 -0
- package/dist/agent-1nFVUP9E.d.ts +1332 -0
- package/dist/cache-DsRqxx6v.d.ts +38 -0
- package/dist/chunk-3HEYCV26.js +10 -0
- package/dist/chunk-3HEYCV26.js.map +1 -0
- package/dist/chunk-3SK5GCI6.js +75 -0
- package/dist/chunk-3SK5GCI6.js.map +1 -0
- package/dist/chunk-42PHHZUA.js +132 -0
- package/dist/chunk-42PHHZUA.js.map +1 -0
- package/dist/chunk-4HW6LN6D.js +10365 -0
- package/dist/chunk-4HW6LN6D.js.map +1 -0
- package/dist/chunk-4SQA2UCV.js +26 -0
- package/dist/chunk-4SQA2UCV.js.map +1 -0
- package/dist/chunk-5GEX6ZSB.js +179 -0
- package/dist/chunk-5GEX6ZSB.js.map +1 -0
- package/dist/chunk-5JN4SPI7.js +94 -0
- package/dist/chunk-5JN4SPI7.js.map +1 -0
- package/dist/chunk-AMYIJSAZ.js +57 -0
- package/dist/chunk-AMYIJSAZ.js.map +1 -0
- package/dist/chunk-BZSFUEWM.js +43 -0
- package/dist/chunk-BZSFUEWM.js.map +1 -0
- package/dist/chunk-CS6WNDCF.js +171 -0
- package/dist/chunk-CS6WNDCF.js.map +1 -0
- package/dist/chunk-D43BWEZA.js +346 -0
- package/dist/chunk-D43BWEZA.js.map +1 -0
- package/dist/chunk-DGUM43GV.js +11 -0
- package/dist/chunk-DGUM43GV.js.map +1 -0
- package/dist/chunk-EKOGVTBT.js +472 -0
- package/dist/chunk-EKOGVTBT.js.map +1 -0
- package/dist/chunk-HEQQQGK5.js +131 -0
- package/dist/chunk-HEQQQGK5.js.map +1 -0
- package/dist/chunk-HL6JCRZJ.js +3112 -0
- package/dist/chunk-HL6JCRZJ.js.map +1 -0
- package/dist/chunk-JACGEMTF.js +43 -0
- package/dist/chunk-JACGEMTF.js.map +1 -0
- package/dist/chunk-JX7CLUCV.js +21 -0
- package/dist/chunk-JX7CLUCV.js.map +1 -0
- package/dist/chunk-KXDB56YW.js +39 -0
- package/dist/chunk-KXDB56YW.js.map +1 -0
- package/dist/chunk-L3L3FG5T.js +16 -0
- package/dist/chunk-L3L3FG5T.js.map +1 -0
- package/dist/chunk-OGXNFXFA.js +196 -0
- package/dist/chunk-OGXNFXFA.js.map +1 -0
- package/dist/chunk-UVSSQBDY.js +192 -0
- package/dist/chunk-UVSSQBDY.js.map +1 -0
- package/dist/chunk-Y45R3PQL.js +684 -0
- package/dist/chunk-Y45R3PQL.js.map +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +874 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/client/index.d.ts +64 -0
- package/dist/client/index.js +409 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client-CRRO2376.js +10 -0
- package/dist/client-CRRO2376.js.map +1 -0
- package/dist/headless-FFU2DESQ.js +142 -0
- package/dist/headless-FFU2DESQ.js.map +1 -0
- package/dist/history-snip-64GYP4ZL.js +12 -0
- package/dist/history-snip-64GYP4ZL.js.map +1 -0
- package/dist/index.d.ts +1459 -422
- package/dist/index.js +398 -1757
- package/dist/index.js.map +1 -1
- package/dist/jsonrpc/index.d.ts +54 -0
- package/dist/jsonrpc/index.js +34 -0
- package/dist/jsonrpc/index.js.map +1 -0
- package/dist/lsp/index.d.ts +36 -0
- package/dist/lsp/index.js +16 -0
- package/dist/lsp/index.js.map +1 -0
- package/dist/lsp-PS3BWIHC.js +8 -0
- package/dist/lsp-PS3BWIHC.js.map +1 -0
- package/dist/manager-DLXK63XC.js +8 -0
- package/dist/manager-DLXK63XC.js.map +1 -0
- package/dist/mcp/index.d.ts +111 -0
- package/dist/mcp/index.js +105 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp-auth-AEI2R4ZC.js +9 -0
- package/dist/mcp-auth-AEI2R4ZC.js.map +1 -0
- package/dist/provider-factory-KCLIF34X.js +20 -0
- package/dist/provider-factory-KCLIF34X.js.map +1 -0
- package/dist/providers/anthropic.d.ts +19 -0
- package/dist/providers/anthropic.js +35 -0
- package/dist/providers/anthropic.js.map +1 -0
- package/dist/providers/bedrock.d.ts +39 -0
- package/dist/providers/bedrock.js +56 -0
- package/dist/providers/bedrock.js.map +1 -0
- package/dist/providers/gemini.d.ts +17 -0
- package/dist/providers/gemini.js +262 -0
- package/dist/providers/gemini.js.map +1 -0
- package/dist/providers/ollama.d.ts +13 -0
- package/dist/providers/ollama.js +20 -0
- package/dist/providers/ollama.js.map +1 -0
- package/dist/providers/openai.d.ts +21 -0
- package/dist/providers/openai.js +9 -0
- package/dist/providers/openai.js.map +1 -0
- package/dist/providers/openrouter.d.ts +16 -0
- package/dist/providers/openrouter.js +24 -0
- package/dist/providers/openrouter.js.map +1 -0
- package/dist/providers/vertex.d.ts +42 -0
- package/dist/providers/vertex.js +67 -0
- package/dist/providers/vertex.js.map +1 -0
- package/dist/render-GRN4ZSSW.js +14 -0
- package/dist/render-GRN4ZSSW.js.map +1 -0
- package/dist/resolve-4JA2BBDA.js +14 -0
- package/dist/resolve-4JA2BBDA.js.map +1 -0
- package/dist/server/index.d.ts +143 -0
- package/dist/server/index.js +695 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server-CHMxuWKq.d.ts +96 -0
- package/dist/spinner-OJNR6NFO.js +8 -0
- package/dist/spinner-OJNR6NFO.js.map +1 -0
- package/dist/types-2kTLUCnD.d.ts +107 -0
- package/dist/types-CD0rUKKT.d.ts +109 -0
- package/dist/types-LrU4LRmX.d.ts +575 -0
- package/dist/types-NIyVwQ4h.d.ts +109 -0
- package/dist/types-QwfylltH.d.ts +71 -0
- package/dist/types-RPKUTu1k.d.ts +645 -0
- package/dist/uuid-RVN2T26F.js +8 -0
- package/dist/uuid-RVN2T26F.js.map +1 -0
- package/dist/zod-7YXKWYMC.js +12 -0
- package/dist/zod-7YXKWYMC.js.map +1 -0
- package/package.json +141 -7
package/README.md
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
# noumen
|
|
1
|
+
# noumen 🐍
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The agent runtime you `npm install`.
|
|
4
4
|
|
|
5
|
-
`noumen` gives you
|
|
5
|
+
`noumen` gives you the full agentic loop — tool execution, file editing, shell commands, context compaction, and session management — with 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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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 =
|
|
63
|
+
const thread = agent.createThread();
|
|
30
64
|
|
|
31
|
-
for await (const event of thread.run("
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
301
|
+
Route Anthropic models through AWS Bedrock. Requires `@anthropic-ai/bedrock-sdk`:
|
|
85
302
|
|
|
86
|
-
|
|
303
|
+
```bash
|
|
304
|
+
pnpm add @anthropic-ai/bedrock-sdk
|
|
305
|
+
```
|
|
87
306
|
|
|
88
307
|
```typescript
|
|
89
|
-
import {
|
|
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
|
-
|
|
92
|
-
|
|
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
|
-
|
|
332
|
+
```typescript
|
|
333
|
+
import { VertexAnthropicProvider } from "noumen/vertex";
|
|
96
334
|
|
|
97
|
-
|
|
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 {
|
|
346
|
+
import { GoogleAuth } from "google-auth-library";
|
|
101
347
|
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
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
|
|
117
|
-
|
|
118
|
-
|
|
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: "
|
|
123
|
-
maxTokens: 8192,
|
|
124
|
-
autoCompact: true,
|
|
125
|
-
autoCompactThreshold: 100_000,
|
|
126
|
-
systemPrompt: "...",
|
|
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
|
|
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 =
|
|
615
|
+
const thread = agent.createThread();
|
|
139
616
|
|
|
140
617
|
// Resume an existing session
|
|
141
|
-
const thread =
|
|
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
|
|
188
|
-
|
|
189
|
-
|
|
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
|
|
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
|
|
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
|