Embedding
Six ways to integrate noumen into your application — in-process, HTTP/SSE, middleware, WebSocket, framework-specific patterns, and headless CLI.
noumen is designed as a library first. Whether you're building a VS Code extension, a web app with an AI coding assistant, or a CI pipeline, you can embed the full agent loop directly into your application.
In-process (Node.js / Bun)
The simplest integration: import Agent, create a thread, and iterate over events.
import { Agent, AiSdkProvider } from "noumen";
import { LocalSandbox } from "noumen/local";
import { createAnthropic } from "@ai-sdk/anthropic";
const anthropic = createAnthropic({ apiKey: process.env.ANTHROPIC_API_KEY! });
const code = new Agent({
provider: new AiSdkProvider({
model: anthropic("claude-opus-4.6"),
providerFamily: "anthropic",
cacheConfig: { enabled: true },
}),
sandbox: LocalSandbox({ cwd: "/my/project" }),
options: {
permissions: { mode: "bypassPermissions" },
autoCompact: true,
costTracking: { enabled: true },
},
});
await code.init();
const thread = code.createThread();
for await (const event of thread.run("Add error handling to server.ts")) {
switch (event.type) {
case "text_delta":
process.stdout.write(event.text);
break;
case "tool_use_start":
console.log(`\n[tool] ${event.toolName}`);
break;
case "tool_result":
console.log(`[done] ${event.toolName}`);
break;
case "turn_complete":
console.log(`\n[cost] ${event.usage.total_tokens} tokens`);
break;
}
}
await code.close();One-shot with run() and execute()
For simple tasks you can skip createThread() entirely. run() streams events, execute() runs to completion and returns a result.
import { LocalAgent } from "noumen/local";
const agent = LocalAgent({ provider: "anthropic", cwd: "." });
// Streaming — same events as thread.run()
for await (const event of agent.run("Add a health-check endpoint")) {
if (event.type === "text_delta") process.stdout.write(event.text);
}
// Execute — runs to completion, optional callbacks along the way
const result = await agent.execute("Fix the auth bug", {
onText: (text) => process.stdout.write(text),
onToolUse: (name) => console.log(`Using ${name}`),
});
console.log(`Done — ${result.toolCalls} tool calls, ${result.usage.total_tokens} tokens`);LocalAgent (on noumen/local) is a thin factory over new Agent({ ..., sandbox: LocalSandbox({ cwd }) }). For raw host access use UnsandboxedAgent from noumen/unsandboxed. For remote sandboxes construct Agent directly with the sandbox of your choice — see Virtual Infrastructure.
Using presets
For quicker setup, use a preset that configures sensible defaults:
import { codingAgent, AiSdkProvider } from "noumen";
import { LocalSandbox } from "noumen/local";
import { createOpenAI } from "@ai-sdk/openai";
const openai = createOpenAI({ apiKey: process.env.OPENAI_API_KEY! });
const code = codingAgent({
provider: new AiSdkProvider({ model: openai.chat("gpt-5") }),
cwd: "/my/project",
sandbox: LocalSandbox({ cwd: "/my/project" }),
});
await code.init();
const thread = code.createThread();
for await (const event of thread.run("Refactor the auth module")) {
if (event.type === "text_delta") process.stdout.write(event.text);
}
await code.close();Presets require an explicit sandbox — import one from its subpath (noumen/local, noumen/unsandboxed, noumen/docker, …) and pass it in. This keeps the root barrel free of host-fs imports for apps that only use a remote sandbox.
Three presets are available:
| Preset | Permissions | Features |
|---|---|---|
codingAgent | default | Subagents, tasks, plan mode, auto-compact, retry, cost tracking, project context |
planningAgent | plan | Read-only exploration, plan mode |
reviewAgent | plan | Read-only with web search for documentation lookups |
Health checks
Before running prompts, verify that all integrations are working. Pass an optional timeoutMs to control per-check timeout (default 10 000 ms):
const result = await code.diagnose();
console.log(result);
// {
// overall: true,
// provider: { ok: true, latencyMs: 342, model: "claude-sonnet-4" },
// sandbox: {
// fs: { ok: true, latencyMs: 2 },
// computer: { ok: true, latencyMs: 45 },
// },
// mcp: { filesystem: { ok: true, latencyMs: 0, status: "connected", toolCount: 5 } },
// lsp: { typescript: { ok: true, latencyMs: 0, state: "running" } },
// timestamp: "2026-04-04T12:00:00.000Z",
// }Each check includes:
ok— whether the subsystem is healthylatencyMs— how long the check took (useful for identifying slow integrations)error/warning— human-readable detail when something is wrongoverall—trueonly when provider, filesystem, and shell all pass
Use result.overall as a gate before accepting user prompts in production.
HTTP / SSE server
Expose the agent over HTTP using the built-in server. Clients connect via Server-Sent Events for streaming.
import { Agent, AiSdkProvider } from "noumen";
import { LocalSandbox } from "noumen/local";
import { createServer } from "noumen/server";
import { createAnthropic } from "@ai-sdk/anthropic";
const anthropic = createAnthropic({ apiKey: process.env.ANTHROPIC_API_KEY! });
const code = new Agent({
provider: new AiSdkProvider({
model: anthropic("claude-opus-4.6"),
providerFamily: "anthropic",
}),
sandbox: LocalSandbox({ cwd: "/workspace" }),
options: { permissions: { mode: "bypassPermissions" } },
});
await code.init();
const server = createServer(code, {
port: 3001,
auth: { type: "bearer", token: process.env.API_TOKEN! },
cors: true,
});
await server.start();
console.log("Agent server running on http://localhost:3001");Client connection
Connect from any HTTP client or use the built-in NoumenClient:
import { NoumenClient } from "noumen/client";
const client = new NoumenClient({
baseUrl: "http://localhost:3001",
token: process.env.API_TOKEN,
transport: "sse",
});
for await (const event of client.run("Fix the failing test in auth.test.ts")) {
if (event.type === "text_delta") process.stdout.write(event.text);
}Middleware adapter
Mount the agent on an existing Express, Fastify, or Hono server using createRequestHandler():
import express from "express";
import { Agent, AiSdkProvider } from "noumen";
import { LocalSandbox } from "noumen/local";
import { createRequestHandler } from "noumen/server";
import { createOpenAI } from "@ai-sdk/openai";
const openai = createOpenAI({ apiKey: process.env.OPENAI_API_KEY! });
const code = new Agent({
provider: new AiSdkProvider({ model: openai.chat("gpt-5") }),
sandbox: LocalSandbox({ cwd: "/workspace" }),
options: { permissions: { mode: "bypassPermissions" } },
});
await code.init();
const app = express();
app.use("/agent", createRequestHandler(code, {
auth: { type: "bearer", token: process.env.API_TOKEN! },
}));
app.listen(3000);All REST endpoints from the Server API Reference are available under the mount path. WebSocket requires createServer() standalone mode.
WebSocket
For bidirectional communication (permissions, user input), use the WebSocket transport:
import { createServer } from "noumen/server";
const server = createServer(code, {
port: 3001,
ws: true,
auth: { type: "bearer", token: process.env.API_TOKEN! },
});
await server.start();The client can handle permission requests and user input interactively:
import { NoumenClient } from "noumen/client";
const client = new NoumenClient({
baseUrl: "http://localhost:3001",
token: process.env.API_TOKEN,
transport: "ws",
});
for await (const event of client.run("Deploy to staging", {
onPermissionRequest: async (req) => {
const approved = await askUser(`Allow ${req.toolName}?`);
return { allow: approved };
},
onUserInput: async (question) => {
return await askUser(question);
},
})) {
if (event.type === "text_delta") process.stdout.write(event.text);
}Framework examples
Next.js API route
Stream agent events to the browser via a Next.js route handler:
// app/api/agent/route.ts
import { Agent, AiSdkProvider } from "noumen";
import { LocalSandbox } from "noumen/local";
import { createOpenAI } from "@ai-sdk/openai";
const openai = createOpenAI({ apiKey: process.env.OPENAI_API_KEY! });
const code = new Agent({
provider: new AiSdkProvider({ model: openai.chat("gpt-5") }),
sandbox: LocalSandbox({ cwd: process.env.WORKSPACE_DIR! }),
options: { permissions: { mode: "bypassPermissions" } },
});
export async function POST(req: Request) {
const { prompt } = await req.json();
const thread = code.createThread();
const stream = new ReadableStream({
async start(controller) {
const encoder = new TextEncoder();
for await (const event of thread.run(prompt)) {
controller.enqueue(
encoder.encode(`data: ${JSON.stringify(event)}\n\n`),
);
}
controller.close();
},
});
return new Response(stream, {
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
},
});
}Electron main process
Run the agent in Electron's main process and communicate with the renderer via IPC:
// main.ts
import { app, ipcMain } from "electron";
import { codingAgent, AiSdkProvider } from "noumen";
import { LocalSandbox } from "noumen/local";
import { createAnthropic } from "@ai-sdk/anthropic";
const anthropic = createAnthropic({ apiKey: process.env.ANTHROPIC_API_KEY! });
const cwd = app.getPath("documents");
const code = codingAgent({
provider: new AiSdkProvider({
model: anthropic("claude-opus-4.6"),
providerFamily: "anthropic",
cacheConfig: { enabled: true },
}),
cwd,
sandbox: LocalSandbox({ cwd }),
});
app.whenReady().then(async () => {
await code.init();
ipcMain.handle("agent:run", async (event, prompt: string) => {
const thread = code.createThread();
const events = [];
for await (const e of thread.run(prompt)) {
event.sender.send("agent:event", e);
events.push(e);
}
return events;
});
});VS Code extension
Integrate the agent into a VS Code extension:
// extension.ts
import * as vscode from "vscode";
import { codingAgent, AiSdkProvider } from "noumen";
import { LocalSandbox } from "noumen/local";
import { createOpenAI } from "@ai-sdk/openai";
export function activate(context: vscode.ExtensionContext) {
const cwd = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
const openai = createOpenAI({
apiKey: vscode.workspace.getConfiguration("noumen").get("apiKey")!,
});
const code = codingAgent({
provider: new AiSdkProvider({ model: openai.chat("gpt-5") }),
cwd,
sandbox: LocalSandbox({ cwd }),
});
const disposable = vscode.commands.registerCommand(
"noumen.run",
async () => {
const prompt = await vscode.window.showInputBox({
prompt: "What should the agent do?",
});
if (!prompt) return;
await code.init();
const thread = code.createThread();
const output = vscode.window.createOutputChannel("noumen");
output.show();
for await (const event of thread.run(prompt)) {
if (event.type === "text_delta") output.append(event.text);
if (event.type === "tool_use_start") {
output.appendLine(`\n[${event.toolName}]`);
}
}
await code.close();
},
);
context.subscriptions.push(disposable);
}Headless CLI
For subprocess integration from any language, run the agent in headless mode with bidirectional NDJSON over stdin/stdout:
noumen --headless -p anthropic -m claude-sonnet-4Send commands as JSON lines on stdin, receive events as JSON lines on stdout. See the Server API Reference for the full protocol and examples in Node.js and Python.
Key stream events to handle
When embedding, you'll typically handle these core events:
| Event | When | What to do |
|---|---|---|
text_delta | Model streams text | Display to user |
tool_use_start | Tool call begins | Show activity indicator |
tool_result | Tool completes | Show result summary |
permission_request | Agent needs approval | Prompt user (WebSocket) or auto-approve |
error | Something went wrong | Display error, retry, or abort |
turn_complete | Run finished | Show cost/token summary |
session_resumed | Resuming a saved session | Confirm to user |
See Stream Events for the complete reference of all event types.