From 616ccfe564fc6e1661ce4bf93e195dea25b6839b Mon Sep 17 00:00:00 2001 From: Redux0223 Date: Tue, 31 Mar 2026 20:47:07 +0800 Subject: [PATCH 1/2] docs: redesign README for open-source launch Complete rewrite of README.md with production-quality presentation: - Hero section with badges, install command, and navigation links - Architecture diagram showing host/SDK relationship - Feature comparison table (what it does vs what makes it different) - Copy-paste quick start with full working example - Core concepts section: event stream, hosted tools, multi-turn, hooks - Built-in tools reference table - MCP integration examples (stdio + HTTP) - Subagent delegation overview - Session management API showcase - Collapsible hook reference (19 SDK-native + 7 host-bridged) - Event reference table - Repository architecture tree - Development commands Made-with: Cursor --- README.md | 767 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 434 insertions(+), 333 deletions(-) diff --git a/README.md b/README.md index e8cb483..75e1640 100644 --- a/README.md +++ b/README.md @@ -1,449 +1,550 @@ +
+ # General Agent SDK -`general-agent-sdk` is a session-first embedded SDK that extracts the agent execution kernel from OpenClaw and exposes it as a host-controlled TypeScript package. +### Build AI agents that actually *do things*. -The SDK is intentionally narrow: it preserves execution-layer semantics such as tool calls, hosted-tool suspend/resume, compaction, plugin policy, and provider-specific streaming, while leaving orchestration, channel routing, profile ownership, and canonical session state to the host application. +**Stream responses. Call tools. Manage sessions. Ship to production.** -## Status +[![npm version](https://img.shields.io/npm/v/general-agent-sdk?style=flat-square&color=cb3837&label=npm)](https://www.npmjs.com/package/general-agent-sdk) +[![License: MIT](https://img.shields.io/badge/license-MIT-blue?style=flat-square)](./LICENSE) +[![TypeScript](https://img.shields.io/badge/TypeScript-5.7-3178c6?style=flat-square&logo=typescript&logoColor=white)](https://www.typescriptlang.org/) +[![Node.js](https://img.shields.io/badge/Node.js-%E2%89%A522.14-339933?style=flat-square&logo=nodedotjs&logoColor=white)](https://nodejs.org/) +[![Tests](https://img.shields.io/badge/tests-133%20passing-brightgreen?style=flat-square)](./tests) -- Repository: `https://github.com/babelcloud/general-agent-sdk` -- Package name: `general-agent-sdk` -- Current package version: `0.1.0` -- Runtime: Node.js `>=22.14.0` -- Module format: ESM -- CI workflow: [`.github/workflows/sdk-ci.yml`](./.github/workflows/sdk-ci.yml) +
-This repository is currently host-oriented and private by default. It is designed to be consumed as a pinned dependency or submodule by a parent host application. +``` +npm install general-agent-sdk +``` -## Breaking Changes +
-The current General Agent SDK surface intentionally removes earlier transitional names and compatibility entrypoints. +[Quick Start](#-quick-start) · [Examples](./SDK%20DOCS/) · [API Reference](./SDK%20DOCS/API-REFERENCE.md) · [Documentation](#-documentation) -- Root factory/type names are now `createGeneralAgentSdk`, `GeneralAgentSdk`, `GeneralAgentSdkOptions`, and `GeneralAgentSession`. -- The package no longer ships the `./compat/visionclaw` entrypoint. -- The package no longer ships the `./plugin-sdk` alias. +
-If you are upgrading from an earlier internal prototype, update root imports and switch any host integration that depended on removed subpaths to the native SDK session/event APIs. +--- -## What This SDK Is +## Why General Agent SDK? -- A standalone embedded agent kernel extracted from OpenClaw -- A session factory plus session objects -- A bridge that preserves structured execution semantics instead of flattening everything into text -- A host-integrated runtime that writes all state and logs under host-owned roots +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. -## What This SDK Is Not +``` +┌─────────────────────────────────────────────────────────┐ +│ 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 │ +└─────────────────────────────────────────────────────────┘ +``` -- Not the full OpenClaw gateway -- Not a replacement for a host application's outer runtime loop -- Not a second authoritative session registry -- Not a channel manager, cron daemon, control plane, or desktop automation environment + + + + + +
-## Design Principles +### What it does -- **Session-first API**: the host bootstraps the SDK once, then creates and reuses sessions explicitly -- **Host-owned persistence**: the host decides where session files, state files, and raw event logs live -- **Execution fidelity**: tool-call identity, hosted-tool resume boundaries, and tool-result ordering are preserved -- **Failure containment**: the SDK stays behind an engine-gated loader path in the host -- **Traceability**: extracted upstream files are tracked through a provenance manifest and verification scripts +- **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 -## Host / SDK Boundary + -### SDK responsibilities +### What makes it different -- Create and reuse agent sessions -- Stream assistant, reasoning, tool, hosted-tool, compaction, and usage events -- Preserve `tool_call`, `tool_result`, and `tool_error` semantics -- Resolve embedded provider/auth/plugin/tool behavior -- Start and stop registered stdio MCP runtimes for active runs -- Emit canonical host-facing logs and optional raw stream events +- **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 -### Host responsibilities +
-- Profile roots and workspace roots -- Credentials and environment variables -- Canonical session metadata -- Channel ingress and egress -- Cross-engine continuity and owner-facing orchestration -- Which MCP servers are registered and enabled for a session +--- -This separation is intentional. The SDK does not introduce a new top-level runtime abstraction above the host. +## ⚡ Quick Start -## Public API +### 1. Install -The supported API surface is exported from [`src/index.ts`](./src/index.ts) and backed by the files under [`src/public/`](./src/public). +```bash +npm install general-agent-sdk +``` -### Factory +### 2. Set your API key -```ts +```bash +export ANTHROPIC_API_KEY="sk-ant-..." + +# Optional: use a proxy +# export ANTHROPIC_BASE_URL="https://your-proxy.example.com" +``` + +### 3. Build your first agent + +```typescript import { createGeneralAgentSdk } from "general-agent-sdk"; +import { randomUUID } from "node:crypto"; +import path from "node:path"; +import os from "node:os"; +// Initialize the SDK const sdk = await createGeneralAgentSdk({ - workspaceDir, - stateDir, - agentDir, + workspaceDir: process.cwd(), + stateDir: path.join(process.cwd(), ".agent-state"), + agentDir: path.join(process.cwd(), ".agent"), profileId: "default", pluginMode: "disabled", - logger, - sessionStore, - hostedTools, - env: process.env, - tools: { - web: { - fetch: { - firecrawl: { - apiKey: process.env.FIRECRAWL_API_KEY, - }, - }, - search: { - apiKey: process.env.BRAVE_SEARCH_API_KEY, - }, + logger: { + onDebug() {}, onInfo() {}, onWarn() {}, onError() {}, + }, + sessionStore: { + async load() { return null; }, + async save() {}, + async resolveSessionFile(id) { + return path.join(os.tmpdir(), `${id.sessionId}.jsonl`); }, }, }); -``` - -### Session creation -```ts +// Create a session const session = sdk.createSession({ - identity: { - mode: "general", - sessionId: "sess-general", - sessionKey: "host:default:general", - }, - systemPrompt: "Use the finish tool immediately.", - modelRef: "openai/gpt-5.4", - sessionFile, - authProfileId: "enterprise-default", - rawEventLogPath, -}); -``` - -### Session lifecycle - -The SDK can enumerate stored sessions, reopen them by `sessionId`, continue a known identity, fork a stored transcript into a new session, and read persisted transcript history. - -```ts -const sessions = await sdk.listSessions(); -const resumed = await sdk.resumeSession("sess-general"); -const continued = await sdk.continueSession({ - identity: { - mode: "general", - sessionId: "sess-general", - sessionKey: "host:default:general", - }, -}); -const forked = await sdk.forkSession("sess-general", { - identity: { - mode: "general", - sessionId: "sess-general-fork", - sessionKey: "host:default:general-fork", - }, - sessionFile: forkSessionFile, + 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"), }); -const history = await sdk.readSessionHistory("sess-general"); -``` -### Turn streaming - -```ts +// Stream a conversation for await (const event of session.streamTurn({ role: "user", - content: [{ type: "text", text: "finish now" }], + content: [{ type: "text", text: "What files are in the current directory?" }], })) { - // host consumes normalized GeneralAgentStreamEvent values + switch (event.kind) { + 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; + } } -``` -### Hosted-tool resume - -When the SDK emits a hosted-tool call, the host must execute the hosted tool and resume the same session with the same `callId`. - -```ts -for await (const event of session.submitHostedToolResult({ - callId, - output: { ok: true }, -})) { - // resumed stream continues with the same execution context -} +await sdk.shutdown(); ``` -Hosted tools currently force sequential tool execution inside the vendored loop. That keeps same-run suspend/resume robust for hosted tools such as `finish`. - -Across SDK recreation, the runtime can recover both single-tool and multi-tool hosted-tool suspensions. When the assistant issues multiple tool calls and one is a hosted tool, the SDK snapshots the full context and resumes correctly after restart. +**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. -### Hooks +--- -The SDK now exposes an OpenClaw-aligned hook surface for embedded-agent flows. Runtime-managed hooks currently include pre-run model/prompt hooks, `llm_input`, `agent_end`, `llm_output`, tool hooks, transcript persist hooks, and session lifecycle hooks. Host-bridged hooks such as `message_sending`, `message_sent`, `message_received`, `inbound_claim`, `before_dispatch`, `gateway_start`, and `gateway_stop` can be emitted directly through the SDK. +## 🎯 Core Concepts -```ts -const result = await sdk.emitHook({ - hookName: "message_sending", - event: { - to: "channel:123", - content: "hello", - }, - context: { - channelId: "discord", - }, -}); -``` +### The Event Stream -The public hook registry accepts the full `GeneralAgentHookRegistration` union, including: - -- `before_model_resolve` -- `before_prompt_build` -- `before_agent_start` -- `llm_input` -- `llm_output` -- `agent_end` -- `before_compaction` -- `after_compaction` -- `before_reset` -- `inbound_claim` -- `message_received` -- `message_sending` -- `message_sent` -- `before_tool_call` -- `after_tool_call` -- `tool_result_persist` -- `before_message_write` -- `session_start` -- `session_end` -- `subagent_spawning` -- `subagent_delivery_target` -- `subagent_spawned` -- `subagent_ended` -- `gateway_start` -- `gateway_stop` -- `before_dispatch` - -All SDK-native hooks listed above are now auto-emitted by the runtime at the appropriate lifecycle points. This includes `before_reset`, compaction hooks (`before_compaction` / `after_compaction`), and subagent lifecycle hooks (`subagent_spawning` / `subagent_delivery_target` / `subagent_spawned` / `subagent_ended`). Host-bridged hooks such as `gateway_start`, `gateway_stop`, `inbound_claim`, `message_received`, `message_sending`, `message_sent`, and `before_dispatch` remain available through the `sdk.emitHook(...)` dispatch path. - -### Dynamic MCP servers - -The session can register dynamic MCP servers. The current runtime supports local `stdio` MCP servers and injects their tools into the same vendored loop as built-ins and hosted tools. - -```ts -session.setDynamicMcpServers({ - echo_server: { - transport: "stdio", - command: process.execPath, - args: ["/abs/path/to/echo-server.mjs"], - }, -}); +Every interaction returns an `AsyncIterable`. No callbacks, no observers — just a `for await` loop: -const query = session.getCurrentQuery(); -const status = await query?.mcpServerStatus?.(); -await query?.toggleMcpServer?.("echo_server", false); +```typescript +for await (const event of session.streamTurn(input)) { + // 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 +} ``` -Both MCP transport modes are supported: - -- `stdio`: local process servers -- `http`: remote HTTP-based MCP endpoints +### Hosted Tools — Your Code, Their Brain -Example with `http` transport: +Define tools that the AI can call. **You implement the logic**, the SDK handles the orchestration: -```ts -session.setDynamicMcpServers({ - remote_server: { - transport: "http", - url: "https://mcp.example.com/api", - headers: { Authorization: "Bearer token" }, - }, +```typescript +const sdk = await createGeneralAgentSdk({ + // ... other options ... + hostedTools: [ + { + name: "get_stock_price", + description: "Get real-time stock price", + inputSchema: { + type: "object", + properties: { symbol: { type: "string" } }, + required: ["symbol"], + }, + }, + ], }); + +// Handle tool calls +for await (const event of session.streamTurn(userMessage)) { + if (event.kind === "hosted_tool_call") { + // SDK automatically suspends here ⏸️ + + // You execute your logic + const price = await fetchStockPrice(event.input.symbol); + + // Resume the agent with the result ▶️ + for await (const resumed of session.submitHostedToolResult({ + callId: event.callId, + output: { price, currency: "USD" }, + })) { + if (resumed.kind === "assistant_delta") process.stdout.write(resumed.text); + } + break; + } +} ``` -### Session reset +### Multi-Turn Sessions -A session can be reset to clear its message history, usage state, and pending hosted-tool state while preserving the session identity and configuration. This is useful when the host wants to start fresh within the same session without creating a new one. +Sessions automatically maintain conversation history. The agent remembers everything: -```ts -await session.reset("context_overflow"); -``` - -The reset fires a `before_reset` hook before clearing state, allowing hooks to observe the outgoing transcript. +```typescript +// Turn 1 +await consume(session.streamTurn({ + role: "user", + content: [{ type: "text", text: "My name is Alice and I like TypeScript." }], +})); -### Compaction +// Turn 2 — the agent remembers! +await consume(session.streamTurn({ + role: "user", + content: [{ type: "text", text: "What's my name and what do I like?" }], +})); +// → "Your name is Alice and you like TypeScript." +``` -The SDK supports runtime compaction to manage context window pressure. Compaction can be triggered manually or automatically based on token usage thresholds. +### Hooks — Intercept Everything -```ts -// Manual compaction -await session.requestCompaction(); +26 hooks let you observe, modify, or block any part of the agent lifecycle: -// Automatic compaction when usage exceeds threshold -await session.maybeCompactByTokens({ - usedPctThreshold: 85, // compact when context is 85% full - cooldownMs: 60_000, // minimum 60s between compactions +```typescript +const sdk = await createGeneralAgentSdk({ + // ... + hooks: [ + // Dynamically switch models + { + pluginId: "my-app", + hookName: "before_model_resolve", + handler: (event) => ({ + modelOverride: isComplexTask(event.prompt) + ? "claude-opus-4-20250514" + : "claude-sonnet-4-20250514", + }), + }, + // Block dangerous tool calls + { + pluginId: "my-app", + hookName: "before_tool_call", + handler: (event) => { + if (event.toolName === "exec" && event.params.command?.includes("rm -rf")) { + return { block: true, blockReason: "Dangerous command blocked" }; + } + }, + }, + // Audit all LLM calls + { + pluginId: "my-app", + hookName: "llm_output", + handler: (event) => { + console.log(`[audit] ${event.model}: ${event.usage?.input}in/${event.usage?.output}out tokens`); + }, + }, + ], }); ``` -Compaction truncates older messages and replaces them with a concise summary, keeping the most recent conversation context intact. The SDK emits `compaction_started` and `compaction_finished` stream events and fires `before_compaction` / `after_compaction` hooks during the process. +--- -The context window size is resolved dynamically based on the model (e.g., 200K for Claude models, 128K for GPT-4o, 1M+ for Gemini models). +## 🧰 Built-in Tools -### Subagents +The agent comes pre-loaded with powerful tools: -The `subagents` tool is a first-class core built-in. When the agent calls it, the SDK automatically creates a child `GeneralAgentSdkSession` with: +| 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 | -- **Independent message history** — the child session has its own transcript, isolated from the parent -- **Scoped instructions** — the child receives its own system prompt via the `instructions` parameter -- **Scoped tool access** — the child inherits the parent's tools except `subagents` itself (preventing infinite recursion). An optional `allowedTools` parameter further restricts the child's tool set. -- **Parent/child coordination** — the parent's agent loop blocks while the child runs to completion, then receives the child's output as the tool result +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. -All 4 subagent lifecycle hooks fire automatically: -- `subagent_spawning` — before creation (can block with `{ status: "error" }`) -- `subagent_delivery_target` — after creation, before execution -- `subagent_spawned` — after child session is ready -- `subagent_ended` — after child completes (with `outcome: "ok"` or `"error"`) +--- -### File checkpoints +## 🔌 MCP Integration -File mutation tools automatically create SDK-managed checkpoints before successful `write`, `edit`, and `apply_patch` calls. Checkpoints are Git-independent and can be rewound through the session API. +Plug in any [Model Context Protocol](https://modelcontextprotocol.io/) server: -```ts -const checkpoints = await session.listCheckpoints(); -await session.restoreCheckpoint(checkpoints[0]!.id); -``` +```typescript +// Local process +session.setDynamicMcpServers({ + filesystem: { + transport: "stdio", + command: "npx", + args: ["-y", "@modelcontextprotocol/server-filesystem", "/data"], + }, +}); -Restoring a checkpoint rewinds that checkpoint and any newer checkpoints, so rollback stays linear and predictable. +// Remote HTTP endpoint +session.setDynamicMcpServers({ + my_api: { + transport: "http", + url: "https://mcp.example.com/api", + headers: { Authorization: "Bearer token" }, + }, +}); +``` -## Event Model +MCP tools show up alongside built-in tools. The agent uses them seamlessly. -`GeneralAgentStreamEvent` currently supports: +--- -- `assistant_delta` -- `reasoning_delta` -- `reasoning_end` -- `tool_call` -- `tool_result` -- `tool_error` -- `hosted_tool_call` -- `usage_snapshot` -- `compaction_started` -- `compaction_finished` -- `turn_complete` +## 🤖 Subagents -The host is expected to normalize these events into its own runtime contract when necessary. +The agent can spawn child agents to divide and conquer: -## Persistence Model +```typescript +const session = sdk.createSession({ + // ... + systemPrompt: `You are a project manager. + Use the subagents tool to delegate tasks to specialists.`, +}); -The SDK does not own canonical session identity. Instead, the host provides a `sessionStore` adapter and resolves the session file path explicitly. +// The agent will autonomously: +// 1. Break the task into subtasks +// 2. Spawn child agents with scoped instructions +// 3. Collect results +// 4. Synthesize a final answer +``` -Key persistence properties: +Each subagent gets its own independent message history and scoped tool access. The `subagents` tool is excluded from children to prevent infinite recursion. -- provider-specific transcripts are allowed -- provider-specific raw stream logs are allowed -- both must remain under host-owned directories -- session identity must come from the host -- no parallel SDK-owned global session registry is introduced -- pending hosted-tool wait states and reconstructible continuation snapshots may be persisted when the runtime can resume them safely +--- -The persistence adapter lives in [`src/public/persistence.ts`](./src/public/persistence.ts). +## 📊 Session Management -## Logging Model +```typescript +// Create +const session = sdk.createSession({ ... }); -The SDK emits canonical host-facing log events through `GeneralAgentHostLogger`. +// Resume by ID +const resumed = await sdk.resumeSession("session-123"); -Supported log categories: +// Fork (branch from existing conversation) +const forked = await sdk.forkSession("session-123", { ... }); -- `system_prompt` -- `tool_call` -- `tool_result` -- `assistant` -- `system` -- `provider_debug` +// List all sessions +const sessions = await sdk.listSessions(); -The logger can also receive raw structured stream events through `onRawStreamEvent()` when the host wants a low-level audit trail. +// Read transcript history +const history = await sdk.readSessionHistory("session-123"); -## Plugin and Tool Policy +// Reset (clear history, keep config) +await session.reset("starting_fresh"); -The factory accepts: +// Check token usage +const usage = session.getUsageSnapshot(); +// → { usedInputTokens: 1234, contextWindow: 200000, usedPct: 0.6 } +``` -- `pluginMode: "disabled" | "allowlisted" | "full-embedded"` -- `enabledPluginIds?: string[]` -- `hostedTools?: GeneralAgentHostedToolDefinition[]` -- `tools?.web?.fetch?: GeneralAgentWebFetchToolOptions` -- `tools?.web?.search?: GeneralAgentWebSearchToolOptions` +### Context Compaction -This makes the host's trust boundary explicit. The SDK can preserve OpenClaw's plugin and tool semantics, but the host decides how much of that surface is enabled in embedded mode. +Long conversations don't overflow — the SDK compacts automatically: -`web_search` is now assembled as a built-in tool by default. Internally it follows a source-synced provider runtime: Brave is selected when credentials are available, and the SDK keeps the tool present by falling back to the bundled keyless DuckDuckGo provider when no Brave key is configured. +```typescript +await session.maybeCompactByTokens({ + usedPctThreshold: 85, // trigger at 85% usage + cooldownMs: 60_000, // min 60s between compactions +}); +``` -Plugin scope is intentionally narrow: in this repository, plugin controls are reserved for web-related capabilities only. General-purpose non-web plugin loading is not a product goal for the SDK; other extensibility should go through built-in tools, hosted tools, MCP, or hooks. +### File Checkpoints -## Repository Layout +Every file write creates an automatic checkpoint. Roll back anytime: -```text -src/ - index.ts # top-level export surface - public/ # supported public API - core/ # SDK-owned runtime implementation - upstream/openclaw/ # extracted upstream subset only -manifests/ - upstream-provenance.json # machine-readable provenance map -scripts/ - sync-from-openclaw.mjs - verify-upstream-snapshot.mjs -tests/ - contract/ - integration/ - unit/ -docs/ - superpowers/ - specs/ - plans/ +```typescript +const checkpoints = await session.listCheckpoints(); +await session.restoreCheckpoint(checkpoints[0].id); ``` -## Development +--- -Install dependencies: +## 📖 Documentation -```bash -pnpm install -``` +| 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 | -Run the main verification steps: +All examples are **runnable** — just set your API key and go: ```bash -pnpm run check -pnpm run build -pnpm run test -pnpm run test:e2e -node scripts/verify-upstream-snapshot.mjs +export ANTHROPIC_API_KEY="sk-ant-..." +npx tsx "SDK DOCS/01-hello-world.ts" ``` -## Upstream Provenance +--- -This repository deliberately does not mirror the entire upstream OpenClaw source tree. +## 🏗️ Architecture -Instead: - -- copied upstream snapshots live under `src/upstream/openclaw/` -- source-synced adapted files may also live in normal SDK paths such as `src/tools/` and `src/security/` -- each extracted file is tracked in [`manifests/upstream-provenance.json`](./manifests/upstream-provenance.json) -- provenance can be revalidated with `node scripts/verify-upstream-snapshot.mjs` +``` +general-agent-sdk/ +├── src/ +│ ├── index.ts → Package entry point +│ ├── public/ → Stable public API types +│ │ ├── sdk.ts → createGeneralAgentSdk() +│ │ ├── session.ts → GeneralAgentSession interface +│ │ ├── events.ts → Stream event types +│ │ ├── hooks.ts → 26 hook definitions +│ │ ├── types.ts → Shared types +│ │ ├── host-tools.ts → Hosted tool types +│ │ └── persistence.ts → Storage adapter +│ ├── core/ → Runtime implementation +│ │ ├── embedded-runner/ → Session + factory +│ │ ├── compaction/ → Context compaction +│ │ ├── mcp/ → MCP client (stdio + http) +│ │ ├── model/ → Model context windows +│ │ ├── plugins/ → Hook runner +│ │ ├── sessions/ → Metadata + transcript repair +│ │ └── checkpoints/ → File checkpoint manager +│ ├── tools/ → Built-in tool implementations +│ ├── loop/ → Agent execution loop +│ └── providers/ → LLM provider adapters +├── SDK DOCS/ → Examples + API reference +├── tests/ → 133 tests (unit/integration/contract/e2e) +└── manifests/ → Upstream provenance tracking +``` -This is a hard boundary, not just documentation. +--- -## Host Integration +## 🔧 Development -A host application typically keeps the following responsibilities outside the SDK: +```bash +# Install +pnpm install -- canonical `session.json` -- dual-session switching -- channel ingress and owner notifications -- cross-engine continuity journal -- top-level profile and environment management +# Type check +pnpm run check -That design keeps the General Agent SDK as an execution backend rather than turning the host into an OpenClaw runtime shell. +# Build +pnpm run build -## Specifications and Implementation Notes +# Run tests +pnpm run test # 133 unit + integration tests +pnpm run test:e2e # package smoke test -- Design spec: [`docs/superpowers/specs/2026-03-31-general-agent-sdk-source-sync-design.md`](./docs/superpowers/specs/2026-03-31-general-agent-sdk-source-sync-design.md) -- Implementation plan: [`docs/superpowers/plans/2026-03-31-general-agent-sdk-source-sync.md`](./docs/superpowers/plans/2026-03-31-general-agent-sdk-source-sync.md) +# Verify upstream provenance +node scripts/verify-upstream-snapshot.mjs +``` -These documents are the source of truth for architecture, boundary rules, continuity requirements, and integration sequencing. +--- + +## 📋 Event Reference + +| Event | Payload | When | +|-------|---------|------| +| `assistant_delta` | `{ text }` | Each streaming text chunk | +| `reasoning_delta` | `{ text }` | Model thinking (extended thinking) | +| `reasoning_end` | — | Thinking complete | +| `tool_call` | `{ callId, toolName, input }` | Built-in tool invoked | +| `tool_result` | `{ callId, toolName, output }` | Tool returned result | +| `tool_error` | `{ callId, toolName, error }` | Tool failed | +| `hosted_tool_call` | `{ callId, toolName, input }` | Your tool requested (SDK suspends) | +| `usage_snapshot` | `{ snapshot }` | Token usage update | +| `compaction_started` | `{ reason }` | Context compaction begins | +| `compaction_finished` | `{ reason, tokensAfter? }` | Compaction complete | +| `turn_complete` | `{ stopReason }` | Turn finished | + +--- + +## 🪝 Hook Reference + +
+19 SDK-native hooks (auto-fired by runtime) + +| Hook | Can modify? | Description | +|------|:-----------:|-------------| +| `before_model_resolve` | ✅ | Override model selection | +| `before_prompt_build` | ✅ | Inject context into prompts | +| `before_agent_start` | ✅ | Final pre-run modifications | +| `llm_input` | — | Observe LLM request | +| `llm_output` | — | Observe LLM response + usage | +| `agent_end` | — | Run completed | +| `before_tool_call` | ✅ | Modify args or block execution | +| `after_tool_call` | — | Observe tool result | +| `tool_result_persist` | ✅ | Modify persisted tool result | +| `before_message_write` | ✅ | Modify or block transcript writes | +| `session_start` | — | Session first used | +| `session_end` | — | Session done | +| `before_compaction` | — | Compaction starting | +| `after_compaction` | — | Compaction finished | +| `before_reset` | — | Session about to reset | +| `subagent_spawning` | ✅ | Block subagent creation | +| `subagent_delivery_target` | ✅ | Override delivery routing | +| `subagent_spawned` | — | Child agent created | +| `subagent_ended` | — | Child agent finished | + +
+ +
+7 Host-bridged hooks (triggered via sdk.emitHook()) + +| Hook | Description | +|------|-------------| +| `inbound_claim` | Incoming message routing | +| `before_dispatch` | Pre-dispatch filtering | +| `message_received` | Message received | +| `message_sending` | Modify/cancel outgoing messages | +| `message_sent` | Message delivery confirmation | +| `gateway_start` | Gateway lifecycle | +| `gateway_stop` | Gateway shutdown | + +
+ +--- + +## License + +[MIT](./LICENSE) — built by [BabelCloud](https://github.com/babelcloud) From b82d27213b3dea8e7d17be5243ae31c09ba1986b Mon Sep 17 00:00:00 2001 From: Redux0223 Date: Tue, 31 Mar 2026 21:08:08 +0800 Subject: [PATCH 2/2] docs: add custom SVG assets and redesign README with visual polish - Hero banner: dark theme with gradient glow, circuit icon, feature pills - Architecture diagram: host/SDK layered view with tools, runtime, providers - Event flow timeline: visual stream event sequence with loop-back indicator - Feature grid: 8-card layout covering all major capabilities - Comparison chart: side-by-side vs other frameworks with check/cross marks - Quick start header: gradient accent bar section divider - README rewritten to integrate all SVG graphics with refined typography Made-with: Cursor --- .github/assets/architecture.svg | 220 +++++++++ .github/assets/event-flow.svg | 106 +++++ .github/assets/features.svg | 66 +++ .github/assets/hero-banner.svg | 131 ++++++ .github/assets/quick-start-header.svg | 21 + .github/assets/why-different.svg | 82 ++++ README.md | 620 ++++++++++++-------------- 7 files changed, 921 insertions(+), 325 deletions(-) create mode 100644 .github/assets/architecture.svg create mode 100644 .github/assets/event-flow.svg create mode 100644 .github/assets/features.svg create mode 100644 .github/assets/hero-banner.svg create mode 100644 .github/assets/quick-start-header.svg create mode 100644 .github/assets/why-different.svg diff --git a/.github/assets/architecture.svg b/.github/assets/architecture.svg new file mode 100644 index 0000000..2e00cf0 --- /dev/null +++ b/.github/assets/architecture.svg @@ -0,0 +1,220 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + How It Works + + + + YOUR APPLICATION + + + + + Credentials + API keys, env vars + + + + Persistence + Session files, logs + + + + Hosted Tools + Your custom logic + + + + Hooks + 26 lifecycle points + + + + MCP Servers + stdio + HTTP + + + + + + + + createSession() + + + + GENERAL AGENT SDK + + + + + Session Factory + create resume fork + list reset history + + + + + + + + + + + Agent Execution Loop + + + + + User + + + + + LLM + + + + + Tool + + + + + LLM + + + + +  + + + + + + + Event Stream + assistant_delta tool_call + hosted_tool_call turn_complete + + + + TOOLS + + + + + = + read + files + + +  + write + create + + + =' + edit + surgical + + + + exec + shell + + + = + web_search + search + + + < + web_fetch + pages + + + > + subagents + delegate + + + = + apply_patch + diff + + + + RUNTIME + + + + Context Compaction + Auto-manage context window + + + + File Checkpoints + Auto-rollback on mutations + + + + Hook Runner + 19 SDK-native + 7 host-bridged + + + + LLM Providers + Claude GPT Gemini + + + + You own the boundary. The SDK owns the execution. + + diff --git a/.github/assets/event-flow.svg b/.github/assets/event-flow.svg new file mode 100644 index 0000000..1e6297b --- /dev/null +++ b/.github/assets/event-flow.svg @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + Event Stream Timeline + + + + + + + + + U + + streamTurn() + user message + + + + + = + + reasoning + _delta + + + + +  + + assistant + _delta + + + + + =' + + tool_call + built-in or hosted + + + + +  + + tool_result + output returned + + + + loops until done + + + + + = + + usage + _snapshot + + + + + < + + turn + _complete + + + + + + + + + + + + + for await (const event of session.streamTurn(input)) { ... } + All events delivered via a single AsyncIterable  no callbacks, no observers + + diff --git a/.github/assets/features.svg b/.github/assets/features.svg new file mode 100644 index 0000000..87eee14 --- /dev/null +++ b/.github/assets/features.svg @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + Real-time Streaming + Every token, tool call, result as events + + + + =' + 8 Built-in Tools + read write exec web + more + + + + < + Hosted Tools + Your code, suspend/resume safe + + + + = + Multi-turn Memory + Automatic conversation history + + + + + > + 26 Lifecycle Hooks + Intercept model, tools, sessions + + + + = + MCP Integration + stdio + HTTP, hot-pluggable + + + + > + Subagent Runtime + Scoped child agents with isolation + + + + = + Production Ready + Compaction, checkpoints, restart-safe + + diff --git a/.github/assets/hero-banner.svg b/.github/assets/hero-banner.svg new file mode 100644 index 0000000..ca27a1e --- /dev/null +++ b/.github/assets/hero-banner.svg @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + General Agent SDK + + + Build AI agents that actually do things. + + + + + + + + + Streaming + + + Tools + + + Sessions + + + Hooks + + + MCP + + + Subagents + + + + + + v0.1.0 MIT + + + diff --git a/.github/assets/quick-start-header.svg b/.github/assets/quick-start-header.svg new file mode 100644 index 0000000..1503345 --- /dev/null +++ b/.github/assets/quick-start-header.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + Get Started in 60 Seconds + + diff --git a/.github/assets/why-different.svg b/.github/assets/why-different.svg new file mode 100644 index 0000000..e33741e --- /dev/null +++ b/.github/assets/why-different.svg @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + VS + + + Other Agent Frameworks + + + + +  + Chat completion wrappers + + + +  + Framework-owned state you can't control + + + +  + Manual tool loop management + + + +  + Restart = lost state + + + +  + Callback-heavy APIs, hard to reason about + + + + + General Agent SDK + + + + +  + Full autonomous execution engine + + + +  + Host-owned persistence & credentials + + + +  + Agent runs tools autonomously until done + + + +  + Restart-safe hosted tool suspend/resume + + + +  + Single for-await loop, zero callbacks + + + + diff --git a/README.md b/README.md index 75e1640..a6fdf07 100644 --- a/README.md +++ b/README.md @@ -1,108 +1,85 @@
-# General Agent SDK + + + + General Agent SDK + -### Build AI agents that actually *do things*. - -**Stream responses. Call tools. Manage sessions. Ship to production.** +
-[![npm version](https://img.shields.io/npm/v/general-agent-sdk?style=flat-square&color=cb3837&label=npm)](https://www.npmjs.com/package/general-agent-sdk) -[![License: MIT](https://img.shields.io/badge/license-MIT-blue?style=flat-square)](./LICENSE) -[![TypeScript](https://img.shields.io/badge/TypeScript-5.7-3178c6?style=flat-square&logo=typescript&logoColor=white)](https://www.typescriptlang.org/) -[![Node.js](https://img.shields.io/badge/Node.js-%E2%89%A522.14-339933?style=flat-square&logo=nodedotjs&logoColor=white)](https://nodejs.org/) -[![Tests](https://img.shields.io/badge/tests-133%20passing-brightgreen?style=flat-square)](./tests) +[![npm](https://img.shields.io/npm/v/general-agent-sdk?style=for-the-badge&logo=npm&logoColor=white&color=CB3837&label=)](https://www.npmjs.com/package/general-agent-sdk) +  +[![TypeScript](https://img.shields.io/badge/TypeScript-5.7-3178c6?style=for-the-badge&logo=typescript&logoColor=white)](https://www.typescriptlang.org/) +  +[![License](https://img.shields.io/badge/MIT-license-blue?style=for-the-badge)](./LICENSE) +  +[![Tests](https://img.shields.io/badge/133-tests_passing-22c55e?style=for-the-badge&logo=vitest&logoColor=white)](./tests) +  +[![Node](https://img.shields.io/badge/Node-%E2%89%A522-339933?style=for-the-badge&logo=nodedotjs&logoColor=white)](https://nodejs.org/)
-``` -npm install general-agent-sdk -``` +**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/) -[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. **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. +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**: -``` -┌─────────────────────────────────────────────────────────┐ -│ 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 │ -└─────────────────────────────────────────────────────────┘ -``` +
+
- - - - - -
+Comparison -### 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 +> **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. -
+
-### 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 +
-
+Features ---- +
-## ⚡ Quick Start +
+ + + +
-### 1. Install +Quick Start + +
+ +### Step 1 — Install ```bash npm install general-agent-sdk ``` -### 2. Set your API key +### Step 2 — Set your API key ```bash export ANTHROPIC_API_KEY="sk-ant-..." -# Optional: use a proxy +# Optional: use a custom endpoint # export ANTHROPIC_BASE_URL="https://your-proxy.example.com" ``` -### 3. Build your first agent +### Step 3 — Run your first agent ```typescript import { createGeneralAgentSdk } from "general-agent-sdk"; @@ -110,16 +87,14 @@ import { randomUUID } from "node:crypto"; import path from "node:path"; import os from "node:os"; -// Initialize the SDK +// 1. Initialize 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() {}, @@ -129,198 +104,225 @@ const sdk = await createGeneralAgentSdk({ }, }); -// Create a session +// 2. Create a session const session = sdk.createSession({ - 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"), + 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"), }); -// Stream a conversation +// 3. Stream a turn — the agent calls tools autonomously for await (const event of session.streamTurn({ role: "user", - content: [{ type: "text", text: "What files are in the current directory?" }], + content: [{ type: "text", text: "List the files in the current directory" }], })) { switch (event.kind) { - 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; + 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; } } await sdk.shutdown(); ``` -**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. +> **That's it.** The agent autonomously reads the directory, reasons about the output, and streams a formatted answer — all in one `for await` loop. + +
--- + + ## 🎯 Core Concepts ### The Event Stream -Every interaction returns an `AsyncIterable`. No callbacks, no observers — just a `for await` loop: +Every turn returns an `AsyncIterable`. No callbacks, no observers — one loop handles everything: + +
+
+ +Event Flow + +

+
```typescript for await (const event of session.streamTurn(input)) { - // 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 + 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 ✅ + } } ``` -### Hosted Tools — Your Code, Their Brain +--- + +### 🎯 Hosted Tools — Your Code, The Model's Brain -Define tools that the AI can call. **You implement the logic**, the SDK handles the orchestration: +Define tools the AI can call. You implement the logic; the SDK orchestrates the lifecycle: ```typescript const sdk = await createGeneralAgentSdk({ - // ... other options ... - hostedTools: [ - { - name: "get_stock_price", - description: "Get real-time stock price", - inputSchema: { - type: "object", - properties: { symbol: { type: "string" } }, - required: ["symbol"], - }, + // ... + hostedTools: [{ + name: "get_weather", + description: "Get current weather for a city", + inputSchema: { + type: "object", + properties: { city: { type: "string" } }, + required: ["city"], }, - ], + }], }); -// Handle tool calls -for await (const event of session.streamTurn(userMessage)) { +for await (const event of session.streamTurn(userMsg)) { if (event.kind === "hosted_tool_call") { - // SDK automatically suspends here ⏸️ - - // You execute your logic - const price = await fetchStockPrice(event.input.symbol); + // SDK suspends automatically ⏸️ + const weather = await getWeather(event.input.city); - // Resume the agent with the result ▶️ - for await (const resumed of session.submitHostedToolResult({ + // Resume with your result ▶️ + for await (const e of session.submitHostedToolResult({ callId: event.callId, - output: { price, currency: "USD" }, + output: weather, })) { - if (resumed.kind === "assistant_delta") process.stdout.write(resumed.text); + if (e.kind === "assistant_delta") process.stdout.write(e.text); } break; } } ``` -### Multi-Turn Sessions +> **Restart-safe:** Hosted tool pauses survive process restarts. The SDK snapshots context and resumes correctly — even with multi-tool parallel calls. + +--- + +### 💬 Multi-Turn Memory -Sessions automatically maintain conversation history. The agent remembers everything: +Sessions automatically maintain conversation history: ```typescript // Turn 1 -await consume(session.streamTurn({ +for await (const e of session.streamTurn({ role: "user", - content: [{ type: "text", text: "My name is Alice and I like TypeScript." }], -})); + content: [{ type: "text", text: "My name is Alice. I'm building a TypeScript app." }], +})) { /* ... */ } -// Turn 2 — the agent remembers! -await consume(session.streamTurn({ +// Turn 2 — the agent remembers everything +for await (const e of session.streamTurn({ role: "user", - content: [{ type: "text", text: "What's my name and what do I like?" }], -})); -// → "Your name is Alice and you like TypeScript." + content: [{ type: "text", text: "What's my name and what am I building?" }], +})) { /* ... */ } +// ➜ "Your name is Alice and you're building a TypeScript app." ``` -### Hooks — Intercept Everything +--- + +### 🪝 Hooks — Intercept Everything -26 hooks let you observe, modify, or block any part of the agent lifecycle: +26 hooks let you observe, modify, or block any lifecycle event: ```typescript const sdk = await createGeneralAgentSdk({ - // ... hooks: [ - // Dynamically switch models + // 🔀 Dynamic model routing { pluginId: "my-app", hookName: "before_model_resolve", - handler: (event) => ({ - modelOverride: isComplexTask(event.prompt) - ? "claude-opus-4-20250514" - : "claude-sonnet-4-20250514", + handler: (ev) => ({ + modelOverride: needsPower(ev) ? "claude-opus-4-20250514" : "claude-sonnet-4-20250514", }), }, - // Block dangerous tool calls + + // 🛡️ Safety guardrails { pluginId: "my-app", hookName: "before_tool_call", - handler: (event) => { - if (event.toolName === "exec" && event.params.command?.includes("rm -rf")) { + handler: (ev) => { + if (ev.toolName === "exec" && ev.params.command?.includes("rm -rf")) return { block: true, blockReason: "Dangerous command blocked" }; - } }, }, - // Audit all LLM calls + + // 📊 Usage audit trail { pluginId: "my-app", hookName: "llm_output", - handler: (event) => { - console.log(`[audit] ${event.model}: ${event.usage?.input}in/${event.usage?.output}out tokens`); + handler: (ev) => { + db.insert({ model: ev.model, tokens: ev.usage?.input + ev.usage?.output }); }, }, ], }); ``` ---- +
+📋 Full hook reference (19 SDK-native + 7 host-bridged) -## 🧰 Built-in Tools +
+ +**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 | -The agent comes pre-loaded with powerful tools: +**Host-bridged hooks** — you trigger these via `sdk.emitHook()`: -| 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 | +| 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 | -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: +Plug in any [Model Context Protocol](https://modelcontextprotocol.io/) server — tools appear alongside built-ins: ```typescript -// Local process session.setDynamicMcpServers({ + // Local process (stdio) filesystem: { transport: "stdio", command: "npx", args: ["-y", "@modelcontextprotocol/server-filesystem", "/data"], }, -}); -// Remote HTTP endpoint -session.setDynamicMcpServers({ + // Remote endpoint (HTTP) my_api: { transport: "http", url: "https://mcp.example.com/api", @@ -329,222 +331,190 @@ session.setDynamicMcpServers({ }); ``` -MCP tools show up alongside built-in tools. The agent uses them seamlessly. - --- -## 🤖 Subagents +### 🤖 Subagents -The agent can spawn child agents to divide and conquer: +The agent can spawn scoped child agents to divide and conquer complex tasks: ```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. Break the task into subtasks -// 2. Spawn child agents with scoped instructions -// 3. Collect results +// 1. Analyze the task +// 2. Spawn child agents with scoped instructions + tools +// 3. Collect results from each child // 4. Synthesize a final answer ``` -Each subagent gets its own independent message history and scoped tool access. The `subagents` tool is excluded from children to prevent infinite recursion. +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`. --- -## 📊 Session Management +### 📊 Session Management ```typescript -// 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 } +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 ``` -### Context Compaction - -Long conversations don't overflow — the SDK compacts automatically: +**Context Compaction** — long conversations don't overflow: ```typescript await session.maybeCompactByTokens({ - usedPctThreshold: 85, // trigger at 85% usage + usedPctThreshold: 85, // trigger at 85% context usage cooldownMs: 60_000, // min 60s between compactions }); ``` -### File Checkpoints - -Every file write creates an automatic checkpoint. Roll back anytime: +**File Checkpoints** — every write creates an automatic rollback point: ```typescript const checkpoints = await session.listCheckpoints(); await session.restoreCheckpoint(checkpoints[0].id); ``` +
+ --- -## 📖 Documentation + -| 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 | +## 🏗 Architecture -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" -``` +Architecture ---- +

+
-## 🏗️ Architecture +
+📁 Repository structure ``` general-agent-sdk/ ├── src/ -│ ├── index.ts → Package entry point -│ ├── public/ → Stable public API types -│ │ ├── sdk.ts → createGeneralAgentSdk() -│ │ ├── session.ts → GeneralAgentSession interface -│ │ ├── events.ts → Stream event types -│ │ ├── hooks.ts → 26 hook definitions -│ │ ├── types.ts → Shared types -│ │ ├── host-tools.ts → Hosted tool types -│ │ └── persistence.ts → Storage adapter -│ ├── core/ → Runtime implementation -│ │ ├── embedded-runner/ → Session + factory -│ │ ├── compaction/ → Context compaction -│ │ ├── mcp/ → MCP client (stdio + http) -│ │ ├── model/ → Model context windows -│ │ ├── plugins/ → Hook runner -│ │ ├── sessions/ → Metadata + transcript repair -│ │ └── checkpoints/ → File checkpoint manager -│ ├── tools/ → Built-in tool implementations -│ ├── loop/ → Agent execution loop -│ └── providers/ → LLM provider adapters -├── SDK DOCS/ → Examples + API reference -├── tests/ → 133 tests (unit/integration/contract/e2e) -└── manifests/ → Upstream provenance tracking +│ ├── index.ts → Package entry point +│ ├── public/ → Stable public API surface +│ │ ├── sdk.ts → createGeneralAgentSdk() +│ │ ├── session.ts → GeneralAgentSession +│ │ ├── events.ts → GeneralAgentStreamEvent +│ │ ├── hooks.ts → 26 hook type definitions +│ │ ├── types.ts → Shared types +│ │ ├── host-tools.ts → Hosted tool definitions +│ │ └── persistence.ts → Storage adapter interface +│ ├── core/ +│ │ ├── embedded-runner/ → Session factory + runtime +│ │ ├── compaction/ → Context window compaction +│ │ ├── mcp/ → MCP client (stdio + HTTP) +│ │ ├── model/ → Model context window resolution +│ │ ├── plugins/ → Hook runner engine +│ │ ├── sessions/ → Metadata index + transcript repair +│ │ └── checkpoints/ → File checkpoint manager +│ ├── tools/ → Built-in tool implementations +│ ├── loop/ → Agent execution loop +│ └── providers/ → LLM provider adapters +├── SDK DOCS/ → Runnable examples + API reference +├── tests/ → 133 tests (unit / integration / contract / e2e) +└── manifests/ → Upstream provenance tracking ``` ---- +
-## 🔧 Development +--- -```bash -# Install -pnpm install +## 📋 Event Reference -# Type check -pnpm run check +| Event | Payload | Description | +|:------|:--------|:------------| +| `assistant_delta` | `{ text }` | Streaming text chunk from the model | +| `reasoning_delta` | `{ text }` | Extended thinking (chain-of-thought) | +| `reasoning_end` | — | Thinking phase complete | +| `tool_call` | `{ callId, toolName, input }` | Built-in tool invoked by the agent | +| `tool_result` | `{ callId, toolName, output }` | Tool execution result | +| `tool_error` | `{ callId, toolName, error }` | Tool execution failed | +| `hosted_tool_call` | `{ callId, toolName, input }` | **Your** tool requested — SDK suspends | +| `usage_snapshot` | `{ snapshot }` | Token usage update | +| `compaction_started` | `{ reason }` | Context compaction in progress | +| `compaction_finished` | `{ reason, tokensAfter? }` | Compaction complete | +| `turn_complete` | `{ stopReason }` | Turn finished | -# Build -pnpm run build +--- -# Run tests -pnpm run test # 133 unit + integration tests -pnpm run test:e2e # package smoke test +## 🧰 Built-in Tools -# Verify upstream provenance -node scripts/verify-upstream-snapshot.mjs -``` +| Tool | Description | Category | +|:-----|:------------|:---------| +| `read` | Read file contents with optional line ranges | File I/O | +| `write` | Create or overwrite files | File I/O | +| `edit` | Surgical string-replace edits | File I/O | +| `apply_patch` | Apply unified diff patches | File I/O | +| `exec` | Execute shell commands | System | +| `web_search` | Search the web (Brave / DuckDuckGo) | Web | +| `web_fetch` | Fetch and parse web pages | Web | +| `subagents` | Spawn scoped child agents | Orchestration | --- -## 📋 Event Reference +## 📖 Examples -| Event | Payload | When | -|-------|---------|------| -| `assistant_delta` | `{ text }` | Each streaming text chunk | -| `reasoning_delta` | `{ text }` | Model thinking (extended thinking) | -| `reasoning_end` | — | Thinking complete | -| `tool_call` | `{ callId, toolName, input }` | Built-in tool invoked | -| `tool_result` | `{ callId, toolName, output }` | Tool returned result | -| `tool_error` | `{ callId, toolName, error }` | Tool failed | -| `hosted_tool_call` | `{ callId, toolName, input }` | Your tool requested (SDK suspends) | -| `usage_snapshot` | `{ snapshot }` | Token usage update | -| `compaction_started` | `{ reason }` | Context compaction begins | -| `compaction_finished` | `{ reason, tokensAfter? }` | Compaction complete | -| `turn_complete` | `{ stopReason }` | Turn finished | +All examples are **runnable** TypeScript files: + +```bash +export ANTHROPIC_API_KEY="sk-ant-..." +npx tsx "SDK DOCS/01-hello-world.ts" +``` + +| # | Example | What it covers | +|:--|:--------|:---------------| +| 01 | [`hello-world.ts`](./SDK%20DOCS/01-hello-world.ts) | Minimal agent, first `streamTurn()` call | +| 02 | [`multi-turn-chat.ts`](./SDK%20DOCS/02-multi-turn-chat.ts) | Interactive REPL with memory | +| 03 | [`hosted-tools.ts`](./SDK%20DOCS/03-hosted-tools.ts) | Custom tool with suspend/resume | +| 04 | [`session-lifecycle.ts`](./SDK%20DOCS/04-session-lifecycle.ts) | Create, resume, fork, reset | +| 05 | [`hooks.ts`](./SDK%20DOCS/05-hooks.ts) | Lifecycle hooks in action | +| 06 | [`mcp-servers.ts`](./SDK%20DOCS/06-mcp-servers.ts) | Dynamic MCP integration | +| 07 | [`compaction.ts`](./SDK%20DOCS/07-compaction.ts) | Context window management | +| 08 | [`subagents.ts`](./SDK%20DOCS/08-subagents.ts) | Child agent delegation | + +> 📚 Full API documentation: [`SDK DOCS/API-REFERENCE.md`](./SDK%20DOCS/API-REFERENCE.md) --- -## 🪝 Hook Reference +## 🔧 Development -
-19 SDK-native hooks (auto-fired by runtime) - -| Hook | Can modify? | Description | -|------|:-----------:|-------------| -| `before_model_resolve` | ✅ | Override model selection | -| `before_prompt_build` | ✅ | Inject context into prompts | -| `before_agent_start` | ✅ | Final pre-run modifications | -| `llm_input` | — | Observe LLM request | -| `llm_output` | — | Observe LLM response + usage | -| `agent_end` | — | Run completed | -| `before_tool_call` | ✅ | Modify args or block execution | -| `after_tool_call` | — | Observe tool result | -| `tool_result_persist` | ✅ | Modify persisted tool result | -| `before_message_write` | ✅ | Modify or block transcript writes | -| `session_start` | — | Session first used | -| `session_end` | — | Session done | -| `before_compaction` | — | Compaction starting | -| `after_compaction` | — | Compaction finished | -| `before_reset` | — | Session about to reset | -| `subagent_spawning` | ✅ | Block subagent creation | -| `subagent_delivery_target` | ✅ | Override delivery routing | -| `subagent_spawned` | — | Child agent created | -| `subagent_ended` | — | Child agent finished | +```bash +pnpm install # Install dependencies +pnpm run check # Type check +pnpm run build # Build +pnpm run test # 133 unit + integration tests +pnpm run test:e2e # Package smoke test +node scripts/verify-upstream-snapshot.mjs # Verify provenance +``` -
+--- -
-7 Host-bridged hooks (triggered via sdk.emitHook()) +
-| Hook | Description | -|------|-------------| -| `inbound_claim` | Incoming message routing | -| `before_dispatch` | Pre-dispatch filtering | -| `message_received` | Message received | -| `message_sending` | Modify/cancel outgoing messages | -| `message_sent` | Message delivery confirmation | -| `gateway_start` | Gateway lifecycle | -| `gateway_stop` | Gateway shutdown | +
-
+**MIT** · Built by [BabelCloud](https://github.com/babelcloud) ---- +
-## License +If this project helps you build something cool, give it a ⭐ — it helps others find it too. -[MIT](./LICENSE) — built by [BabelCloud](https://github.com/babelcloud) +

+ +