-
-
-
-
-
+# General Agent SDK
-
+### Build AI agents that actually *do things*.
-[](https://www.npmjs.com/package/general-agent-sdk)
-
-[](https://www.typescriptlang.org/)
-
-[](./LICENSE)
-
-[](./tests)
-
-[](https://nodejs.org/)
+**Stream responses. Call tools. Manage sessions. Ship to production.**
-
+[](https://www.npmjs.com/package/general-agent-sdk)
+[](./LICENSE)
+[](https://www.typescriptlang.org/)
+[](https://nodejs.org/)
+[](./tests)
-**The TypeScript SDK for building AI agents that call tools, manage sessions, and ship to production.**
+
-[Quick Start](#-get-started-in-60-seconds) Β· [Architecture](#-architecture) Β· [API Reference](./SDK%20DOCS/API-REFERENCE.md) Β· [Examples](./SDK%20DOCS/)
+```
+npm install general-agent-sdk
+```
+[Quick Start](#-quick-start) Β· [Examples](./SDK%20DOCS/) Β· [API Reference](./SDK%20DOCS/API-REFERENCE.md) Β· [Documentation](#-documentation)
+
-
+---
## Why General Agent SDK?
-Most agent frameworks give you **wrappers around chat completions** β you manage the tool loop, you track the state, you handle restarts. General Agent SDK gives you a **complete execution kernel**:
+Most "agent frameworks" give you wrappers around chat completions. **General Agent SDK gives you a full execution kernel** β the agent runs tools autonomously, suspends for human input, resumes across restarts, and streams every event back to your app in real time.
-
-
-
-
-
-
-
-
-> **The SDK runs the agent loop for you.** You send a message, the agent autonomously calls tools, reasons about results, calls more tools, and streams every event back β until it's done or needs your input.
-
-
+```
+βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+β Your App (Host) β
+β β
+β ββββββββββββββββββββββββββββββββββββββββββββββββββββ β
+β β General Agent SDK β β
+β β β β
+β β User βββ LLM βββ Tool βββ LLM βββ Tool βββ β β β
+β β β β β β β β
+β β β built-in β hosted tool β β
+β β β (read, exec, β (your code) β β
+β β β web_search) β β β
+β β β β β β
+β β βββ stream events back to host βββ β β
+β ββββββββββββββββββββββββββββββββββββββββββββββββββββ β
+β β
+β You control: credentials, persistence, tools, hooks β
+βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+```
-
+
+
+
-
+### What it does
-
+- **Autonomous tool loops** β agent calls tools and continues thinking until done
+- **8 built-in tools** β `read`, `write`, `edit`, `exec`, `web_search`, `web_fetch`, `apply_patch`, `subagents`
+- **Hosted tools** β define your own tools, SDK suspends & resumes
+- **Multi-turn memory** β sessions remember across turns automatically
+- **Real-time streaming** β every token, tool call, and result as events
+- **MCP integration** β plug in any MCP server (stdio or HTTP)
+- **26 lifecycle hooks** β intercept anything from model selection to tool execution
+- **Subagent delegation** β spawn child agents with scoped instructions
-
+
+
-
+### What makes it different
-
+- **Not a wrapper** β it's a complete agent execution engine
+- **Host-owned** β you control persistence, credentials, and policy
+- **Restart-safe** β hosted tool pauses survive process restarts
+- **Production-ready** β context compaction, file checkpoints, error boundaries
+- **Type-safe** β full TypeScript with zero `any` in public API
+- **Tested** β 133 tests + real API E2E verification
+- **Lightweight** β ~180KB packaged, 6 dependencies
+- **Escape hatch friendly** β use as much or as little as you need
-
+
+
+
-
+---
-
+## β‘ Quick Start
-### Step 1 β Install
+### 1. Install
```bash
npm install general-agent-sdk
```
-### Step 2 β Set your API key
+### 2. Set your API key
```bash
export ANTHROPIC_API_KEY="sk-ant-..."
-# Optional: use a custom endpoint
+# Optional: use a proxy
# export ANTHROPIC_BASE_URL="https://your-proxy.example.com"
```
-### Step 3 β Run your first agent
+### 3. Build your first agent
```typescript
import { createGeneralAgentSdk } from "general-agent-sdk";
@@ -87,14 +110,16 @@ import { randomUUID } from "node:crypto";
import path from "node:path";
import os from "node:os";
-// 1. Initialize
+// Initialize the SDK
const sdk = await createGeneralAgentSdk({
workspaceDir: process.cwd(),
- stateDir: path.join(process.cwd(), ".agent-state"),
- agentDir: path.join(process.cwd(), ".agent"),
- profileId: "default",
- pluginMode: "disabled",
- logger: { onDebug() {}, onInfo() {}, onWarn() {}, onError() {} },
+ stateDir: path.join(process.cwd(), ".agent-state"),
+ agentDir: path.join(process.cwd(), ".agent"),
+ profileId: "default",
+ pluginMode: "disabled",
+ logger: {
+ onDebug() {}, onInfo() {}, onWarn() {}, onError() {},
+ },
sessionStore: {
async load() { return null; },
async save() {},
@@ -104,225 +129,198 @@ const sdk = await createGeneralAgentSdk({
},
});
-// 2. Create a session
+// Create a session
const session = sdk.createSession({
- identity: { mode: "general", sessionId: randomUUID(), sessionKey: "demo" },
- systemPrompt: "You are a helpful assistant. Use tools when needed.",
- modelRef: "claude-sonnet-4-20250514",
- sessionFile: path.join(os.tmpdir(), "demo-session.jsonl"),
+ identity: { mode: "general", sessionId: randomUUID(), sessionKey: "my-app:default" },
+ systemPrompt: "You are a helpful assistant.",
+ modelRef: "claude-sonnet-4-20250514",
+ sessionFile: path.join(os.tmpdir(), "session.jsonl"),
});
-// 3. Stream a turn β the agent calls tools autonomously
+// Stream a conversation
for await (const event of session.streamTurn({
role: "user",
- content: [{ type: "text", text: "List the files in the current directory" }],
+ content: [{ type: "text", text: "What files are in the current directory?" }],
})) {
switch (event.kind) {
- case "assistant_delta": process.stdout.write(event.text); break;
- case "tool_call": console.log(`\nπ§ ${event.toolName}`); break;
- case "tool_result": console.log(` β done`); break;
- case "turn_complete": console.log(`\n\nπ Turn complete (${event.stopReason})`); break;
+ case "assistant_delta":
+ process.stdout.write(event.text);
+ break;
+ case "tool_call":
+ console.log(`\nπ§ ${event.toolName}(${JSON.stringify(event.input)})`);
+ break;
+ case "tool_result":
+ console.log(`β Done`);
+ break;
+ case "turn_complete":
+ console.log(`\n\nβ Finished (${event.stopReason})`);
+ break;
}
}
await sdk.shutdown();
```
-> **That's it.** The agent autonomously reads the directory, reasons about the output, and streams a formatted answer β all in one `for await` loop.
-
-
+**That's it.** The agent will autonomously read the directory, think about the results, and give you a formatted answer β all streamed in real time.
---
-
-
## π― Core Concepts
### The Event Stream
-Every turn returns an `AsyncIterable`. No callbacks, no observers β one loop handles everything:
-
-
-
-
-
-
-
-
+Every interaction returns an `AsyncIterable`. No callbacks, no observers β just a `for await` loop:
```typescript
for await (const event of session.streamTurn(input)) {
- switch (event.kind) {
- case "assistant_delta": // β streaming text from the model
- case "reasoning_delta": // β model thinking (extended thinking)
- case "tool_call": // β built-in tool invoked
- case "tool_result": // β tool returned a result
- case "hosted_tool_call": // β YOUR tool was requested β SDK suspends βΈοΈ
- case "usage_snapshot": // β token usage update
- case "turn_complete": // β this turn is done β
- }
+ // event.kind tells you what happened:
+ //
+ // "assistant_delta" β streaming text chunk
+ // "reasoning_delta" β model thinking (extended thinking)
+ // "tool_call" β agent is calling a built-in tool
+ // "tool_result" β tool returned a result
+ // "hosted_tool_call" β YOUR tool was requested (SDK suspends)
+ // "usage_snapshot" β token usage update
+ // "turn_complete" β this turn is done
}
```
----
-
-### π― Hosted Tools β Your Code, The Model's Brain
+### Hosted Tools β Your Code, Their Brain
-Define tools the AI can call. You implement the logic; the SDK orchestrates the lifecycle:
+Define tools that the AI can call. **You implement the logic**, the SDK handles the orchestration:
```typescript
const sdk = await createGeneralAgentSdk({
- // ...
- hostedTools: [{
- name: "get_weather",
- description: "Get current weather for a city",
- inputSchema: {
- type: "object",
- properties: { city: { type: "string" } },
- required: ["city"],
+ // ... other options ...
+ hostedTools: [
+ {
+ name: "get_stock_price",
+ description: "Get real-time stock price",
+ inputSchema: {
+ type: "object",
+ properties: { symbol: { type: "string" } },
+ required: ["symbol"],
+ },
},
- }],
+ ],
});
-for await (const event of session.streamTurn(userMsg)) {
+// Handle tool calls
+for await (const event of session.streamTurn(userMessage)) {
if (event.kind === "hosted_tool_call") {
- // SDK suspends automatically βΈοΈ
- const weather = await getWeather(event.input.city);
+ // SDK automatically suspends here βΈοΈ
+
+ // You execute your logic
+ const price = await fetchStockPrice(event.input.symbol);
- // Resume with your result βΆοΈ
- for await (const e of session.submitHostedToolResult({
+ // Resume the agent with the result βΆοΈ
+ for await (const resumed of session.submitHostedToolResult({
callId: event.callId,
- output: weather,
+ output: { price, currency: "USD" },
})) {
- if (e.kind === "assistant_delta") process.stdout.write(e.text);
+ if (resumed.kind === "assistant_delta") process.stdout.write(resumed.text);
}
break;
}
}
```
-> **Restart-safe:** Hosted tool pauses survive process restarts. The SDK snapshots context and resumes correctly β even with multi-tool parallel calls.
-
----
-
-### π¬ Multi-Turn Memory
+### Multi-Turn Sessions
-Sessions automatically maintain conversation history:
+Sessions automatically maintain conversation history. The agent remembers everything:
```typescript
// Turn 1
-for await (const e of session.streamTurn({
+await consume(session.streamTurn({
role: "user",
- content: [{ type: "text", text: "My name is Alice. I'm building a TypeScript app." }],
-})) { /* ... */ }
+ content: [{ type: "text", text: "My name is Alice and I like TypeScript." }],
+}));
-// Turn 2 β the agent remembers everything
-for await (const e of session.streamTurn({
+// Turn 2 β the agent remembers!
+await consume(session.streamTurn({
role: "user",
- content: [{ type: "text", text: "What's my name and what am I building?" }],
-})) { /* ... */ }
-// β "Your name is Alice and you're building a TypeScript app."
+ content: [{ type: "text", text: "What's my name and what do I like?" }],
+}));
+// β "Your name is Alice and you like TypeScript."
```
----
-
-### πͺ Hooks β Intercept Everything
+### Hooks β Intercept Everything
-26 hooks let you observe, modify, or block any lifecycle event:
+26 hooks let you observe, modify, or block any part of the agent lifecycle:
```typescript
const sdk = await createGeneralAgentSdk({
+ // ...
hooks: [
- // π Dynamic model routing
+ // Dynamically switch models
{
pluginId: "my-app",
hookName: "before_model_resolve",
- handler: (ev) => ({
- modelOverride: needsPower(ev) ? "claude-opus-4-20250514" : "claude-sonnet-4-20250514",
+ handler: (event) => ({
+ modelOverride: isComplexTask(event.prompt)
+ ? "claude-opus-4-20250514"
+ : "claude-sonnet-4-20250514",
}),
},
-
- // π‘οΈ Safety guardrails
+ // Block dangerous tool calls
{
pluginId: "my-app",
hookName: "before_tool_call",
- handler: (ev) => {
- if (ev.toolName === "exec" && ev.params.command?.includes("rm -rf"))
+ handler: (event) => {
+ if (event.toolName === "exec" && event.params.command?.includes("rm -rf")) {
return { block: true, blockReason: "Dangerous command blocked" };
+ }
},
},
-
- // π Usage audit trail
+ // Audit all LLM calls
{
pluginId: "my-app",
hookName: "llm_output",
- handler: (ev) => {
- db.insert({ model: ev.model, tokens: ev.usage?.input + ev.usage?.output });
+ handler: (event) => {
+ console.log(`[audit] ${event.model}: ${event.usage?.input}in/${event.usage?.output}out tokens`);
},
},
],
});
```
-
-π Full hook reference (19 SDK-native + 7 host-bridged)
-
-
+---
-**SDK-native hooks** β auto-fired by the runtime:
-
-| Hook | Modifiable | Fires when... |
-|:-----|:---:|:---|
-| `before_model_resolve` | β | Model selection begins |
-| `before_prompt_build` | β | System prompt is being assembled |
-| `before_agent_start` | β | Agent run is about to begin |
-| `llm_input` | β | Request sent to LLM |
-| `llm_output` | β | Response received from LLM |
-| `agent_end` | β | Agent run completed |
-| `before_tool_call` | β | Tool is about to execute |
-| `after_tool_call` | β | Tool execution finished |
-| `tool_result_persist` | β | Tool result being saved to transcript |
-| `before_message_write` | β | Message being written to history |
-| `session_start` | β | Session first activated |
-| `session_end` | β | Session completed |
-| `before_compaction` | β | Context compaction starting |
-| `after_compaction` | β | Context compaction finished |
-| `before_reset` | β | Session about to clear |
-| `subagent_spawning` | β | Child agent creation requested |
-| `subagent_delivery_target` | β | Routing child agent delivery |
-| `subagent_spawned` | β | Child agent created |
-| `subagent_ended` | β | Child agent finished |
+## π§° Built-in Tools
-**Host-bridged hooks** β you trigger these via `sdk.emitHook()`:
+The agent comes pre-loaded with powerful tools:
-| Hook | Purpose |
-|:-----|:--------|
-| `inbound_claim` | Incoming message routing |
-| `before_dispatch` | Pre-dispatch filtering |
-| `message_received` | Message arrival notification |
-| `message_sending` | Modify/cancel outgoing messages |
-| `message_sent` | Delivery confirmation |
-| `gateway_start` | Gateway lifecycle start |
-| `gateway_stop` | Gateway shutdown |
+| Tool | What it does |
+|------|-------------|
+| `read` | Read file contents (with line ranges) |
+| `write` | Create or overwrite files |
+| `edit` | Surgical file edits with diff |
+| `apply_patch` | Apply unified diffs |
+| `exec` | Run shell commands |
+| `web_search` | Search the web (DuckDuckGo / Brave) |
+| `web_fetch` | Fetch and parse web pages |
+| `subagents` | Delegate tasks to child agents |
-
+The agent decides which tools to use. You can restrict available tools per session, and every tool call flows through the `before_tool_call` / `after_tool_call` hooks.
---
-### π MCP Integration
+## π MCP Integration
-Plug in any [Model Context Protocol](https://modelcontextprotocol.io/) server β tools appear alongside built-ins:
+Plug in any [Model Context Protocol](https://modelcontextprotocol.io/) server:
```typescript
+// Local process
session.setDynamicMcpServers({
- // Local process (stdio)
filesystem: {
transport: "stdio",
command: "npx",
args: ["-y", "@modelcontextprotocol/server-filesystem", "/data"],
},
+});
- // Remote endpoint (HTTP)
+// Remote HTTP endpoint
+session.setDynamicMcpServers({
my_api: {
transport: "http",
url: "https://mcp.example.com/api",
@@ -331,190 +329,222 @@ session.setDynamicMcpServers({
});
```
+MCP tools show up alongside built-in tools. The agent uses them seamlessly.
+
---
-### π€ Subagents
+## π€ Subagents
-The agent can spawn scoped child agents to divide and conquer complex tasks:
+The agent can spawn child agents to divide and conquer:
```typescript
const session = sdk.createSession({
- systemPrompt: `You are a tech lead. Delegate tasks using the subagents tool.`,
// ...
+ systemPrompt: `You are a project manager.
+ Use the subagents tool to delegate tasks to specialists.`,
});
// The agent will autonomously:
-// 1. Analyze the task
-// 2. Spawn child agents with scoped instructions + tools
-// 3. Collect results from each child
+// 1. Break the task into subtasks
+// 2. Spawn child agents with scoped instructions
+// 3. Collect results
// 4. Synthesize a final answer
```
-Each child gets **independent message history** and **scoped tool access**. The `subagents` tool is excluded from children to prevent infinite recursion. Four lifecycle hooks fire automatically: `subagent_spawning` β `subagent_spawned` β `subagent_ended`.
+Each subagent gets its own independent message history and scoped tool access. The `subagents` tool is excluded from children to prevent infinite recursion.
---
-### π Session Management
+## π Session Management
```typescript
-const session = sdk.createSession({ ... }); // Create
-const resumed = await sdk.resumeSession("sess-123"); // Resume
-const forked = await sdk.forkSession("sess-123", { // Fork
- identity: { ... },
-});
-const sessions = await sdk.listSessions(); // List all
-const history = await sdk.readSessionHistory("id"); // Read transcript
-await session.reset("context_overflow"); // Reset
-const usage = session.getUsageSnapshot(); // Token usage
+// Create
+const session = sdk.createSession({ ... });
+
+// Resume by ID
+const resumed = await sdk.resumeSession("session-123");
+
+// Fork (branch from existing conversation)
+const forked = await sdk.forkSession("session-123", { ... });
+
+// List all sessions
+const sessions = await sdk.listSessions();
+
+// Read transcript history
+const history = await sdk.readSessionHistory("session-123");
+
+// Reset (clear history, keep config)
+await session.reset("starting_fresh");
+
+// Check token usage
+const usage = session.getUsageSnapshot();
+// β { usedInputTokens: 1234, contextWindow: 200000, usedPct: 0.6 }
```
-**Context Compaction** β long conversations don't overflow:
+### Context Compaction
+
+Long conversations don't overflow β the SDK compacts automatically:
```typescript
await session.maybeCompactByTokens({
- usedPctThreshold: 85, // trigger at 85% context usage
+ usedPctThreshold: 85, // trigger at 85% usage
cooldownMs: 60_000, // min 60s between compactions
});
```
-**File Checkpoints** β every write creates an automatic rollback point:
+### File Checkpoints
+
+Every file write creates an automatic checkpoint. Roll back anytime:
```typescript
const checkpoints = await session.listCheckpoints();
await session.restoreCheckpoint(checkpoints[0].id);
```
-
-
---
-
+## π Documentation
-## π Architecture
+| Resource | Description |
+|----------|-------------|
+| [`SDK DOCS/README.md`](./SDK%20DOCS/README.md) | Full documentation index |
+| [`SDK DOCS/API-REFERENCE.md`](./SDK%20DOCS/API-REFERENCE.md) | Complete API reference |
+| [`SDK DOCS/01-hello-world.ts`](./SDK%20DOCS/01-hello-world.ts) | Your first agent |
+| [`SDK DOCS/02-multi-turn-chat.ts`](./SDK%20DOCS/02-multi-turn-chat.ts) | Interactive multi-turn REPL |
+| [`SDK DOCS/03-hosted-tools.ts`](./SDK%20DOCS/03-hosted-tools.ts) | Custom tool integration |
+| [`SDK DOCS/04-session-lifecycle.ts`](./SDK%20DOCS/04-session-lifecycle.ts) | Session management |
+| [`SDK DOCS/05-hooks.ts`](./SDK%20DOCS/05-hooks.ts) | Lifecycle hooks |
+| [`SDK DOCS/06-mcp-servers.ts`](./SDK%20DOCS/06-mcp-servers.ts) | MCP server integration |
+| [`SDK DOCS/07-compaction.ts`](./SDK%20DOCS/07-compaction.ts) | Context window management |
+| [`SDK DOCS/08-subagents.ts`](./SDK%20DOCS/08-subagents.ts) | Subagent delegation |
-
-
+All examples are **runnable** β just set your API key and go:
-
+```bash
+export ANTHROPIC_API_KEY="sk-ant-..."
+npx tsx "SDK DOCS/01-hello-world.ts"
+```
-