llms-full.txt@docs · git:20260804.f9c61d2 · 2026-08-04 · sha256 f53f322948cbdc8b

llms-full.txt@docs git:20260804.f9c61d2C

Immutable. This exact content is served forever at /api/v1/blob/f53f322948cbdc8b.

# personaforge — Full Documentation

> This file is the complete, machine-readable documentation for personaforge (1.1.0). It is the concatenation of the guides and runbooks below, suitable for agentic crawlers and RAG ingestion.

_Generated by `node scripts/gen-runbooks.mjs`. Do not edit by hand._

## Table of contents

- adapters
- admin-api
- agents
- all-modules
- approval
- artifacts
- background-queues
- checkpoint
- code-mode
- comparisons
- compose
- compression
- concepts
- context-provider
- control-plane
- custom-adapter
- custom-tools
- database
- deep-research
- durable
- eval
- event-streaming
- events
- getting-started
- goals
- graph
- guardrails
- harness
- hitl
- hooks
- introduction
- learning-machine
- llm-router
- loaders
- mcp
- memory
- migration-agno
- migration-crewai
- migration-langchain
- migration-langgraph
- migration-mastra
- migration-vercel
- model-fallbacks
- monorepo-migration
- multi-tenancy
- observability
- orchestration
- output-parsers
- packages
- planner
- plugins
- processors
- production
- providers
- rag
- reasoning
- reasoning-tools
- registry
- retrieval-advanced
- runnable
- scheduler
- secret-manager
- session
- skills
- storage
- stream-utils
- structured-output
- team-modes
- tool-composition
- toolkits
- tools
- trace-dataset
- trust
- video
- vision
- voice
- websocket
- workflow-branching
- workflows
- runbooks/adapter-redis
- runbooks/adapters
- runbooks/agentic
- runbooks/approval
- runbooks/artifacts
- runbooks/background
- runbooks/checkpoint
- runbooks/cli
- runbooks/code-mode
- runbooks/compression
- runbooks/config
- runbooks/context
- runbooks/contracts
- runbooks/control-plane
- runbooks/core
- runbooks/create-agent
- runbooks/db
- runbooks/durable
- runbooks/dx
- runbooks/eval
- runbooks/events
- runbooks/execution
- runbooks/goals
- runbooks/graph
- runbooks/guard
- runbooks/guardrails
- runbooks/harness
- runbooks/hooks
- runbooks/index
- runbooks/interfaces
- runbooks/knowledge
- runbooks/learning
- runbooks/lite
- runbooks/memory
- runbooks/model
- runbooks/models
- runbooks/observability
- runbooks/observe
- runbooks/orchestration
- runbooks/parsers
- runbooks/planner
- runbooks/playground
- runbooks/plugins
- runbooks/processors
- runbooks/production
- runbooks/providers
- runbooks/reasoning
- runbooks/registry
- runbooks/router
- runbooks/runnable
- runbooks/runtime
- runbooks/scheduler
- runbooks/sdk
- runbooks/serve
- runbooks/session
- runbooks/shared
- runbooks/simulation
- runbooks/skills
- runbooks/storage
- runbooks/streaming
- runbooks/structured
- runbooks/system
- runbooks/test
- runbooks/test-utils
- runbooks/test-utils-conformance
- runbooks/testing
- runbooks/tool
- runbooks/toolkits
- runbooks/tools
- runbooks/tools-ai
- runbooks/tools-communication
- runbooks/tools-core
- runbooks/tools-crm
- runbooks/tools-data
- runbooks/tools-devtools
- runbooks/tools-finance
- runbooks/tools-mcp
- runbooks/tools-media
- runbooks/tools-memory
- runbooks/tools-productivity
- runbooks/tools-scraping
- runbooks/tools-search
- runbooks/tools-shell
- runbooks/tools-social
- runbooks/tools-utils
- runbooks/video
- runbooks/voice
- runbooks/workflow

---



# Guide: adapters

# Adapters

Adapters are the infrastructure hand-off layer for `createAgent()`. The safest public patterns are:

- `createProductionSetup()` when you want a ready-made binding set
- `createAdapterRegistry()` when you want a central registry with lifecycle and health checks
- explicit `adapters` bindings when you want direct control over each slot

## Quick start

```
import { createAgent } from 'personaforge';
import { createProductionSetup } from 'personaforge/adapters';

const setup = createProductionSetup({ dev: true });
await setup.connect();

const agent = createAgent({
  name: 'assistant',
  instructions: 'You are a helpful assistant.',
  model: 'gpt-4o-mini',
  adapters: setup.bindings,
});

const result = await agent.run('Hello');
console.log(result.text);
console.log(await setup.healthCheck());
```

## Manual registry

```
import { createAgent } from 'personaforge';
import {
  InMemoryCacheAdapter,
  InMemorySessionStoreAdapter,
  InMemoryVectorAdapter,
  createAdapterRegistry,
} from 'personaforge/adapters';

const registry = createAdapterRegistry();
registry.register(new InMemoryCacheAdapter());
registry.register(new InMemoryVectorAdapter());
registry.register(new InMemorySessionStoreAdapter());

await registry.connectAll();

const agent = createAgent({
  name: 'assistant',
  instructions: 'Use the registered adapters.',
  model: 'gpt-4o-mini',
  adapters: registry,
});

console.log(registry.toBindings());
void agent;
```

## Explicit bindings

```
import { createAgent } from 'personaforge';
import {
  InMemoryAuditLogAdapter,
  InMemoryRateLimitAdapter,
  InMemorySessionStoreAdapter,
  InMemoryVectorAdapter,
} from 'personaforge/adapters';

const agent = createAgent({
  name: 'assistant',
  instructions: 'Use explicitly bound adapters.',
  model: 'gpt-4o-mini',
  adapters: {
    sessionStore: new InMemorySessionStoreAdapter(),
    memory: new InMemoryVectorAdapter(),
    rateLimit: new InMemoryRateLimitAdapter(),
    auditLog: new InMemoryAuditLogAdapter(),
  },
});

void agent;
```

## Choosing a pattern

- Use `createProductionSetup()` for the shortest path.
- Use a registry when you want health checks, connect/disconnect, and typed lookups.
- Use explicit bindings when you already know exactly which adapters each slot should use.


# Guide: admin-api

# Admin API

The Admin API is an operational overlay mounted inside `createHttpService`. It exposes read-only visibility into agent health, audit logs, active sessions, pending approvals, and throughput statistics.

```
import { createHttpService } from 'personaforge/serve';
import { createSqliteAuditStore, createSqliteCheckpointStore } from 'personaforge/production';
```

---

## Enable the Admin API

```
import { createHttpService } from 'personaforge/serve';
import { apiKeyAuth } from 'personaforge/serve';
import { createSqliteAuditStore, createSqliteCheckpointStore } from 'personaforge/production';

const svc = createHttpService({
  agents: { assistant },
  adminApi: {
    enabled: true,
    prefix: '/admin',               // default: /admin
    bearerToken: process.env.ADMIN_BEARER_TOKEN!,
    auditStore: createSqliteAuditStore('./agent.db'),
    checkpointStore: createSqliteCheckpointStore('./agent.db'),
  },
});

await listenService(svc, 8787);
// Admin endpoints now live at http://localhost:8787/admin/*
```

> **Warning:** If `bearerToken` is omitted the Admin API is unprotected. A warning is logged. Never deploy without `bearerToken` in production.

---

## Endpoints

All endpoints are under the configured prefix (default `/admin`).

| Method | Path | Description |
|---|---|---|
| `GET` | `/admin/health` | Deep health check — uptime, memory, process info |
| `GET` | `/admin/agents` | List registered agents + metadata |
| `GET` | `/admin/audit` | Paginated audit log (from `auditStore`) |
| `GET` | `/admin/sessions` | Active session listing |
| `GET` | `/admin/approvals` | Pending HITL approvals |
| `GET` | `/admin/checkpoints` | Active resumable run checkpoints |
| `GET` | `/admin/stats` | Aggregated request + error + token counts |

Authentication is `Authorization: Bearer <token>` on every request.

---

## Sample responses

```
# Health check
curl -H "Authorization: Bearer $ADMIN_TOKEN" http://localhost:8787/admin/health

# Audit log (last 20 entries)
curl -H "Authorization: Bearer $ADMIN_TOKEN" \
  "http://localhost:8787/admin/audit?limit=20"

# Pending approvals
curl -H "Authorization: Bearer $ADMIN_TOKEN" http://localhost:8787/admin/approvals

# Throughput stats
curl -H "Authorization: Bearer $ADMIN_TOKEN" http://localhost:8787/admin/stats
```

---

## `AdminApiOptions`

```
interface AdminApiOptions {
  /** Enable the admin API (default: false) */
  enabled?: boolean;
  /** URL prefix (default: /admin). Must start with /. */
  prefix?: string;
  /** Bearer token required for all admin requests. */
  bearerToken?: string;
  /** Durable audit store. Falls back to 500-entry in-memory ring buffer. */
  auditStore?: AuditStore;
  /** Checkpoint store for active resumable runs. */
  checkpointStore?: AgentCheckpointStore;
}
```

---

## `createHttpService` server options

| Option | Type | Default | Description |
|---|---|---|---|
| `requestTimeoutMs` | `number` | none | Abort agent execution and return HTTP 504 after this many milliseconds |
| `host` | `string` | `'0.0.0.0'` | Bind host. Set to `'127.0.0.1'` to restrict to loopback |
| `exposeErrors` | `boolean` | `false` | Include raw error messages in 500 responses. Enable only in development |

`close(drainTimeoutMs?)` — stops accepting new connections and waits up to `drainTimeoutMs` (default: 30 000 ms) for in-flight requests to finish before resolving.

---

## Full `createHttpService` example

```
import { createHttpService, listenService, apiKeyAuth } from 'personaforge/serve';
import {
  createSqliteAuditStore,
  createSqliteIdempotencyStore,
  createOpenAIRateLimiter,
} from 'personaforge/production';

const svc = createHttpService({
  agents: { assistant, coder },

  // CORS (allow local UI)
  cors: process.env.CORS_ORIGIN ?? '*',

  // Auth
  auth: { strategy: 'api-key', keys: [process.env.API_KEY!] },

  // Rate limiting
  rateLimit: createOpenAIRateLimiter('tier1'),

  // Idempotency
  idempotency: {
    store: createSqliteIdempotencyStore('./agent.db'),
    ttlMs: 24 * 60 * 60_000,
  },

  // Audit log
  auditStore: createSqliteAuditStore('./agent.db'),

  // WebSocket streaming
  websocket: true,

  // Per-request timeout — abort + 504 after 60 s
  requestTimeoutMs: 60_000,

  // Bind to loopback only (omit for 0.0.0.0)
  host: '127.0.0.1',

  // Expose raw error messages in responses (dev only — never set true in production)
  exposeErrors: false,

  // Admin API
  adminApi: {
    enabled: true,
    bearerToken: process.env.ADMIN_BEARER_TOKEN!,
    auditStore: createSqliteAuditStore('./agent.db'),
  },
});

await listenService(svc, 8787);

// Graceful shutdown — drain in-flight requests for up to 30 s
await svc.close(30_000);
```

---

## Where to go next

- [Production](./production) — circuit breakers, audit stores, graceful shutdown.
- [Observability](./observability) — OpenTelemetry traces for deeper inspection.
- [HITL](./hitl) — manage approvals exposed under `/admin/approvals`.


# Guide: agents

# Agents

`createAgent()` is the primary authoring surface. It wires an LLM, tools, session, guardrails, memory, and knowledge into one agent and returns a `run()` method.

## Minimal agent

```
import { createAgent } from 'personaforge';

const agent = createAgent({
  name: 'assistant',
  instructions: 'You are a helpful assistant. Be concise.',
  model: 'gpt-4o',        // or llm: new OpenAIProvider({ ... })
  apiKey: process.env.OPENAI_API_KEY!,
});

const result = await agent.run('Summarise quantum entanglement in 3 bullet points.');
console.log(result.text);
```

---

## `createAgent` options

```
interface CreateAgentOptions {
  // ── Identity ──────────────────────────────────────────────────────────────
  name: string;           // unique name; used in logs and traces
  instructions: string;   // system-level instructions for the model

  // ── Model / Provider ──────────────────────────────────────────────────────
  llm?: LLMProvider;      // any provider instance (takes priority over model)
  model?: string;         // e.g. 'gpt-4o', 'claude-sonnet-4', 'provider:model'
  apiKey?: string;        // used when model string is provided
  baseURL?: string;       // override provider base URL
  openRouter?: { apiKey?: string; model?: string };  // shorthand for OpenRouter

  // ── Tools ─────────────────────────────────────────────────────────────────
  tools?: Tool[] | ToolRegistry | false | 'web';
  // 'web' = built-in HttpClientTool + BrowserTool preset
  // false / [] = no tools (pure text reasoning)
  toolMiddleware?: ToolMiddleware[];

  // ── Session ───────────────────────────────────────────────────────────────
  sessionStore?: SessionStore | false;
  // false = stateless (no session tracking)
  // omit = in-memory session store

  // ── Safety ────────────────────────────────────────────────────────────────
  guardrails?: GuardrailEngine | false;
  // false = disabled
  // omit = default PII + sensitive-data guardrail

  // ── Schema validation ─────────────────────────────────────────────────────
  inputSchema?: z.ZodType;    // validate input before sending to the model
  outputSchema?: z.ZodType;   // force structured JSON output + validate it

  // ── Memory ────────────────────────────────────────────────────────────────
  memoryStore?: MemoryStore;                // persist remembered facts
  enableAgenticMemory?: boolean;            // give agent remember/recall tools
  addMemoriesToContext?: boolean;           // prepend recalled memories
  numMemories?: number;                     // max memories in context (default: 5)

  // ── RAG / Knowledge ───────────────────────────────────────────────────────
  knowledgebase?: RAGEngine;                // attach a knowledge base
  addKnowledgeToContext?: boolean;          // prepend retrieved chunks (default: true when set)

  // ── Context management ────────────────────────────────────────────────────
  addHistoryToContext?: boolean;            // include prior turns
  numHistoryRuns?: number;                  // max prior runs to include
  numHistoryMessages?: number;              // max prior messages to include

  // ── Reliability ───────────────────────────────────────────────────────────
  maxSteps?: number;                        // max tool-call iterations (default: 10)
  timeoutMs?: number;                       // request timeout in ms
  temperature?: number;                     // default sampling temperature 0–2 (default: 0.7)
  maxTokens?: number;                       // default max output tokens (default: 4096)
  retry?: { maxRetries?: number; backoffMs?: number; maxBackoffMs?: number };

  // ── Storage & observability ───────────────────────────────────────────────
  storage?: Storage;                        // persist run metadata + usage
  logger?: Logger;                          // custom logger

  // ── Follow-ups ────────────────────────────────────────────────────────────
  followUps?: boolean;                      // generate follow-up suggestions
  numFollowups?: number;                    // max follow-ups (default: 3)

  // ── Lifecycle hooks ───────────────────────────────────────────────────────
  hooks?: AgenticLifecycleHooks;            // intercept every stage of the run — see the Hooks guide

  // ── Dev ───────────────────────────────────────────────────────────────────
  debugMode?: boolean;                      // console visibility for runs
  debugLevel?: 1 | 2;                       // level 2 streams text chunks
}
```

---

## `run()` options

```
const result = await agent.run('Your prompt here', {
  sessionId: 'user-123',        // load / persist session for this user
  userId: 'user-123',           // attach to traces and audit logs
  runId: 'run-abc',             // custom run id for correlation
  traceId: 'trace-xyz',         // W3C trace context propagation
  maxSteps: 5,                  // override per-run
  timeoutMs: 10_000,            // override per-run
  allowedTools: ['search'],     // restrict which tools can be called this run
});

// result shape
result.text;          // final text response
result.messages;      // full message history
result.usage;         // { promptTokens, completionTokens, totalTokens }
result.followups;     // follow-up suggestions (if enabled)
result.storageKey;    // key used when storage adapter persisted this run
```

---

## Examples

### Agent with tools

```
import { createAgent, tool } from 'personaforge';
import { z } from 'zod';

const getWeather = tool({
  name: 'get_weather',
  description: 'Get current weather for a city.',
  parameters: z.object({ city: z.string() }),
  execute: async ({ city }) => {
    // call your weather API
    return { city, temperature: 22, condition: 'sunny' };
  },
});

const agent = createAgent({
  name: 'weather-agent',
  instructions: 'You help with weather queries. Always use the get_weather tool.',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
  tools: [getWeather],
});

const result = await agent.run('What is the weather in Tokyo?');
console.log(result.text);
```

### Agent with sessions (multi-turn)

```
import { createAgent, InMemorySessionStore } from 'personaforge';

const agent = createAgent({
  name: 'support-bot',
  instructions: 'You are a customer support agent. Remember context across turns.',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
  sessionStore: new InMemorySessionStore(),
  addHistoryToContext: true,
  numHistoryRuns: 10,
});

// Turn 1
await agent.run('My order #12345 has not arrived.', { sessionId: 'user-99' });

// Turn 2 — agent has full prior context
const result = await agent.run('What was my order number again?', { sessionId: 'user-99' });
console.log(result.text);  // references order #12345
```

### Agent with structured output

```
import { createAgent } from 'personaforge';
import { z } from 'zod';

const SentimentSchema = z.object({
  sentiment: z.enum(['positive', 'neutral', 'negative']),
  score: z.number().min(-1).max(1),
  explanation: z.string(),
});

const agent = createAgent({
  name: 'sentiment-classifier',
  instructions: 'Classify the sentiment of the given text.',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
  outputSchema: SentimentSchema,
});

const result = await agent.run('I absolutely loved the new product launch!');
const data = result.structuredOutput as z.infer<typeof SentimentSchema>;
console.log(data.sentiment);  // 'positive'
console.log(data.score);      // 0.95
```

### Agent with memory

```
import { createAgent, InMemoryStore } from 'personaforge';

const agent = createAgent({
  name: 'personal-assistant',
  instructions: 'You are a personal assistant. Remember user preferences.',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
  memoryStore: new InMemoryStore(),
  enableAgenticMemory: true,     // gives agent remember() / recall() tools
  addMemoriesToContext: true,     // prepends recalled memories to each run
  numMemories: 5,
});

await agent.run('I prefer dark mode and use TypeScript for all my projects.', { userId: 'alice' });

// Later session — the agent recalls these facts automatically
const result = await agent.run('What code editor settings should I use?', { userId: 'alice' });
console.log(result.text);
```

### Agent with RAG (knowledge base)

```
import { createAgent, createKnowledgeEngine, InMemoryVectorStore } from 'personaforge';

const kb = createKnowledgeEngine({
  vectorStore: new InMemoryVectorStore(),
  embeddingFn: async (texts) => { /* return embeddings */ return []; },
});

await kb.addDocuments([
  { id: '1', content: 'Refund policy: all products have a 30-day return window.' },
  { id: '2', content: 'Shipping: standard delivery takes 3-5 business days.' },
]);

const agent = createAgent({
  name: 'support-agent',
  instructions: 'Answer questions using the provided knowledge base.',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
  knowledgebase: kb,
});

const result = await agent.run('What is your return policy?');
console.log(result.text);  // uses the knowledge base content
```

### Agent with retry + timeout

```
const agent = createAgent({
  name: 'resilient-agent',
  instructions: 'Process the user request.',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
  maxSteps: 5,
  timeoutMs: 30_000,
  retry: {
    maxRetries: 3,
    backoffMs: 500,
    maxBackoffMs: 5_000,
  },
});
```

---

## Low-level: `AgenticRunner`

For direct control over the ReAct loop without the `createAgent` convenience layer:

```
import { AgenticRunner, createAgenticAgent } from 'personaforge';
import { OpenAIProvider } from 'personaforge';

const runner = new AgenticRunner({
  llm: new OpenAIProvider({ apiKey: process.env.OPENAI_API_KEY! }),
  tools: myToolRegistry,
  maxSteps: 10,
  timeoutMs: 60_000,
});

runner.setGuardrails(myGuardrailEngine);
runner.setHumanInTheLoop(myHITLHooks);

const result = await runner.run({
  name: 'my-agent',
  instructions: 'Process the request.',
  prompt: 'Analyse the latest sales data.',
});
```

---

## Where to go next

- [Tools](./tools) — define and attach tools to agents.
- [Memory](./memory) — persist facts across sessions.
- [Guardrails](./guardrails) — validate inputs/outputs and block unsafe behaviour.
- [Orchestration](./orchestration) — coordinate multiple agents in teams or pipelines.


# Guide: all-modules

# All Modules

Install `personaforge` once and import the module you need from that package.

```
npm install personaforge
```

## Headline API

```
import { agent, defineAgent, compose, pipe, tool } from 'personaforge';
```

## Public module map

| Import path | What it exposes |
|---|---|
| `personaforge` | Headline agent APIs, SDK helpers, custom tool helpers |
| `personaforge/tools` | Integrations and toolkits |
| `personaforge/session` | Session stores |
| `personaforge/knowledge` | Knowledge engine, loaders, retrieval |
| `personaforge/memory` | Memory stores and embedding-backed recall |
| `personaforge/guardrails` | Safety validators and built-in rules |
| `personaforge/production` | Production wrappers such as `withResilience()` |
| `personaforge/guard` | Circuit breaker, rate limiter, health checks |
| `personaforge/runtime` | HTTP runtime, auth, WebSocket transport |
| `personaforge/orchestration` | Supervisor, routing, consensus, A2A |
| `personaforge/workflow` | Workflow control-flow helpers |
| `personaforge/graph` | Durable DAG execution |
| `personaforge/scheduler` | Cron scheduling |
| `personaforge/reasoning` | Reasoning engines |
| `personaforge/db` | Framework-managed persistence backends |
| `personaforge/observability` | Logging, tracing, metrics, eval helpers |
| `personaforge/llm` | Provider classes and routing helpers |
| `personaforge/model` | `openai()`, `anthropic()`, `ollama()` shorthand factories |
| `personaforge/skills` | Pre-built skill bundles |
| `personaforge/processors` | Mastra-style input/output/error processor pipeline |
| `personaforge/durable` | Long-running, resumable agent execution with replay |
| `personaforge/goals` | Durable, thread-scoped judge-scored objectives |
| `personaforge/code-mode` | Sandboxed multi-tool computation |
| `personaforge/approval` | Human-in-the-loop approval + suspended runs |
| `personaforge/events` | Typed event bus + core event vocabulary |
| `personaforge/registry` | Agent registration, discovery, delegation toolkit |
| `personaforge/harness` | `evaluate()` — A/B harness over agents/tasks/workflows |

## Example imports

```
import { agent } from 'personaforge';
import { TavilySearchTool } from 'personaforge/tools/search';
import { createSqliteStore } from 'personaforge/session';
import { GuardrailValidator } from 'personaforge/guardrails';
import { withResilience } from 'personaforge/production';
import { CircuitBreaker } from 'personaforge/guard';
import { createHttpService } from 'personaforge/runtime';
import { createGraph } from 'personaforge/graph';
import { ScheduleManager } from 'personaforge/scheduler';
import { ReasoningManager } from 'personaforge/reasoning';
import { createAgentDb } from 'personaforge/db';
import { ConsoleLogger } from 'personaforge/observability';
import { openai } from 'personaforge/model';
import { webResearchSkill } from 'personaforge/skills';
```

## Guidance

Use root imports for the common getting-started flow.

Use `personaforge/<module>` when you want a narrower import surface or a clearer ownership boundary in app code.

The repository is still implemented as a monorepo, so contributor docs and migration notes may refer to `@personaforge/*` workspace package names. Those internal names are not the public install story.


# Guide: approval

# Agent Approval

`personaforge/approval` provides the signals and stores for human-in-the-loop agent control. Two suspension mechanisms are supported:

- **Before execution** — a tool call is paused before `execute` runs when `requireApproval` / `needsApproval` is set on the tool (or `requireToolApproval` on the run).
- **Mid-execution** — a tool self-pauses by calling `context.agent.suspend(payload)` to request more input.

```
import {
  isApprovalRequiredError,
  isToolSuspendedError,
  InMemorySuspendedRunStore,
  createSqliteSuspendedRunStore,
} from 'personaforge/approval';
```

---

## Signals

### `ApprovalRequiredError`

Raised *before* a tool executes when approval is required. The `toolCall` and `step` are attached for inspection:

```
import { isApprovalRequiredError } from 'personaforge/approval';

try {
  await agent.run('Send an email to bob@example.com');
} catch (err) {
  if (isApprovalRequiredError(err)) {
    console.log(`Tool ${err.toolName} needs approval (args:`, err.args, ')');
    // answer via the durable agent / approval store, or surface to a human UI.
  }
}
```

### `ToolSuspendedError`

Raised *inside* a tool's `execute` when it calls `context.agent.suspend(payload)`:

```
import { tool } from 'personaforge';

const collectAddress = tool({
  name: 'collect_address',
  description: 'Collect a shipping address.',
  parameters: z.object({ orderId: z.string() }),
  execute: async ({ orderId }, ctx) => {
    // Pause and ask for more input instead of failing:
    ctx.agent.suspend({ orderId, question: 'Please provide the shipping address.' });
    // Unreachable — suspend() never returns.
  },
});
```

---

## Suspended-run store

Pending approvals / suspensions are persisted as `SuspendedRun` records so a later request (after a restart, or from a different server) can rediscover and answer them.

### In-memory (development)

```
import { InMemorySuspendedRunStore } from 'personaforge/approval';

const store = new InMemorySuspendedRunStore();
```

### SQLite (production)

```
import { createSqliteSuspendedRunStore } from 'personaforge/approval';

const store = createSqliteSuspendedRunStore('./agent.db');
await store.save({
  runId: 'run_123',
  agentId: 'support-bot',
  threadId: 't1',
  resourceId: 'user-7',
  status: 'approval',
  toolCalls: [{
    toolCallId: 'call_1',
    toolName: 'send_invoice',
    args: { customerId: 'c1', amount: 500 },
    requiresApproval: true,
  }],
  createdAt: new Date().toISOString(),
  updatedAt: new Date().toISOString(),
});

const pending = await store.list({ threadId: 't1' });
await store.markResolved('run_123');
```

---

## Run-scoped approval

For run-wide approval policy, use `requireToolApproval` in the agent run options — boolean for every tool, or a function for per-call decisions (fails closed):

```
await agent.run('Deploy to prod', {
  requireToolApproval: ({ toolName, args }) =>
    toolName === 'deploy' && args.environment === 'production',
});
```

Use `approvedToolCalls` to carry already-approved call ids into a resumed run:

```
await agent.run('Deploy to prod', {
  approvedToolCalls: ['call_789'],
});
```

---

## Integration with durable agents

Durable runs expose `approveToolCall`, `declineToolCall`, `resumeStream`, and `listSuspendedRuns` directly. See [Durable Agents](./durable) for the full flow.

---

## Related pages

- [Durable Agents](./durable) — resumable, replayable runs with approval wiring.
- [Human-in-the-Loop (HITL)](./hitl) — the production approval store + HTTP endpoints.
- [Tools](./tools) — `needsApproval` / `requireApproval` on `tool()`.


# Guide: artifacts

# Artifacts

Artifacts are typed, versioned outputs produced by an agent run — files, reports, code, structured data, plans, reasoning traces — that should persist beyond the message text.

```
import {
  createTextArtifact,
  createMarkdownArtifact,
  createDataArtifact,
  createPlanArtifact,
  createReasoningArtifact,
  InMemoryArtifactStorage,
} from 'personaforge/artifacts';
```

---

## Artifact types

```
type ArtifactType =
  | 'file'
  | 'image'
  | 'audio'
  | 'video'
  | 'code'
  | 'data'
  | 'document'
  | 'markdown'
  | 'json'
  | 'reasoning'
  | 'plan'
  | 'report';
```

---

## Create artifacts

```
import {
  createTextArtifact,
  createMarkdownArtifact,
  createDataArtifact,
  createPlanArtifact,
  createReasoningArtifact,
} from 'personaforge/artifacts';

// Plain text / code file
const code = createTextArtifact({
  name: 'auth-handler.ts',
  content: `export function verifyToken(token: string) { ... }`,
  type: 'code',
  mimeType: 'text/typescript',
  tags: ['auth', 'typescript'],
  createdBy: 'code-agent',
});

// Markdown report
const report = createMarkdownArtifact({
  name: 'Q4-report.md',
  content: '## Q4 Summary\n\nRevenue up 12% YoY...',
  tags: ['report', 'q4'],
});

// Structured data
const data = createDataArtifact({
  name: 'search-results',
  content: { query: 'LLM benchmarks', results: [...] },
  type: 'json',
});

// Agent reasoning trace
const trace = createReasoningArtifact({
  steps: [
    { title: 'Analyse', action: 'Read the requirements', result: '...', confidence: 0.9 },
  ],
  conclusion: 'Use a queue-based approach.',
  model: 'gpt-4o',
});

// Execution plan
const plan = createPlanArtifact({
  goal: 'Migrate database to PostgreSQL',
  tasks: [
    { id: '1', name: 'Backup current DB', priority: 0 },
    { id: '2', name: 'Provision RDS', priority: 1, dependencies: ['1'] },
  ],
});
```

---

## `ArtifactMetadata` fields

All artifacts share these fields:

```
interface ArtifactMetadata {
  id: string;             // auto-generated UUID
  name: string;           // human-readable name
  type: ArtifactType;
  mimeType?: string;
  sizeBytes?: number;
  createdAt: Date;
  updatedAt: Date;
  version: number;        // starts at 1
  tags?: string[];
  metadata?: Record<string, unknown>;  // custom key-value pairs
  createdBy?: string;     // agent name
  sessionId?: string;
}
```

---

## Store artifacts

```
import { InMemoryArtifactStorage } from 'personaforge/artifacts';

const storage = new InMemoryArtifactStorage({
  maxSizeBytes: 100 * 1024 * 1024,  // 100 MB per artifact (default)
  versioning: true,                 // keep a full version history (default: true)
  // basePath?, ttlMs?, metrics? — see ArtifactStorageConfig
});

// Save (creates version 1; id/createdAt/version are generated)
const stored = await storage.save(report);

// Retrieve
const retrieved = await storage.get(stored.id);

// List by type
const allReports = await storage.list({
  type: 'markdown',
  tags: ['q4'],
  createdBy: 'report-agent',
});

// Search
const results = await storage.search('Q4 revenue');

// Delete
await storage.delete(stored.id);
```

---

## Emit artifacts from agent hooks

Attach artifact creation to the `afterRun` hook to capture every run's output:

```
import { createAgent } from 'personaforge';
import { InMemoryArtifactStorage, createMarkdownArtifact } from 'personaforge/artifacts';

const artifactStorage = new InMemoryArtifactStorage();

const agent = createAgent({
  name: 'report-agent',
  instructions: 'Generate detailed reports when asked.',
  model: 'gpt-4o',
  apiKey: process.env.OPENAI_API_KEY!,
  hooks: {
    afterRun: async (result) => {
      // Persist agent text output as a markdown artifact
      const artifact = createMarkdownArtifact({
        name: `report-${result.runId}.md`,
        content: result.text,
        createdBy: 'report-agent',
        metadata: { runId: result.runId, tokens: result.usage?.totalTokens },
      });
      await artifactStorage.save(artifact);
      return result;
    },
  },
});
```

---

## `ArtifactStorage` interface

Implement this to persist artifacts to S3, GCS, or any external store:

```
interface ArtifactStorage {
  save<T>(artifact: Omit<Artifact<T>, 'id' | 'createdAt' | 'updatedAt' | 'version'>): Promise<Artifact<T>>;
  get<T>(id: string): Promise<Artifact<T> | null>;
  getVersion<T>(id: string, version: number): Promise<Artifact<T> | null>;
  listVersions(id: string): Promise<ArtifactMetadata[]>;
  update<T>(id: string, updates: Partial<Omit<Artifact<T>, 'id' | 'createdAt' | 'version'>>): Promise<Artifact<T>>;
  delete(id: string): Promise<boolean>;
  list(filters?: {
    type?: ArtifactType;
    tags?: string[];
    createdBy?: string;
    sessionId?: string;
    limit?: number;
    offset?: number;
  }): Promise<ArtifactMetadata[]>;
  search(query: string, limit?: number): Promise<ArtifactMetadata[]>;
}
```

---

## Versioning

Storage keeps a full version history (toggle with `versioning` in
`ArtifactStorageConfig`). `update()` creates a new version; `getVersion()` and
`listVersions()` read the history:

```
const stored = await storage.save(report);   // version 1

// update() records a new version (2, 3, …)
const v2 = await storage.update(stored.id, {
  content: '## Q4 Summary (revised)\n\nRevenue up 14% YoY...',
});

const history = await storage.listVersions(stored.id);   // ArtifactMetadata[]
const original = await storage.getVersion(stored.id, 1);
```

---

## Media artifacts

Images, audio, and video are first-class artifact types. Build them with the
media helpers, or manage them through `MediaManager` against any `ArtifactStorage`.

```
import {
  MediaManager,
  createImageFromUrl,
  createImageFromBase64,
  createAudioFromUrl,
  createVideoFromUrl,
  InMemoryArtifactStorage,
} from 'personaforge/artifacts';
import type { ImageArtifact, AudioArtifact, VideoArtifact } from 'personaforge/artifacts';

// Build media artifacts directly (each returns an artifact ready for storage.save()):
const image = createImageFromUrl('hero.png', 'https://cdn.example.com/hero.png', {
  width: 1024, height: 768, prompt: 'a mountain at sunrise', model: 'dall-e-3',
});
const inline = createImageFromBase64('chart.png', base64Data, 'image/png');
const speech = createAudioFromUrl('greeting.mp3', 'https://cdn.example.com/greeting.mp3', {
  durationSeconds: 3.2, voiceId: 'alloy', transcript: 'Hello there.',
});
const clip = createVideoFromUrl('demo.mp4', 'https://cdn.example.com/demo.mp4', {
  durationSeconds: 30, width: 1920, height: 1080, fps: 30,
});

// …or use MediaManager to save + retrieve in one call:
const media = new MediaManager(new InMemoryArtifactStorage());
const savedImage: ImageArtifact = await media.saveImage('hero.png', 'https://cdn.example.com/hero.png', { width: 1024, height: 768 });
const savedAudio: AudioArtifact = await media.saveAudio('greeting.mp3', 'https://cdn.example.com/greeting.mp3');
const savedVideo: VideoArtifact = await media.saveVideo('demo.mp4', 'https://cdn.example.com/demo.mp4');
```

---

## Where to go next

- [Storage](./storage) — key-value storage for lighter-weight state.
- [Hooks](./hooks) — `afterRun` where artifacts are typically created.
- [Production](./production) — audit stores for compliance.


# Guide: background-queues

# Background Queues

Background queues let agent hooks and long-running work execute outside the main request path — with retries, persistence, and worker-based consumption. The interface is uniform across all backends.

```
import {
  InMemoryBackgroundQueue,
  BullMQBackgroundQueue,
  KafkaBackgroundQueue,
  RabbitMQBackgroundQueue,
  RedisPubSubBackgroundQueue,
  SQSBackgroundQueue,
  queueHook,
} from 'personaforge/background';
```

---

## Quick start

```
import { createAgent } from 'personaforge';
import { InMemoryBackgroundQueue, queueHook } from 'personaforge/background';

// In-memory queue — no dependencies, good for dev/test
const queue = new InMemoryBackgroundQueue({ concurrency: 5 });

const agent = createAgent({
  name: 'analytics-agent',
  instructions: 'Help users with their questions.',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
  hooks: {
    // Dispatch post-run analytics to the queue without blocking the response
    afterRun: queueHook(queue, 'analytics', (result) => ({
      steps:  result.steps,
      tokens: result.usage?.totalTokens,
      runId:  result.runId,
    })),
  },
});

// Register the worker handler (same or separate process)
await queue.consume('analytics', async (task) => {
  await analyticsService.track('agent.run', task.payload);
});

const result = await agent.run('Help me track my order.');
// The afterRun hook fires the task to the queue and returns immediately
```

---

## Queue backends

### `InMemoryBackgroundQueue` (development / testing)

```
import { InMemoryBackgroundQueue } from 'personaforge/background';

const queue = new InMemoryBackgroundQueue({ concurrency: 3 });
```

### `BullMQBackgroundQueue` — Redis-backed, durable

```
bun add bullmq
```

```
import { BullMQBackgroundQueue } from 'personaforge/background';

const queue = new BullMQBackgroundQueue({
  queueName: 'agent-hooks',
  connection: { host: 'localhost', port: 6379 },
  defaultJobOptions: {
    attempts: 3,
    backoff: { type: 'exponential', delay: 1_000 },
    removeOnComplete: 100,
    removeOnFail: 500,
  },
});

// Worker (same or separate process)
await queue.consume('afterRun', async (task) => {
  await saveToDb(task.payload);
}, { concurrency: 5 });
```

### `KafkaBackgroundQueue` — high-throughput, ordered, replay

```
bun add kafkajs
```

```
import { KafkaBackgroundQueue } from 'personaforge/background';

const queue = new KafkaBackgroundQueue({
  brokers: ['kafka:9092'],
  topic: 'agent-hooks',
  clientId: 'my-agent-app',
  groupId: 'agent-workers',
});
```

### `RabbitMQBackgroundQueue` — AMQP, dead-letter exchanges

```
bun add amqplib
```

```
import { RabbitMQBackgroundQueue } from 'personaforge/background';

const queue = new RabbitMQBackgroundQueue({
  url: process.env.RABBITMQ_URL!,
  queue: 'agent-hooks',
  exchange: 'agent',
  routingKey: 'hook',
});
```

### `RedisPubSubBackgroundQueue` — lightweight fanout

```
import { RedisPubSubBackgroundQueue } from 'personaforge/background';

const queue = new RedisPubSubBackgroundQueue({
  redis: process.env.REDIS_URL!,
  channel: 'agent-hooks',
});
```

### `SQSBackgroundQueue` — AWS managed, serverless

```
bun add @aws-sdk/client-sqs
```

```
import { SQSBackgroundQueue } from 'personaforge/background';

const queue = new SQSBackgroundQueue({
  queueUrl: process.env.SQS_QUEUE_URL!,
  region: 'us-east-1',
});
```

---

## `BackgroundQueue` interface

Implement this to add any backend:

```
interface BackgroundQueue {
  readonly name: string;

  enqueue<T>(task: Omit<BackgroundTask<T>, 'id' | 'enqueuedAt'>, options?: EnqueueOptions): Promise<void>;

  consume<T>(
    type: string,
    handler: (task: BackgroundTask<T>) => Promise<void> | void,
    options?: WorkerOptions,
  ): Promise<() => Promise<void>>;

  close(): Promise<void>;
}
```

---

## `queueHook` — hook → queue dispatch

`queueHook` turns any agent lifecycle hook into a fire-and-forget queue dispatch:

```
import { queueHook } from 'personaforge/background';

const hooks = {
  afterRun:     queueHook(queue, 'run-complete',   (result) => ({ text: result.text, tokens: result.usage?.totalTokens })),
  afterToolCall: queueHook(queue, 'tool-call-log', (name, result, args) => ({ name, args, result })),
};
```

---

## Enqueue manually

```
// Fire a background task directly (without a hook)
await queue.enqueue({
  type: 'send-report',
  payload: {
    userId: 'user-42',
    reportType: 'weekly',
  },
}, {
  delay: 5_000,     // delay 5 seconds (BullMQ, Kafka support this)
  retries: 3,
});
```

---

## Where to go next

- [Scheduler](./scheduler) — time-based recurring execution.
- [Hooks](./hooks) — lifecycle hooks where queue dispatch originates.
- [Production](./production) — circuit breakers and graceful shutdown.


# Guide: checkpoint

# Durable Interrupt & Resume

The `personaforge/checkpoint` module lets any graph node pause execution via `interrupt()`, persist a checkpoint, and resume later when a human (or external system) provides a value. Fork-from-checkpoint clones any saved state into a new thread for time-travel exploration.

```
import {
  DurableExecutor, InMemoryCheckpointStore, InterruptSignal,
} from 'personaforge/checkpoint';
```

---

## Quick start

```
import type { NodeFn } from 'personaforge/checkpoint';

const askApproval: NodeFn = (input, ctx) => {
  const value = ctx.interrupt({ question: 'Approve this transfer?' });
  return { input, approved: value };
};

const execute: NodeFn = (data) => ({ ...data, done: true });

const exec = new DurableExecutor({
  nodes: [['ask', askApproval], ['execute', execute]],
});

// Run — pauses at the interrupt
const r1 = await exec.run({ amount: 500 });
// r1.interrupted === true
// r1.interruptPayload === { question: 'Approve this transfer?' }

// Resume — passes a value back into the paused node
const r2 = await exec.resume(r1.threadId, { ok: true });
// r2.output === { input: { amount: 500 }, approved: { ok: true }, done: true }
```

---

## How it works

1. `interrupt(payload)` throws an `InterruptSignal` that the executor catches.
2. The executor persists a `Checkpoint` (state, history, pending input) to the `CheckpointStore`.
3. `resume(threadId, value)` re-runs the graph from the interrupted node, but this time `interrupt()` **returns** the resume value instead of throwing.
4. Execution continues past the pause point with no side-effect replay.

---

## Fork-from-checkpoint

Clone any saved checkpoint into a new thread:

```
const forkedThread = await exec.fork(originalThread);
await exec.resume(forkedThread, { ok: false });  // explore a different branch
```

---

## Pluggable `CheckpointStore`

The default `InMemoryCheckpointStore` is suitable for development. For production, implement the interface:

```
interface CheckpointStore {
  save(cp: Checkpoint): Promise<void>;
  load(threadId: string): Promise<Checkpoint | null>;
  loadById(checkpointId: string): Promise<Checkpoint | null>;
  list(threadId: string): Promise<Checkpoint[]>;
  delete(threadId: string): Promise<void>;
}
```

A SQLite or Postgres implementation follows the same pattern as `SqliteSessionStore`.

---

## Checkpoint shape

```
interface Checkpoint {
  id: string;
  threadId: string;
  node: string;                     // which node paused
  interruptPayload: unknown;       // data passed to interrupt()
  state: Record<string, unknown>;  // accumulated per-node outputs
  history: Array<{ node; output }>;
  pendingInput: unknown;           // input that was flowing into the paused node
  createdAt: number;
}
```

---

## Related pages

- [Graph Engine](/guide/graph) — event-sourced, replayable execution.
- [Human-in-the-Loop](/guide/hitl) — approval-based pauses.


# Guide: code-mode

# Code Mode

`personaforge/code-mode` lets an agent answer a multi-tool query with **one tool call**: the model writes a JavaScript/TypeScript function that orchestrates your existing tools as `external_*` functions and reduces/aggregates their results into a single structured answer. Fewer round-trips, correct arithmetic, smaller context.

```
import { createCodeMode } from 'personaforge/code-mode';
```

---

## Quick start

```
import { createCodeMode } from 'personaforge/code-mode';
import { agent } from 'personaforge';

const { tool, instructions } = createCodeMode({
  tools: { getTopProducts, getProductRatings }, // scoped tools
  sandbox: new LocalSandbox(),                   // default: isolated node process
});

const shopping = agent({
  instructions: ['You are a helpful shopping assistant.', instructions],
  tools: { execute_typescript: tool },           // one tool for the LLM
});
```

Now the model can call `execute_typescript` with code like:

```
const tops = await external_getTopProducts({ limit: 5 });
const scores = await Promise.all(tops.map(t => external_getProductRatings({ id: t.id })));
return tops.map((t, i) => ({ ...t, avg: average(scores[i]) }));
```

The generated code calls your real tools through the host bridge and returns an exact computed answer — no more multi-step tool-call round trips and no arithmetic hallucination.

---

## Scoped tools

Pass the tools the generated code may call as `external_*` functions. Each takes a single object argument and returns a Promise of its result:

```
const { tool, instructions } = createCodeMode({
  tools: [getTopProducts, getProductRatings],   // array or record form
});
```

Arguments are validated against each tool's parameter schema before execution, and the caller's tool context (agent/session identity, abort signal) is threaded through so tracing, approval, and audit keep working.

---

## Sandboxes

### `LocalSandbox` (default) — isolated child process

Spawns an isolated `node` child process over JSON-lines IPC. The script has **no filesystem, network, or module access** beyond the bridged tool calls — the strongest built-in boundary.

### `VMSandbox` — in-process `node:vm`

Runs in-process inside a `node:vm` context. Cheaper, but `vm` is **not a hard security boundary** — prefer `LocalSandbox` for untrusted input.

```
import { LocalSandbox, VMSandbox, createSandbox } from 'personaforge/code-mode';

const { tool } = createCodeMode({ sandbox: new VMSandbox() });
// or by name:
const sandbox = createSandbox('local'); // | 'vm'
```

---

## Options

```
export interface CodeModeOptions {
  id?: string;                    // default 'execute_typescript'
  description?: string;           // tool description
  tools?: Record<string, Tool | LightweightTool> | Array<Tool | LightweightTool>;
  sandbox?: Sandbox;              // default LocalSandbox
  timeoutMs?: number;             // default 60_000
  maxCodeChars?: number;          // default 16_000
  maxOutputChars?: number;        // default 100_000
}
```

The returned `tool` returns `{ result, stdout, executionMs }` on success. Failures throw with the sandbox error (and any captured stdout) attached.

---

## Related pages

- [Tools](./tools) — `tool()` and the tool helper.
- [Skills](./skills) — pre-built skill bundles.
- [Agentic Runner](./agents) — how tool calls execute in the loop.


# Guide: comparisons

# Framework Comparisons

`personaforge` is built for teams that outgrow streaming primitives or Python-only runtimes and need **TypeScript-native agents with production controls built in** — not bolted on later.

Use this page to pick the right migration guide, or scan the capability matrix below.

---

## Capability matrix

<ComparisonMatrix />

---

## Where personaforge wins

| Against | personaforge wins on |
|---|---|
| **LangChain** | Single package (not 200+). Built-in checkpoint/replay. MCP + A2A protocols. SSRF-protected tools. τ-bench cross-framework benchmarks. |
| **Vercel AI SDK** | Full agent runtime (not just streaming primitives). Sessions, memory, knowledge, teams, durability, guardrails, eval, control plane. |
| **CrewAI** | TypeScript-native. 6 team modes vs 2. Event-sourced durability. Built-in eval. 120+ tools. Graph DAG engine. |
| **LangGraph** | Same graph semantics in TypeScript — plus budget enforcement, guardrails, OTLP tracing, eval, and a control-plane dashboard in one install. |
| **Mastra** | Durable DAG engine, circuit breakers, USD budget caps, multi-tenancy, 100+ tools, and enterprise audit logging — not just typed step workflows. |
| **AutoGen / Agno** | TypeScript-native. Durable interrupts + resume. Built-in guardrails + budget enforcement. OTLP tracing. Control-plane dashboard. |

---

## Migrate from

Each guide follows the same structure: **quick comparison table → side-by-side code → where to go next**.

| Framework | Guide | Best for |
|---|---|---|
| **LangChain** | [Migrate From LangChain](./migration-langchain) | Chains, LCEL, retrievers, AgentExecutor |
| **Vercel AI SDK** | [Migrate From Vercel AI SDK](./migration-vercel) | `streamText`, `generateText`, `useChat` |
| **CrewAI** | [Migrate From CrewAI](./migration-crewai) | Role-based crews, tasks, hierarchical process |
| **LangGraph** | [Migrate From LangGraph](./migration-langgraph) | StateGraph, conditional edges, checkpointers |
| **Mastra** | [Migrate From Mastra](./migration-mastra) | Typed step workflows, agents, MCP |
| **Agno** | [Migrate From Agno](./migration-agno) | Python agents, teams, reasoning tools, knowledge |

---

## Cross-framework benchmarks

`personaforge` ships a τ-bench harness that runs **identical tool-calling tasks** against personaforge, LangGraph, Agno, CrewAI, and Mastra. Scores are verifier-based (tool-call arguments and ordering), not prose style — so results are reproducible in CI.

```
# Hermetic (mock LLM, always in CI)
bun run test tests/tau-bench-hermetic.test.ts

# Head-to-head vs Agno (requires agno server)
bun examples/agno-vs-personaforge.ts
```

See [`benchmarks/tau-bench/`](https://github.com/confused-ai/personaforge/tree/main/benchmarks/tau-bench) and [`PROTOCOL.md`](https://github.com/confused-ai/personaforge/blob/main/benchmarks/tau-bench/PROTOCOL.md) for the full protocol.

---

## Feature parity callouts

Some personaforge modules intentionally mirror familiar patterns from other frameworks:

| Pattern | personaforge module | Inspired by |
|---|---|---|
| `interrupt()` / `resume()` | [Durable Interrupt & Resume](./checkpoint) | LangGraph checkpointers |
| `values \| updates \| messages` stream modes | [Event Streaming](./event-streaming) | LangGraph stream protocol |
| `think` / `analyze` scratchpad tools | [Reasoning Tools](./reasoning-tools) | Agno reasoning tools |
| Typed step workflows | [Execution Workflows](./workflows) | Mastra workflows |

These are **API-compatible concepts**, not wrappers — you get the same mental model with TypeScript-native types and production middleware included.

---

## Where to go next

- [Trust & Reliability](./trust) — security, testing, benchmarks, and governance.
- [Getting Started](./getting-started) — first agent in minutes.
- [Core Concepts](./concepts) — agents, teams, workflows mental model.
- [Evaluation & Benchmarking](./eval) — built-in eval and regression detection.


# Guide: compose

# Compose

`compose()` and `pipe()` chain agents into sequential pipelines. The output text of each agent becomes the prompt for the next. No graph required.

```
import { compose, pipe, createAgent } from 'personaforge';
```

---

## `compose()` — simple pipeline

```
import { createAgent, compose } from 'personaforge';

const researcher = createAgent({
  name: 'researcher',
  instructions: 'Research topics and return raw findings.',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
});

const writer = createAgent({
  name: 'writer',
  instructions: 'Turn research findings into polished reports.',
  model: 'gpt-4o',
  apiKey: process.env.OPENAI_API_KEY!,
});

// Always pass researcher output → writer
const pipeline = compose(researcher, writer);
const result = await pipeline.run('Write a report on TypeScript 5.5');
// result.text — the writer's final output
```

---

## Conditional hand-off

Use `when` to stop the pipeline early:

```
const pipeline = compose(researcher, writer, {
  // Only hand off to writer if research is substantial
  when: (result) => result.text.length > 200,
});
```

---

## Transform output between stages

Use `transform` to reshape the output before passing it to the next agent:

```
const pipeline = compose(researcher, writer, {
  transform: (result) => `Here are the research findings:\n\n${result.text}`,
});
```

---

## `pipe()` — fluent step-by-step builder

```
import { pipe } from 'personaforge';

const draft   = createAgent({ name: 'drafter',   instructions: 'Draft a blog post.', ... });
const editor  = createAgent({ name: 'editor',    instructions: 'Edit for clarity.', ... });
const publish = createAgent({ name: 'publisher', instructions: 'Format for publication.', ... });

const pipeline = pipe(draft)
  .then(editor,  { transform: (r) => `Edit this draft:\n\n${r.text}` })
  .then(publish, { when: (r) => r.text.length > 50 });

const result = await pipeline.run('TypeScript 5.5 features');
```

---

## Three-stage document pipeline

```
const summarizer = createAgent({ name: 'summarizer', instructions: 'Summarise this document in 3 bullet points.', ... });
const classifier = createAgent({ name: 'classifier', instructions: 'Classify the document: legal / technical / marketing.', ... });
const router     = createAgent({ name: 'router',     instructions: 'Given the classification, suggest the right team to handle this.', ... });

const result = await compose(summarizer, classifier, router, {
  transform: (r, i) => i === 0
    ? `Summary:\n${r.text}\n\nPlease classify this document.`
    : r.text,
}).run('CONTRACT-2024-001.pdf contents...');
```

---

## When to use `compose` vs graph

| Use case | Use |
|---|---|
| Linear, fixed-order pipeline | `compose()` / `pipe()` |
| Conditional branching | `pipe(...).then(agent, { when })` or [workflow-branching](./workflow-branching) |
| Cycles, loops, or revisiting stages | [Graph workflows](./graph) |
| Parallel execution | [Graph workflows](./graph) |
| Durable checkpointing across process restarts | [Graph workflows](./graph) |
| Supervisor / consensus / handoff patterns | [Orchestration](./orchestration) |

---

## Where to go next

- [Workflow branching](./workflow-branching) — conditional routing between stages.
- [Graph workflows](./graph) — DAG execution for complex multi-path flows.
- [Orchestration](./orchestration) — supervisor patterns and agent handoffs.


# Guide: compression

# Compression

`CompressionManager` detects when message threads have grown too large and compresses verbose tool outputs into compact, fact-preserving summaries — in-place, without losing the context the task depends on.

```
import { CompressionManager } from 'personaforge';
```

---

## Quick start

```
import { createAgent } from 'personaforge';

// Mastermind context compression is built into createAgent and ON by default.
// It compresses tool outputs, logs, code, and history before they reach the LLM.
const agent = createAgent({
  name: 'research-agent',
  instructions: 'Research topics in depth using multiple searches.',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
  // Pass a MastermindConfig to tune it — or `mastermind: false` to disable.
  mastermind: {
    contextTokenBudget: 16_000,    // keep history under ~16k tokens
    messageTokenThreshold: 2_000,  // compress messages larger than ~2k tokens
    compressToolResults: true,
  },
});

// Inspect cumulative savings after runs:
await agent.run('Research the history of TypeScript.');
const stats = agent.getCompressionStats();
console.log(`saved ${stats?.tokensSaved} tokens (~$${stats?.costSavedUsd.toFixed(4)})`);
```

---

## `CompressionManager` API

### Constructor options

```
interface CompressionManagerConfig {
  /** LLM callable for summarisation */
  generate: (messages: Array<{ role: string; content: string }>) => Promise<string>;

  /** Whether to compress tool / function call results (default: true) */
  compressToolResults?: boolean;

  /** Minimum number of tool messages before compressing (default: 3) */
  compressToolResultsLimit?: number;

  /**
   * Single-message content token threshold above which compression triggers
   * regardless of message count. Estimated as content.length / 4.
   * Set to 0 to disable. (default: 4096)
   */
  compressTokenLimit?: number;

  /** Override the default compression system prompt */
  prompt?: string;

  debug?: boolean;
}
```

### Methods

```
// Check if the message list needs compression
cm.shouldCompress(messages);

// Compress tool-result messages in-place (sequential)
await cm.compress(messages);

// Compress in parallel (faster for large batches)
await cm.acompress(messages);
```

---

## What gets compressed

- Tool / function-call result messages where content exceeds `compressTokenLimit`
- Any batch of tool-result messages that reaches `compressToolResultsLimit`

Compressed messages have the original content replaced with a fact-preserving summary. The original `role` and all other message fields are preserved.

---

## Manual use in hooks

You can also trigger compression explicitly in an `afterRun` hook or before sending to the model:

```
const agent = createAgent({
  name: 'deep-researcher',
  instructions: '...',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
  hooks: {
    beforeRun: async (input) => {
      if (compression.shouldCompress(input.messages)) {
        await compression.acompress(input.messages);
      }
      return input;
    },
  },
});
```

---

## Default compression prompt

The built-in prompt instructs the model to:
1. Preserve all key facts, entities, IDs, numbers, names, dates.
2. Remove filler, pleasantries, repeated boilerplate, and excess whitespace.
3. Keep the same language as the input.
4. Output only the compressed content — no preamble.

Override it with the `prompt` option if your domain has specific compression requirements.

---

---

## Mastermind Context Compression Suite

While `CompressionManager` handles general summarization, the **Mastermind** compression pipeline is a production-grade, multi-stage compression engine. It optimizes KV-cache reuse, compresses message formats using specialized parsers, enforces strict token budgets, and stashes original data in an on-demand retrieval store (CCR).

The pipeline executes four stages on every run:
1. **CacheAligner**: Stabilizes the prefix of the message history to maximize KV-cache hits.
2. **Content Routing & Crusher Dispatch**: Routes message content based on type (JSON, Code, Logs, CSV, XML) to specialized, deterministic parsing algorithms that compress the text without LLM latency.
3. **Group-based Budget Enforcement**: Drops conversation groups oldest-first to fit within a strict token budget, while ensuring tool calls and tool results are never orphaned.
4. **Code & Context Reduction (CCR)**: Replaces original content with compressed annotations, stashing the originals. Re-injects a retrieval tool so the agent can fetch the raw details if needed.

```
import { Mastermind, OpenAIProvider } from 'personaforge';

const provider = new OpenAIProvider({ apiKey: process.env.OPENAI_API_KEY! });

const mastermind = new Mastermind({
  contextTokenBudget: 16_000,        // Fit history within 16k tokens
  messageTokenThreshold: 1_500,       // Only compress messages larger than 1.5k tokens
  enableCCR: true,                    // Allow agents to retrieve uncompressed content
  recentMessagesWindow: 4,            // Keep the last 4 messages completely uncompressed
  generate: async (msgs) => {         // Fallback LLM summarizer for prose
    const res = await provider.generateText({
      messages: msgs,
      model: 'gpt-4o-mini',
    });
    return res.text;
  },
});

const agent = createAgent({
  name: 'mastermind-agent',
  instructions: 'Use your tools to solve tasks.',
  model: 'gpt-4o',
  apiKey: process.env.OPENAI_API_KEY!,
  // Expose the retrieve tool so the agent can recall compressed details
  tools: [mastermind.retrieveTool],
  hooks: {
    beforeRun: async (input) => {
      const { messages } = await mastermind.compress(input.messages);
      // Materialize replaces message contents with their compressed versions
      input.messages = Mastermind.materialize(messages);
      return input;
    },
  },
});
```

### Stage 1: Cache Aligner
LLM providers charge less and respond faster when prompts hit their KV-cache. The `CacheAligner` normalizes whitespaces, matches repetitive formatting, and structures history headers so prefix matches are maximized.

### Stage 2: Specialized Crushers
Instead of relying purely on expensive LLMs to summarize structural data, Mastermind inspects the text and routes it to optimized local parsers:
* **JSON Crusher (`smart-crusher`)**: Strips empty properties, normalizes indentation, and collapses deeply nested schemas.
* **Code Compressor**: Minifies JS/TS, python, and other codeblocks by removing comments, redundant blank lines, and compressing indentation.
* **Log Crusher**: Aggregates duplicate trace lines, strips timestamps, and retains only unique stack traces or warning/error contexts.
* **CSV / XML Crushers**: Retains headers while truncating or downsampling datasets.
* **Prose Summarizer (`summary-llm`)**: Falls back to an LLM summary ONLY when unstructured markdown/prose is detected.

### Stage 3: Sliding-Window Group Budget Enforcement
When history exceeds the budget, Mastermind drops the oldest messages. However, standard truncation often separates a tool call from its tool result, breaking the ReAct loop. Mastermind groups assistant tool calls and their subsequent tool results into **atomic blocks** that are dropped together, ensuring the conversation tree remains valid.

### Stage 4: Code & Context Reduction (CCR)
For highly detailed inputs, compression can lose crucial bits. Under CCR:
1. Mastermind compresses the message and stashes the raw string in an in-memory `CCRStore`.
2. The message is annotated with a handle, e.g. `[ccr_0001 — call mastermind_retrieve("ccr_0001") for full content]`.
3. If the agent needs the exact values, it invokes the built-in `mastermind_retrieve` tool — `execute({ handle, query? })`. Pass a `query` to get back only the original lines matching it (case-insensitive); omit it to get the full original.

### Session stats & inspection

Every `Mastermind` instance tracks cumulative savings and exposes budget / CCR inspection:

```
// Cumulative savings across every compress() call on this instance
const life = mastermind.stats();
console.log(life.tokensSaved, life.costSavedUsd);  // plus life.recent[] ring buffer

// Is the current message list over the token budget?
mastermind.isOverBudget(messages);

// CCR store occupancy
mastermind.ccrStats();  // { size, maxEntries }
```

Attached to an agent via the `mastermind` option, the same lifetime dashboard is available through `agent.getCompressionStats()`.

### Also in the compression module

Beyond `CompressionManager` and `Mastermind`, `personaforge/compression` also exports standalone utilities: `HuffmanCodec` (+ `compressContext` / `decompressContext`), `SummaryBufferMemory`, `createSlidingWindow` / `applyWindow`, `EntityExtractionMemory`, `createTokenCounter` / `countTokens` / `contextBudget`, the structural crushers (`crushJsonText`, `compressCode`, `crushLog`, `crushXml`, `crushCsv`), and the CCR primitives `CCRStore` / `createRetrieveTool`.

---

## Where to go next

- [Session](./session) — conversation persistence; use compression to keep sessions lean.
- [Memory](./memory) — retain selected facts rather than summarising everything.
- [Context providers](./context-provider) — inject context deliberately instead of accumulating it.


# Guide: concepts

# Concepts

`personaforge` becomes much easier to use when you stop thinking of it as a pile of modules and start thinking of it as a layered system. Each layer solves a different problem, and most applications only need some of them.

## The core mental model

At the center is the agent. Around that core are the layers that give the agent context, execution boundaries, and operational controls.

| Layer | Purpose |
|---|---|
| Agent | instructions, model choice, tools, and runtime behavior |
| Tools | live data access and controlled side effects |
| Sessions and memory | continuity across runs and selected recall |
| Knowledge and storage | document retrieval and durable application state |
| Runtime | HTTP serving, scheduling, and transport boundaries |
| Coordination | composition, workflows, teams, supervisors, and reasoning |
| Operations | observability, budgets, approvals, resilience, and guardrails |

## Why the single-package story matters

The public install story is intentionally simple: install `personaforge` once, then move to dedicated public subpaths only when a concern becomes explicit.

That matters because it lets the architecture grow without forcing you to reorganize the entire app just because the agent got more capable.

## How complexity should grow

The framework works best when complexity grows outward from a stable center:

1. first the agent works
2. then the agent can access what it needs
3. then the runtime becomes durable and observable
4. then coordination and policy layers get added where justified

If you invert that order, you usually end up debugging infrastructure before you understand the model behavior.

## The main design principle

Every new layer should answer a specific missing requirement. Add a feature because you know why it is needed, not because the framework happens to offer it.

That keeps the final system cleaner and makes the documentation path easier to follow in practice.

## Where to go next

- Read `agents.md` for the authoring model.
- Read `tools.md` for the system boundary model.
- Read `workflows.md` and `orchestration.md` when one agent is no longer enough.


# Guide: context-provider

# Context Providers

A `ContextProvider` is a typed, reusable context source that agents can query. It wraps a backend (database, API, web) and exposes it either as injected system-prompt text, callable tools, or an agent sub-capability.

```
import { ContextProvider, ContextMode } from 'personaforge';
```

---

## Modes

| Mode | Constant | Description |
|---|---|---|
| Default | `ContextMode.DEFAULT` | Provider content is injected into the system prompt before each run |
| Tools | `ContextMode.TOOLS` | Provider registers query/update tools the agent calls on demand |
| Agent | `ContextMode.AGENT` | Provider injects context AND registers as a sub-agent capability |

---

## Implement a custom provider

```
import { ContextProvider, ContextMode } from 'personaforge';
import type { Answer, QueryOptions } from 'personaforge';

class CompanyDocsProvider extends ContextProvider {
  constructor() {
    super({
      name: 'company-docs',
      mode: ContextMode.TOOLS,          // expose as a callable tool
      queryToolName: 'search_company_docs',
      instructions: 'Use search_company_docs to look up internal policies and procedures.',
    });
  }

  async query(query: string, options?: QueryOptions): Promise<Answer> {
    const docs = await internalSearch(query, {
      limit: options?.limit ?? 5,
      namespace: options?.namespace,
    });
    return {
      results: docs.map(d => ({
        id: d.id,
        name: d.title,
        content: d.body,
        snippet: d.body.slice(0, 200),
        source: 'company-docs',
      })),
    };
  }
}

// Attach to agent — there is no `contextProviders` option. Wire a provider in
// through the tools and instructions it exposes.
import { createAgent, tool } from 'personaforge';
import { z } from 'zod';

const provider = new CompanyDocsProvider();
await provider.setup();                     // initialise connections before first query

// TOOLS-mode providers expose BackendTool objects ({ name, description, fn }).
// Wrap each as a framework tool() so the agent can call it.
const providerTools = provider.getTools().map((t) =>
  tool({
    name: t.name,                           // e.g. 'search_company_docs'
    description: t.description,
    parameters: z.object({ query: z.string().describe('Search query') }),
    execute: async ({ query }) => t.fn(query),
  }),
);

const agent = createAgent({
  name: 'support-agent',
  // DEFAULT-mode providers contribute prompt text via instructions(); fold it in.
  instructions: ['Help employees with policy questions.', provider.instructions()]
    .filter(Boolean)
    .join('\n\n'),
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
  tools: providerTools,
});
```

---

## Database context provider

```
import { ContextProvider, ContextMode } from 'personaforge';

class CustomerContextProvider extends ContextProvider {
  constructor(private db: Database) {
    super({
      name: 'customer-context',
      mode: ContextMode.DEFAULT,   // inject as system-prompt text
    });
  }

  async query(userId: string): Promise<Answer> {
    const customer = await this.db.findCustomer(userId);
    return {
      results: [{
        id: customer.id,
        name: customer.name,
        content: `Customer: ${customer.name}, Plan: ${customer.plan}, Since: ${customer.createdAt}`,
        source: 'database',
      }],
      text: `Current customer: ${customer.name} on ${customer.plan} plan.`,
    };
  }
}
```

---

## `ContextProvider` base class

```
abstract class ContextProvider {
  readonly name: string;
  readonly mode: ContextMode;
  readonly queryToolName: string;    // default: `${name}_query`
  readonly updateToolName: string;   // default: `${name}_update`
  readonly metadata: Record<string, unknown>;

  // Must implement:
  abstract query(query: string, options?: QueryOptions): Promise<Answer>;

  // Optional override:
  async update(documents: Document[], options?: UpdateOptions): Promise<void> { ... }

  // Lifecycle:
  async setup(): Promise<void> { ... }   // called once before the first query
  async close(): Promise<void> { ... }   // release resources

  // Agent integration:
  instructions(): string | undefined { ... }  // text injected into the system prompt
  getTools(): BackendTool[] { ... }            // TOOLS-mode callable tools ({ name, description, fn })

  // Health:
  status(): Status { ... }
  async astatus(): Promise<Status> { ... }
}
```

---

## `Document` and `Answer` types

```
interface Document {
  id: string;
  name: string;
  uri?: string;
  content?: string;
  source?: string;          // 'database', 'web', 'gdrive', etc.
  snippet?: string;
  metadata?: Record<string, unknown>;
}

interface Answer {
  results: Document[];
  text?: string;            // optional synthesised summary
}
```

---

## `QueryOptions`

```
interface QueryOptions {
  userId?: string;         // for access-control aware backends
  sessionId?: string;
  namespace?: string;      // collection / partition
  limit?: number;          // max results (default: 5)
  minScore?: number;       // similarity threshold (0.0–1.0)
}
```

---

## Where to go next

- [RAG](./rag) — full retrieval pipeline with vector search and reranking.
- [Hooks](./hooks) — inject context via `buildSystemPrompt` hook.
- [Memory](./memory) — agent long-term memory as a context source.


# Guide: control-plane

# Control Plane

`createControlPlane` starts a zero-dependency HTTP dashboard for operating your agents: sessions, memory, evals, traces, HITL approvals, knowledge, and a chat playground — all from one browser tab.

```
import { createControlPlane } from 'personaforge/control-plane';
```

---

## Quick start

```
const cp = createControlPlane({
  agents: [
    { name: 'support', run: (prompt) => supportAgent.run(prompt) },
  ],
  sessionStore,
  evalStore,
  traceStore,
  approvalStore,
  knowledgeStore,
});

await cp.start(4100);
console.log('Control plane on http://localhost:4100');
```

Open the URL to get a tabbed dashboard. Every panel is backed by a JSON API under `/api/*`, so you can also drive it programmatically or build a custom frontend.

---

## Panels

| Panel | Backed by | Endpoint |
|---|---|---|
| Sessions | `sessionStore.list()` / `load(id)` | `/api/sessions` |
| Memory | `memory store` (inspector) | — |
| Evals | `evalStore.list()` | `/api/evals` |
| Traces | `traceStore.list()` | `/api/traces` |
| Approvals | `approvalStore.listPending/approve/reject` | `/api/approvals` |
| Knowledge | `knowledgeStore.listDocuments()` | `/api/knowledge` |
| Chat | `agents[].run()` | `/api/chat` |

Every config field is optional — the dashboard degrades gracefully, showing an empty state for panels without a backing store.

---

## Wiring stores

The config uses structural interfaces so your existing stores usually fit without adapters:

```
createControlPlane({
  sessionStore: {
    list: () => store.listSessions(),
    load: (id) => store.getSession(id),
  },
  approvalStore: {
    listPending: () => approvals.listPending(),
    approve: (id) => approvals.approve(id),
    reject: (id) => approvals.reject(id),
  },
});
```

---

## HITL approval queue

The Approvals panel lists pending requests with Approve / Reject buttons wired to `POST /api/approvals/approve?id=…` and `…/reject`. Combine with the [HITL guide](/guide/hitl) so agents pause on risky actions and a human resolves them from the dashboard.

---

## Chat playground

The Chat panel posts to `/api/chat` with `{ agent, prompt }` and streams the agent's reply into a log. Use it for smoke-testing agents without writing a client.

---

## Stopping

```
await cp.stop();
```

---

## Production notes

- The server has **no external dependencies** — pure `node:http`.
- Put it behind your own auth proxy; it does not ship authentication.
- Request bodies are capped at 64 KB to avoid unbounded memory growth.

---

## Related pages

- [Admin API](/guide/admin-api) — health, audit, throughput endpoints.
- [Observability](/guide/observability) — trace and metric sources.
- [Human-in-the-Loop](/guide/hitl) — the approval workflow behind the queue.


# Guide: custom-adapter

# Custom Adapter

The adapter system lets you bind external infrastructure (databases, caches, vector stores, queues) to the framework via a single registry. Modules auto-pick the right adapter by category — swap backends at deploy time without touching agent code.

```
import {
  createAdapterRegistry,
  InMemoryCacheAdapter,
  InMemoryVectorAdapter,
} from 'personaforge/adapters';
```

---

## Quick start

```
import { createAdapterRegistry, InMemoryCacheAdapter } from 'personaforge/adapters';
import { createAgent } from 'personaforge';

const registry = createAdapterRegistry();

// Register adapters (in-memory for dev)
registry.register(new InMemoryCacheAdapter());
registry.register(new InMemoryVectorAdapter());

await registry.connectAll();

const agent = createAgent({
  name: 'my-agent',
  instructions: '...',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
  adapters: registry,   // modules auto-resolve their adapter
});
```

---

## Built-in adapters (zero dependencies)

These ship with the framework and require no external services:

| Adapter | Class | Description |
|---|---|---|
| Cache | `InMemoryCacheAdapter` | In-process TTL cache |
| Vector | `InMemoryVectorAdapter` | Cosine similarity search |
| SQL | `InMemorySqlAdapter` | In-process relational store |
| NoSQL | `InMemoryNoSqlAdapter` | Document store |
| Search | `InMemorySearchAdapter` | Full-text search |
| Object storage | `InMemoryObjectStorageAdapter` | Blob / file store |
| Graph | `InMemoryGraphAdapter` | Node-edge graph |
| Message queue | `InMemoryMessageQueueAdapter` | Pub/sub queue |
| Session | `InMemorySessionStoreAdapter` | Conversation history |
| Memory | `InMemoryMemoryStoreAdapter` | Agent long-term memory |
| RAG | `InMemoryRagAdapter` | Vector retrieval |
| Rate limit | `InMemoryRateLimitAdapter` | Token-bucket limiter |
| Audit log | `InMemoryAuditLogAdapter` | Audit trail |

---

## Production preset

Wire all core adapters in one call using `createProductionSetup`:

```
import { createProductionSetup } from 'personaforge/adapters';

const { registry } = await createProductionSetup({
  postgres: { connectionString: process.env.DATABASE_URL! },
  redis:    { url: process.env.REDIS_URL! },
  pinecone: { apiKey: process.env.PINECONE_API_KEY! },
});

const agent = createAgent({ adapters: registry, ... });
```

---

## Implement a custom adapter

Pick the category interface that matches your backend. All adapters share the same base:

```
import type { CacheAdapter, Adapter } from 'personaforge/adapters';

class RedisCacheAdapter implements CacheAdapter {
  readonly category = 'cache' as const;
  readonly name = 'redis';

  private client: Redis;

  constructor(config: { url: string }) {
    this.client = new Redis(config.url);
  }

  async connect()    { await this.client.ping(); }
  async disconnect() { await this.client.quit(); }
  async health()     { return { connected: true }; }

  async get(key: string)                                           { return JSON.parse(await this.client.get(key) ?? 'null'); }
  async set(key: string, value: unknown, ttlSeconds?: number)     { await this.client.set(key, JSON.stringify(value), 'EX', ttlSeconds ?? 3600); }
  async del(key: string)                                           { await this.client.del(key); }
  async flush(pattern: string)                                     { const keys = await this.client.keys(pattern); if (keys.length) await this.client.del(...keys); return keys.length; }
}

// Register
registry.register(new RedisCacheAdapter({ url: process.env.REDIS_URL! }));
```

---

## Adapter categories

| Category | Interface | Use for |
|---|---|---|
| `sql` | `SqlAdapter` | Relational data, joins, transactions |
| `nosql` | `NoSqlAdapter` | Document collections |
| `vector` | `VectorAdapter` | Embedding similarity search |
| `cache` | `CacheAdapter` | TTL key-value cache |
| `search` | `SearchAdapter` | Full-text / keyword search |
| `object-storage` | `ObjectStorageAdapter` | File/blob storage (S3, GCS) |
| `time-series` | `TimeSeriesAdapter` | Metrics, sensor data |
| `graph` | `GraphAdapter` | Graph traversal |
| `message-queue` | `MessageQueueAdapter` | Pub/sub, task queues |
| `observability` | `ObservabilityAdapter` | Logs, traces, metrics |
| `embedding` | `EmbeddingAdapter` | Text → vector |
| `session` | `SessionStoreAdapter` | Conversation history |
| `memory` | `MemoryStoreAdapter` | Agent long-term memory |
| `rag` | `RagAdapter` | Retrieve + augment |
| `guardrail` | `GuardrailAdapter` | Content safety |
| `auth` | `AuthAdapter` | Authentication |
| `rate-limit` | `RateLimitAdapter` | Rate limiting |
| `audit` | `AuditLogAdapter` | Audit trail |

---

## `AdapterRegistry` interface

```
interface AdapterRegistry {
  register(adapter: AnyAdapter, opts?: { replace?: boolean }): void;
  unregister(category: AdapterCategory, name: string): boolean;
  resolve<T>(category: AdapterCategory, name?: string): T;
  connectAll(): Promise<void>;
  disconnectAll(): Promise<void>;
  health(): Promise<Record<string, AdapterHealth>>;
  list(): AnyAdapter[];
}
```

---

## Where to go next

- [Storage](./storage) — key-value store built on top of the adapter system.
- [Session](./session) — conversation persistence via `SessionStoreAdapter`.
- [Secret manager](./secret-manager) — fetch credentials for adapter configuration.


# Guide: custom-tools

# Custom Tools

Custom tools expose your application's capabilities to agents. Each tool has a typed Zod schema, an execute function, and optional metadata like approval requirements, timeouts, and categories.

```
import { tool, defineTool, createTools } from 'personaforge';
import { z } from 'zod';
```

---

## `tool()` — primary helper

```
import { tool, createAgent } from 'personaforge';
import { z } from 'zod';

const getOrder = tool({
  name: 'get_order',
  description: 'Retrieve an order by ID. Returns status, items, and shipping info.',
  parameters: z.object({
    orderId: z.string().describe('The order ID to look up'),
  }),
  execute: async ({ orderId }, ctx) => {
    const order = await orderService.findById(orderId);
    if (!order) return { error: `Order ${orderId} not found.` };
    return { id: order.id, status: order.status, items: order.items };
  },
});

const agent = createAgent({
  name: 'support',
  instructions: 'Help customers with their orders.',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
  tools: [getOrder],
});
```

---

## `ToolContext`

The second argument to `execute` is a `ToolContext` with request-scoped metadata:

```
const auditedTool = tool({
  name: 'update_record',
  description: 'Update a database record.',
  parameters: z.object({
    id: z.string(),
    patch: z.record(z.unknown()),
  }),
  execute: async ({ id, patch }, ctx) => {
    console.log('Updating record', { id, sessionId: ctx.sessionId, agentId: ctx.agentId });

    // Abort early if the run was cancelled
    if (ctx.abortSignal?.aborted) {
      return { error: 'Run cancelled.' };
    }

    await db.update(id, patch);
    return { updated: true };
  },
});

// Tool context fields:
// ctx.agentId     — ID of the agent executing the tool
// ctx.sessionId   — current session ID
// ctx.abortSignal — AbortSignal (fires when the run is cancelled/timed out)
```

---

## Approval gates

Set `needsApproval: true` to require human approval before the tool runs:

```
const sendEmail = tool({
  name: 'send_email',
  description: 'Send an email to a customer.',
  parameters: z.object({ to: z.string().email(), subject: z.string(), body: z.string() }),
  needsApproval: true,   // agent will pause and wait for human approval
  execute: async ({ to, subject, body }) => {
    await mailer.send({ to, subject, body });
    return { sent: true };
  },
});

// Dynamic approval based on parameters
const chargeCard = tool({
  name: 'charge_card',
  description: 'Charge a customer credit card.',
  parameters: z.object({ customerId: z.string(), amount: z.number() }),
  needsApproval: ({ amount }) => amount > 100,  // only require approval for large charges
  execute: async ({ customerId, amount }) => {
    await payments.charge(customerId, amount);
    return { charged: true };
  },
});
```

---

## Tool timeout

```
const slowTool = tool({
  name: 'run_report',
  description: 'Generate a complex report (can take up to 2 minutes).',
  parameters: z.object({ reportId: z.string() }),
  timeoutMs: 120_000,   // 2 minutes
  execute: async ({ reportId }) => {
    return await reportEngine.generate(reportId);
  },
});
```

---

## Tool categories and tags

```
import { ToolCategory } from 'personaforge/tool';

const myTool = tool({
  name: 'search_products',
  description: 'Search the product catalogue.',
  parameters: z.object({ query: z.string() }),
  category: ToolCategory.DATA,
  tags: ['search', 'products', 'catalogue'],
  execute: async ({ query }) => searchProducts(query),
});
```

---

## `defineTool` (alias)

```
import { defineTool } from 'personaforge';

// Identical to tool() — just a named alias
const myTool = defineTool({
  name: 'hello',
  description: 'Say hello.',
  parameters: z.object({ name: z.string() }),
  execute: async ({ name }) => `Hello, ${name}!`,
});
```

---

## `createTools()` — define multiple tools at once

```
import { createTools } from 'personaforge';

const { get_product, update_inventory, check_stock } = createTools({
  get_product: {
    description: 'Get product details by SKU.',
    parameters: z.object({ sku: z.string() }),
    execute: async ({ sku }) => getProductBySku(sku),
  },
  update_inventory: {
    description: 'Update inventory count for a product.',
    parameters: z.object({ sku: z.string(), delta: z.number() }),
    needsApproval: true,
    execute: async ({ sku, delta }) => adjustInventory(sku, delta),
  },
  check_stock: {
    description: 'Check if a product is in stock.',
    parameters: z.object({ sku: z.string() }),
    execute: async ({ sku }) => checkProductStock(sku),
  },
});
```

---

## Streaming tool output (long-running)

```
const streamingTool = tool({
  name: 'process_large_file',
  description: 'Process a large file and stream progress.',
  parameters: z.object({ fileUrl: z.string() }),
  execute: async ({ fileUrl }, ctx) => {
    const lines: string[] = [];
    for await (const line of streamFile(fileUrl)) {
      if (ctx.abortSignal?.aborted) break;
      lines.push(processLine(line));
    }
    return { lines: lines.length, sample: lines.slice(0, 5) };
  },
});
```

---

## Where to go next

- [Tool composition](./tool-composition) — `extendTool`, `wrapTool`, `pipeTools`.
- [Tools](./tools) — built-in tools (100+) and the `tools: 'web'` preset.
- [HITL](./hitl) — durable approval stores for `needsApproval` tools.


# Guide: database

# Database

`AgentDb` is the unified database abstraction used internally by sessions, memory, knowledge, schedules, and eval stores. You can also use it directly to connect agents to structured data.

```
import {
  SqliteAgentDb,
  PostgresAgentDb,
  MongoAgentDb,
  RedisAgentDb,
  MysqlAgentDb,
  DynamoDbAgentDb,
  TursoAgentDb,
  JsonFileAgentDb,
  InMemoryAgentDb,
  createAgentDb,
} from 'personaforge/db';
```

---

## Backends

### SQLite (zero-config local)

```
import { SqliteAgentDb } from 'personaforge/db';

const db = new SqliteAgentDb({ path: './data/agent.db' });
```

### PostgreSQL

```
import { PostgresAgentDb } from 'personaforge/db';

const db = new PostgresAgentDb({
  connectionString: process.env.DATABASE_URL!,
  // ssl: { rejectUnauthorized: false },  // for managed Postgres
});
```

### MongoDB

```
import { MongoAgentDb } from 'personaforge/db';

const db = new MongoAgentDb({
  url: process.env.MONGODB_URI!,
  database: 'myapp',
});
```

### Redis (key-value)

```
import { RedisAgentDb } from 'personaforge/db';

const db = new RedisAgentDb({
  url: process.env.REDIS_URL!,
  prefix: 'myapp:',
});
```

### Turso (libSQL, edge-ready)

```
import { TursoAgentDb } from 'personaforge/db';

const db = new TursoAgentDb({
  url: process.env.TURSO_DATABASE_URL!,
  authToken: process.env.TURSO_AUTH_TOKEN,
});
```

### DynamoDB

```
import { DynamoDbAgentDb } from 'personaforge/db';

const db = new DynamoDbAgentDb({
  region: 'us-east-1',
  tableName: 'agent-data',
});
```

### JSON file (zero-dependency persistence)

`JsonFileAgentDb` persists each table as a JSON file under a directory — handy for demos and small local apps with no database server:

```
import { JsonFileAgentDb } from 'personaforge/db';

const db = new JsonFileAgentDb({ dir: './data/agent-db' });
```

### `createAgentDb` factory

Pick a backend by string at runtime:

```
import { createAgentDb } from 'personaforge/db';

// createAgentDb is async. `uri` is the connection string for every backend
// (its meaning depends on `type`). A plain URL string also works, e.g.
// `await createAgentDb('postgres://…')`.
const db = await createAgentDb({
  type: process.env.DB_TYPE as 'sqlite' | 'postgres' | 'mongo' | 'redis',
  uri: process.env.DATABASE_URL,   // 'sqlite://./agent.db' | 'postgres://…' | 'mongodb://…' | 'redis://…'
  database: 'myapp',               // mongo only
  // tables: { ... }               // optional table-name overrides
});
```

---

## Plug into framework stores

The main use of `AgentDb` is wiring all framework stores to a single persistent backend:

```
import { createAgent, DbSessionStore } from 'personaforge';
import { createDbKnowledgeEngine, OpenAIEmbeddingProvider } from 'personaforge';
import { SqliteAgentDb } from 'personaforge/db';
import { createDbMemoryStore } from 'personaforge/memory';

const db = new SqliteAgentDb({ path: './agent.db' });
const embedder = new OpenAIEmbeddingProvider({ apiKey: process.env.OPENAI_API_KEY! });

const agent = createAgent({
  name: 'persistent-agent',
  instructions: '...',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
  sessionStore:  new DbSessionStore(db),
  memoryStore:   createDbMemoryStore(db),   // AgentDb passed positionally
  knowledgebase: createDbKnowledgeEngine({
    db,
    embed: (text) => embedder.embed(text),  // embed is an EmbeddingFn, not a provider
  }),
});
```

---

## Database as a tool

For agent-initiated queries, expose database access as a typed tool:

```
import { tool, createAgent } from 'personaforge';
import { z } from 'zod';
import { db } from './db.js';  // your existing database client (Drizzle, Prisma, Knex...)

const lookupOrder = tool({
  name: 'lookup_order',
  description: 'Look up an order by ID. Returns order status and line items.',
  schema: z.object({ orderId: z.string() }),
  execute: async ({ orderId }) => {
    const order = await db.query.orders.findFirst({
      where: (o, { eq }) => eq(o.id, orderId),
      with: { lineItems: true },
    });
    if (!order) return { error: `Order ${orderId} not found.` };
    return order;
  },
});

const agent = createAgent({
  name: 'support-agent',
  instructions: 'Help customers with order questions.',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
  tools: [lookupOrder],
});
```

### Built-in data tools

If you don't want to hand-write tools, the framework ships ready-made data toolkits under `personaforge/tools/data` — SQL (`DatabaseToolkit`: Postgres/MySQL/SQLite), `RedisToolkit`, `CsvToolkit`, plus BigQuery and Neo4j tools:

```
import { DatabaseToolkit, RedisToolkit, CsvToolkit } from 'personaforge/tools/data';
```

---

## Where to go next

- [Storage](./storage) — key-value storage for application state.
- [Session](./session) — plug `DbSessionStore` into agents.
- [Memory](./memory) — `createDbMemoryStore` for persistent memory.
- [RAG](./rag) — `createDbKnowledgeEngine` for vector search.


# Guide: deep-research

# Deep Research Agent

`createDeepAgent` packages planner + parallel sub-agents + compression into a single opinionated factory for long-horizon research tasks.

```
import { createDeepAgent } from 'personaforge/skills';
```

---

## Quick start

```
const deep = createDeepAgent({
  generate: (prompt) => llm.generate(prompt),
  tools: [webSearchTool, wikipediaTool],
});

const result = await deep.run('What are the long-term economic effects of UBI?');
console.log(result.answer);
console.log(result.subQuestions);
console.log(result.rawSubAnswers);
console.log(result.steps.map((s) => `${s.phase}: ${s.detail}`));
```

---

## Pipeline

1. **Plan** — the LLM decomposes the question into focused sub-questions.
2. **Research** — each sub-question runs in parallel. Optional tools (search, Wikipedia) are called first, and their results are injected into the research prompt.
3. **Synthesize** — the findings are concatenated and a final synthesis prompt produces a structured answer with inline citations.

---

## Configuration

```
interface DeepAgentConfig {
  generate: (prompt: string) => Promise<string>;  // any LLM
  tools?: Array<{ name; description; execute }>;   // called per sub-question
  maxParallel?: number;      // default 5
  maxQuestions?: number;     // default 5
  subAnswerMaxChars?: number; // default 2000
}
```

---

## Result shape

```
interface DeepResearchResult {
  answer: string;                                  // multi-paragraph synthesis
  steps: Array<{ phase; detail }>;                 // audit trail
  subQuestions: string[];
  rawSubAnswers: Array<{ question; answer }>;
}
```

---

## Usage tips

- **Narrow `maxQuestions`** for simple queries to avoid over-decomposition.
- **Add a reranker** after the tool calls to filter noisy search results before feeding them to the sub-agent.
- **Chain with compression** if the synthesis prompt grows beyond the model's context window.

---

## Related pages

- [Planner](/guide/planner) — lower-level task decomposition.
- [Orchestration](/guide/orchestration) — multi-agent pipeline patterns.
- [Compression](/guide/compression) — token budget control.


# Guide: durable

# Durable Agents

`personaforge/durable` wraps any agent so its agentic loop runs in the background, publishes every event to a per-run topic, and lets late subscribers **replay or reconnect** without missing a chunk. Runs survive process restarts when backed by a Redis server cache and a suspended-run store.

```
import { createDurableAgent } from 'personaforge/durable';
```

---

## Quick start

```
import { createDurableAgent } from 'personaforge/durable';
import { agent } from 'personaforge';

const researcher = agent('You research topics and return findings.');

const durable = createDurableAgent({ agent: researcher });

// Start a run — the agentic loop runs in the background.
const { runId, output, cleanup } = await durable.stream('Research TypeScript 5');

// Consume events as they arrive (text, tool, approval, goal, run-finish).
for await (const event of output.fullStream) {
  if (event.type === 'text-delta') process.stdout.write(event.delta);
}
const final = await output.runResult;

// Clean up the run subscriptions / timers when you're done.
cleanup();
```

### Reconnect from another client

A client can disconnect and reconnect to a live run without missing chunks. Cached events are replayed first, then live ones:

```
// From any client, with the runId:
const { output } = await durable.observe(runId);
for await (const event of output.fullStream) { /* replay + live */ }
```

---

## Event output

`DurableAgentOutput` exposes independent async-iterable feeds (they never share a generator, so you can consume both):

| Property | Type | Description |
|---|---|---|
| `fullStream` | `AsyncIterable<DurableRunEvent>` | All events in order (`text-delta`, `tool-call`, `run-finish`, …) |
| `textStream` | `AsyncIterable<string>` | Text deltas only |
| `object` | `Promise<unknown>` | Final structured output (`run.object`) |
| `runResult` | `Promise<AgentRunResult>` | Final run result |

Each `DurableRunEvent` is a `StreamChunk` stamped with `seq` (monotonic ordering) and `at` (ISO timestamp), so consumers can dedupe and order across reconnects.

---

## Evented (fire-and-forget) mode

`createEventedAgent` starts runs and immediately closes the topic — ideal for webhooks / queue workers that don't need the caller to wait:

```
import { createEventedAgent } from 'personaforge/durable';

const durable = createEventedAgent({ agent: researcher });
await durable.stream('Process this ticket'); // returns immediately
```

---

## Human-in-the-loop on durable runs

Durable runs integrate with `personaforge/approval`: tools with `requireApproval` (or `needsApproval`) pause before executing, and `suspend()`-based tools pause mid-execution. Approve or decline without losing the run:

```
// A pending approval pauses the run and stores a SuspendedRun record.
const { output } = await durable.stream('Send an invoice to cust-123');

// The caller (or an admin UI) answers:
await durable.approveToolCall({ runId, toolCallId: 'call_123' });
// or
await durable.declineToolCall({ runId, toolCallId: 'call_123' });

// Resume a self-suspended tool with data:
await durable.resumeStream({ approved: true }, { runId, toolCallId: 'call_123' });

// Rediscover pending runs for a conversation (even after a restart):
const { runs } = await durable.listSuspendedRuns({ threadId: 't1', resourceId: 'user-7' });
```

---

## Crash recovery

Runs stuck in `running` status after a process crash can be re-driven from the last snapshot. Tools must be idempotent — LLM + tool calls are re-issued:

```
const { recovered, succeeded, failed } = await durable.recoverActiveRuns();
// Recover a specific run:
await durable.recoverActiveRuns({ runId: 'run_...' });
```

---

## Production persistence

By default runs use in-process caches and an in-memory suspended-run store — fine for a single process. For multi-replica deployments:

```
import { createDurableAgent, InMemoryServerCache } from 'personaforge/durable';
import { createSqliteSuspendedRunStore } from 'personaforge/approval';

const durable = createDurableAgent({
  agent: researcher,
  // Redis-backed cache → cached events survive restarts / scale across replicas.
  cache: InMemoryServerCache.fromRedis(process.env.REDIS_URL!),
  // SQLite suspended-run store → approvals survive restarts.
  suspendedStore: createSqliteSuspendedRunStore('./agent.db'),
});
```

You can also pass any `ioredis`-compatible `ServerCache` implementation.

---

## `untilIdle` / max idle time

By default `stream()` keeps the topic open for `maxIdleMs` (default 5 minutes) after completion so late observers can still read. `evented` mode closes immediately. Tune with:

```
const durable = createDurableAgent({ agent: researcher, maxIdleMs: 60_000 });
```

---

## Related pages

- [Approval (HITL)](./hitl) — tool approval / suspension signals and stores.
- [Memory](./memory) — thread-scoped, durable conversation state.
- [Processors](./processors) — input/output/error guardrails.
- [Goals](./goals) — durable, judge-scored objectives.


# Guide: eval

# Evaluation

The evaluation framework gives you LLM-as-judge scoring, text metrics (ROUGE-L, word overlap), benchmark runners, persistent eval stores, and CI regression detection. Import from `personaforge`.

## LLM-as-judge

Score a response against a prompt with an LLM:

```
import { runLlmAsJudge, OpenAIProvider } from 'personaforge';

const llm = new OpenAIProvider({ apiKey: process.env.OPENAI_API_KEY!, model: 'gpt-4o-mini' });

const result = await runLlmAsJudge({
  llm,
  rubric: 'Award full marks for an explanation that is factually correct, easy for a non-expert to follow, and concise.',
  candidate: agentResult.text,
  // reference: 'optional gold answer to compare against',
  maxScore: 10,   // default 10
});

console.log(result.score);      // 0 – maxScore (default 10)
console.log(result.rationale);  // judge explanation
```

### Pre-built criteria sets

```
import { createMultiCriteriaJudge, RAG_CRITERIA, AGENT_CRITERIA } from 'personaforge';

// For RAG agents: relevance, groundedness, completeness, conciseness
const ragJudge = createMultiCriteriaJudge({ llm, criteria: RAG_CRITERIA });
const ragResult = await ragJudge({ candidate: response, reference: expected });

// For general agents: task_completion, correctness, helpfulness, safety
const agentJudge = createMultiCriteriaJudge({ llm, criteria: AGENT_CRITERIA });
const agentResult2 = await agentJudge({ candidate: response });
```

### Multi-criteria judge

```
import { createMultiCriteriaJudge } from 'personaforge';

const judge = createMultiCriteriaJudge({
  llm,
  criteria: AGENT_CRITERIA,
});

// The judge is a function: call it with the candidate (and optional reference/context)
const scores = await judge({ candidate: response, reference: expected });
console.log(scores.overallScore);  // mean normalised score, 0–1
console.log(scores.criteria);      // per-criterion breakdown
```

---

## Text metrics

Fast, deterministic metrics that require no LLM:

```
import {
  ExactMatchAccuracy,
  PartialMatchAccuracy,
  LevenshteinAccuracy,
  wordOverlapF1,
  rougeLWords,
} from 'personaforge';

// These accuracy metrics are ready-to-use objects, not classes — no `new`, no config.

// Exact string match
console.log(ExactMatchAccuracy.score('hello world', 'hello world')); // 1.0
console.log(ExactMatchAccuracy.score('hello world', 'Hello World')); // 0.0

// Partial (substring) match — case-insensitive; 0.5 for a partial hit
console.log(PartialMatchAccuracy.score('hello world', 'hello'));  // 0.5

// Normalized edit distance
console.log(LevenshteinAccuracy.score('kitten', 'sitting')); // 0.57

// Word overlap F1 (great for longer text)
console.log(wordOverlapF1('the cat sat on the mat', 'the cat sat'));  // 0.75

// ROUGE-L (longest common subsequence)
console.log(rougeLWords('the cat sat on the mat', 'cat sat on mat'));  // 0.83
```

---

## Batch eval runner

Run an eval against a dataset and collect aggregate metrics:

```
import { runEvalBatch, createMultiCriteriaJudge, AGENT_CRITERIA } from 'personaforge';

// runEvalBatch scores pre-computed candidate outputs against a multi-criteria judge.
const judge = createMultiCriteriaJudge({ llm, criteria: AGENT_CRITERIA });

const cases = [
  { id: '1', candidate: agentAnswer1, reference: '4' },
  { id: '2', candidate: agentAnswer2, reference: 'Paris' },
  { id: '3', candidate: agentAnswer3, reference: 'Shakespeare' },
];

const summary = await runEvalBatch({
  judge,
  cases,
  concurrency: 3,
});

console.log(summary.total, summary.succeeded, summary.failed);
console.log(summary.meanOverallScore);  // mean overall score across cases, 0–1
console.log(summary.criteriaScores);    // mean score per criterion
```

---

## Benchmark pipeline

Run a full benchmark with multiple scorers and get a formatted report:

```
import {
  runBenchmark,
  exactMatchScorer,
  containsScorer,
  wordOverlapScorer,
  rougeLScorer,
  llmJudgeScorer,
  formatBenchmarkReport,
} from 'personaforge/eval';

const report = await runBenchmark({
  name: 'qa-benchmark-v3',
  dataset: [
    { input: 'What is the boiling point of water?', expected: '100°C' },
    { input: 'Who invented the telephone?',          expected: 'Alexander Graham Bell' },
  ],
  // `run` receives the input string as the first argument
  run: async (input) => {
    const result = await agent.run(input);
    return result.text;
  },
  // Built-in scorers take no arguments
  scorers: [
    exactMatchScorer(),
    containsScorer(),
    wordOverlapScorer(),
    rougeLScorer(),
    llmJudgeScorer({ llm, rubric: 'Award full marks for a correct, complete answer.' }),
  ],
  concurrency: 5,
});

console.log(formatBenchmarkReport(report));
```

---

## Eval store — persist and query results

```
import { InMemoryEvalStore, createSqliteEvalStore, runEvalSuite } from 'personaforge';

// Development: in-memory
const store = new InMemoryEvalStore();

// Production: SQLite (the factory takes a plain path string)
const store2 = createSqliteEvalStore('./evals.db');

const dataset = [
  { input: 'How do I reset my password?', expectedOutput: 'Use the "Forgot password" link on the sign-in page.' },
  { input: 'What are your support hours?', expectedOutput: 'We are available 24/7.' },
];

const report = await runEvalSuite({
  suiteName: 'customer-service-v2',
  dataset,
  agent,
  store: store2,
});

// Query stored runs for this suite (newest first)
const history = await store2.queryRuns('customer-service-v2', 10);
console.log(history.map(r => ({ date: r.timestamp, avgScore: r.averageScore })));
```

---

## Regression detection (CI/CD)

Compare a new eval run against a baseline and fail if scores drop:

```
import { runEvalSuite } from 'personaforge';

const report = await runEvalSuite({
  suiteName: 'regression-check',
  dataset,
  agent,
  store,
  regressionThreshold: 0.05,   // fail if avg score drops > 5% from the stored baseline
});

if (!report.passed) {
  console.error(`Regression detected! Score delta vs baseline: ${report.regressionDelta}`);
  process.exit(1);
}
```

---

## Dataset loading

```
import { loadDataset } from 'personaforge/eval';

// JSON array or JSON lines
const jsonlCases = await loadDataset({ source: './evals/qa.jsonl' });

// CSV — columns are matched by header name (defaults: 'input' / 'expected')
const csvCases = await loadDataset({
  source: './evals/qa.csv',
  inputColumn: 'question',
  expectedColumn: 'answer',
});

// Pass raw text instead of a file path
const rawCases = await loadDataset({ source: '{"input":"...","expected":"..."}', raw: true });

// Inline
const inlineCases = [
  { input: '...', expected: '...' },
];
```

---

## Fine-tuning dataset generator

Collect high-quality runs and export them as fine-tuning data:

```
import { generateDataset, filterByScore } from 'personaforge/eval';
import { writeFile } from 'node:fs/promises';

// Your collected runs as training examples (input / output, with a quality score 0–10)
const examples = [
  { input: 'What is TypeScript?', output: 'TypeScript is a typed superset of JavaScript.', score: 9.5 },
  // ...more examples
];

// Keep only high-quality examples
const highQuality = filterByScore(examples, { minScore: 9 });

// Serialize to a fine-tuning format: 'openai' | 'alpaca' | 'sharegpt'
const jsonl = generateDataset(highQuality, { format: 'openai' });
await writeFile('./finetune-data.jsonl', jsonl, 'utf-8');

console.log(`Exported ${highQuality.length} examples`);
```

---

## Where to go next

- [Observability](./observability) — trace and measure agent runs in production.
- [Production](./production) — circuit breakers, budget tracking, and rate limits.
- [Examples: eval CI pipeline](../examples/22-eval-ci) — complete CI regression example.


# Guide: event-streaming

# Event Streaming

The `personaforge/streaming` module provides a LangGraph-style event stream protocol. Nodes, tools, and LLM adapters emit typed events; consumers choose which event types they want.

```
import {
  StreamEventBus, StreamContext, createStreamableRun,
  type StreamEvent, type StreamMode,
} from 'personaforge/streaming';
```

---

## Stream modes

| Mode | What it delivers |
|---|---|
| `values` | Full state snapshot after each node finishes |
| `updates` | Per-node delta (node name + output) |
| `messages` | Token-level chunks from the LLM |
| `debug` | Tool call details, timing, internal telemetry |
| `custom` | User-emitted events via `ctx.emit()` |

Subscribe to one or more modes when you create a bus or a streamable run:

```
const bus = new StreamEventBus(['messages', 'updates']);
```

---

## Quick start with `createStreamableRun`

```
const { events, result } = createStreamableRun(async (ctx) => {
  ctx.token('Hello');
  ctx.token(' world');
  ctx.emit('milestone', { step: 1 });
  return { answer: 'Hello world' };
}, { streamMode: ['messages', 'custom'] });

for await (const event of events) {
  if (event.type === 'token') process.stdout.write(event.data);
  if (event.type === 'custom') console.log('Custom:', event.name, event.data);
}

const output = await result;
```

---

## Using `StreamContext` inside a node or tool

Every node receives a `StreamContext` bound to the event bus:

```
function myNode(input: unknown, ctx: StreamContext) {
  ctx.token('Thinking...');
  ctx.emit('search_started', { query: 'x' });
  ctx.toolCall('websearch', { q: 'x' }, { results: ['...'] });
  ctx.debug({ latencyMs: 42 });
  ctx.update({ partial: true });
  ctx.value({ full: 'state' });
}
```

Only events matching the bus's modes are forwarded to consumers.

---

## Using `StreamEventBus` directly

For low-level integration:

```
const bus = new StreamEventBus(['messages', 'debug']);

// Consumer
const iter = bus.events();
const consume = (async () => {
  for await (const event of iter) {
    console.log(event.type, event);
  }
})();

// Producer
bus.emit({ type: 'token', data: 'hi', timestamp: Date.now() });
bus.close();
await consume;
```

The iterator terminates cleanly when `close()` is called and the buffer is drained.

---

## Event types

```
interface TokenEvent   { type: 'token';     data: string;   node?: string }
interface UpdateEvent  { type: 'update';    data: unknown;  node: string  }
interface ValueEvent   { type: 'value';     data: Record<string, unknown>; node: string }
interface ToolCallEvent{ type: 'tool_call'; data: { name; arguments; result? } }
interface DebugEvent   { type: 'debug';     data: Record<string, unknown> }
interface CustomEvent  { type: 'custom';    name: string;  data: unknown }
```

All events carry a `timestamp` (epoch ms).

---

## Related pages

- [Graph Engine](/guide/graph) — event-sourced execution.
- [Stream Utilities](/guide/stream-utils) — lower-level text stream helpers.


# Guide: events

# Events

`personaforge/events` gives every agent and workflow a first-class typed pub/sub bus. Everything that happens — agent started, tool called, LLM delta, run finished, workflow suspended — is an observable, typed event.

```
import { eventBus, AGENT_EVENT } from 'personaforge/events';
```

---

## Quick start

```
import { eventBus, AGENT_EVENT } from 'personaforge/events';
import { agent } from 'personaforge';

// A bus pre-wired with the core event vocabulary.
const bus = eventBus({ replayBufferSize: 100 });

bus.on(AGENT_EVENT.runFinished, (e) => {
  console.log('run finished', e.agentId, e.result);
});
bus.on('*', (type, payload) => {
  console.log('any event →', type);
});

// Emit from your own hooks:
const bot = agent({
  instructions: 'You are helpful.',
  hooks: {
    afterRun: async (result) => {
      await bus.emit(AGENT_EVENT.runFinished, { agentId: 'bot', sessionId: 's1', result });
    },
  },
});
```

---

## Core event vocabulary

`AGENT_EVENT` contains the canonical event names — use these to avoid typos across the framework:

| Constant | Event name | Payload |
|---|---|---|
| `AGENT_EVENT.agentStarted` | `agent:started` | `{ agentId?, sessionId?, prompt? }` |
| `AGENT_EVENT.agentOutput` | `agent:output` | `{ agentId?, sessionId?, text? }` |
| `AGENT_EVENT.agentFinished` | `agent:finished` | `{ agentId?, steps, tokensUsed?, costUsd? }` |
| `AGENT_EVENT.toolCalled` | `tool:called` | `{ agentId?, sessionId?, name, input }` |
| `AGENT_EVENT.toolResult` | `tool:result` | `{ agentId?, sessionId?, name, success, output?, durationMs? }` |
| `AGENT_EVENT.llmDelta` | `llm:delta` | `{ agentId?, sessionId?, delta }` |
| `AGENT_EVENT.stepFinished` | `step:finished` | `{ agentId?, sessionId?, step }` |
| `AGENT_EVENT.runFinished` | `run:finished` | `{ agentId?, sessionId?, result? }` |
| `AGENT_EVENT.workflowSuspended` | `workflow:suspended` | `{ workflowId?, awaiting, token?, message? }` |
| `AGENT_EVENT.workflowCompleted` | `workflow:completed` | `{ workflowId?, results? }` |
| `AGENT_EVENT.error` | `error` | `{ agentId?, message, error? }` |

`CoreEventMap` is the TypeScript type for these payloads — your handlers are fully typed.

---

## Generic event bus

For a custom `EventMap`, use `createAgentEventBus`:

```
import { createAgentEventBus } from 'personaforge/events';

interface MyEvents {
  'ping': { at: number };
  'pong': { at: number };
}
const bus = createAgentEventBus<MyEvents>({ replayBufferSize: 64 });

bus.on('ping', (p) => console.log('ping at', p.at));
await bus.emit('ping', { at: Date.now() });
```

---

## Replay buffer

With `replayBufferSize > 0`, late subscribers receive buffered events after subscribing — useful for audit / dashboard views:

```
const bus = eventBus({ replayBufferSize: 100 });
await bus.emit(AGENT_EVENT.toolCalled, { name: 'search', input: {...} });

// Later subscriber still sees the buffered event:
bus.on(AGENT_EVENT.toolCalled, (e) => console.log('replay >', e.name));
```

---

## Handler failures

Handler errors surface as an `AggregateError` after all handlers run, so one failing handler doesn't break the others:

```
try {
  await bus.emit(AGENT_EVENT.error, { message: 'x' });
} catch (err) {
  // AggregateError of handler failures
}
```

---

## Related pages

- [Hooks](./hooks) — lifecycle hooks on agents / workflows.
- [Event Streaming](./event-streaming) — SSE event streaming for HTTP serving.
- [Observability](./observability) — traces and metrics.


# Guide: getting-started

# Getting Started

The fastest way to learn `personaforge` is to get one working result quickly, then add capability in the order the application actually needs it. This page is that first path.

## Step 1: Install the package

```
npm install personaforge
```

If you use Bun or pnpm, the package name is still the same. The public install story is one package: `personaforge`.

## Step 2: Set one provider key

Start with one provider only. Do not add several until the task is already working.

```
OPENAI_API_KEY=sk-...
```

## Step 3: Run one agent

```
import { createAgent } from 'personaforge';

const assistant = createAgent({
	name: 'hello-agent',
	model: 'gpt-4o-mini',
	instructions: 'You are a concise and helpful assistant.',
});

const result = await assistant.run('What is the capital of France?');
console.log(result.text);
```

If this run is not working reliably, stop here and fix it before layering anything else on top.

## What you need before going further

The minimum starting point is small:

- the `personaforge` package installed in your project
- one working model or provider configuration
- one task simple enough to validate in a single run

Do not start with orchestration, multiple providers, or a full production runtime. Those layers are useful, but they are not the first milestone.

## Your first milestone

The first milestone is not “build the platform.” The first milestone is “prove one agent can do one job correctly.”

That usually means:

1. define clear instructions
2. choose one model
3. run one prompt
4. inspect the returned text

If that path is unreliable, every advanced feature you add later will be harder to debug and easier to misdiagnose.

## Step 4: Add only the next missing capability

Once the base path works, the next layer should be chosen by need, not by curiosity.

Use this rule of thumb:

- add a **tool** when the model needs live data or a side effect
- add a **session store** when the user returns across turns
- add **knowledge or retrieval** when answers should come from source material
- add **HTTP serving** when the agent becomes a real endpoint
- add **orchestration** when one agent is no longer the right boundary

## Recommended build order

Use this progression unless you already know a later layer is required:

1. Start with one agent and one verified run.
2. Add one tool when the model needs live data or a side effect.
3. Add a session store when the user returns across turns.
4. Add retrieval or knowledge when answers should come from documents.
5. Add HTTP serving, scheduling, or orchestration only when the base behavior is solid.

This order keeps the moving parts separated so you can tell which layer introduced a failure.

## A second example: add one tool

The next upgrade should still feel simple.

```
import { agent, tool } from 'personaforge';
import { z } from 'zod/v3';

const getWeather = tool({
	name: 'get_weather',
	description: 'Return the current weather for a city.',
	parameters: z.object({ city: z.string() }),
	execute: async ({ city }) => `${city}: sunny, 24°C`,
});

const weatherAgent = agent({
	name: 'weather-agent',
	model: 'gpt-4o-mini',
	instructions: 'Use the tool to answer weather questions.',
	tools: [getWeather],
});

const result = await weatherAgent.run('What is the weather in Tokyo?');
console.log(result.text);
```

That is the general pattern for growing the system: add one explicit capability, verify it, then continue.

## What to avoid early

These are common ways to make the first version harder than it needs to be:

- starting with a team when one agent would do
- mixing several model providers before you know the task shape
- adding persistence or approvals before the core prompt behavior is understood
- writing many tools before one tool has proven its value

## Where to go next

After the first run works, choose the next page based on the missing capability:

- `agents.md` if you want a clearer authoring model
- `tools.md` if the agent needs live system access
- `session.md` if the conversation should continue over time
- `rag.md` if the answers must come from your documents
- `production.md` if the agent is moving into a real runtime


# Guide: goals

# Goals

`personaforge/goals` adds durable, thread-scoped objectives to an agent. A goal is a standing instruction the agent keeps working toward **across loop iterations** until a judge model decides it's satisfied, a run budget is exhausted, or the loop hits a step cap. Objectives persist in thread state, so they survive reloads and are still judged when a new message arrives mid-run.

```
import { InMemoryGoalStore, createSqliteGoalStore } from 'personaforge/goals';
```

---

## Quick start

Goals are configured on the agent and driven per-thread:

```
import { agent } from 'personaforge';

const worker = agent({
  instructions: 'You complete software tasks end to end.',
  model: 'openai/gpt-5',                     // main agent
  goal: {
    judge: 'openai/gpt-5-mini',              // judge model
    maxRuns: 50,                             // per-objective budget
  },
});

// Set a durable objective scoped to a thread:
await worker.setObjective('Add and test a /health endpoint', {
  threadId: 'thread-42',
  resourceId: 'user-7',
});

// Each stream()/run() now works toward the objective in-loop:
const stream = await worker.stream('Start working on the goal', {
  memory: { thread: 'thread-42', resource: 'user-7' },
});

// The loop emits `goal` events as the judge evaluates:
for await (const chunk of stream) {
  if (chunk.type === 'goal') {
    console.log('iteration', chunk.goal.iteration,
                'passed?', chunk.goal.passed,
                'reason:', chunk.goal.reason);
  }
}
```

---

## How the loop uses the goal

Each model iteration:

1. The judge (`GoalRunConfig.judge`) receives the agent's current output.
2. If `passed` → the loop stops with `finishReason: 'stop'`.
3. If not passed → the judge's `reason` is fed back as revision feedback and the loop continues.
4. The loop stops with `finishReason: 'max_runs'` when `maxRuns` is exhausted, `maxSteps` forces a stop, or the judge produced no actionable feedback.

A `StreamChunk` with `type: 'goal'` carries the `GoalEvaluation` (see below) so you can observe progress in real time.

---

## Objective records

The in-loop judge reads the thread's `ObjectiveRecord`:

```
interface ObjectiveRecord {
  objective: string;
  threadId?: string;
  resourceId?: string;
  maxRuns?: number;        // per-objective budget override
  runsUsed: number;
  status: 'active' | 'done' | 'paused';
  activeDurationMs?: number;
  updatedAt: string;
  prompt?: string;         // per-objective judge prompt override
}
```

### Managing objectives at runtime

The agent surface exposes runtime goal management:

```
// Set / get / update / clear per-thread objectives:
await worker.setObjective('Fix the flaky test', { threadId: 't9', maxRuns: 20 });
const rec = await worker.getObjective({ threadId: 't9' });
await worker.updateObjectiveOptions({ threadId: 't9', maxRuns: 100, prompt: 'Be lenient about style.' });
await worker.clearObjective({ threadId: 't9' });
```

---

## Goal stores

### In-memory (development / tests)

```
import { InMemoryGoalStore } from 'personaforge/goals';
```

### SQLite (production) — survives restarts

```
import { createSqliteGoalStore } from 'personaforge/goals';

const store = createSqliteGoalStore('./agent.db'); // requires better-sqlite3
```

When using `createAgent`/`agent`, the factory auto-creates a SQLite goal store when `AGENT_DB_PATH` is set (falling back to in-memory if `better-sqlite3` isn't installed). Pass `goalStore` explicitly to override:

```
const worker = agent({
  instructions: '...',
  goal: { judge: 'openai/gpt-5-mini' },
  goalStore: createSqliteGoalStore('./prod.db'),
});
```

---

## LLM judges

The goal feature is built on `personaforge/goals` judges — LLM-as-judge scoring for the agentic loop:

```
import { createLlmJudge, createStaticJudge, createRubricScorer, createSchemaScorer } from 'personaforge/goals';

// LLM judge with a custom prompt:
const judge = createLlmJudge({
  llm: myLlmProvider,
  prompt: 'You are a strict completeness judge. Respond with JSON.',
});

// Deterministic predicate judge:
const staticJudge = createStaticJudge((text) => text.includes('DONE'));

// Rubric (checklist) scorer with a backing LLM judge:
const rubric = createRubricScorer({
  judge,
  criteria: [
    { description: 'lists acceptance criteria', required: true },
    { description: 'explains test strategy' },
  ],
  requireAll: true,
});

// Schema-validated scorer:
const schemaScorer = createSchemaScorer(myOutputSchema);
```

Judges return a `JudgeVerdict`: `{ passed, reason?, score? }`.

---

## Related pages

- [Durable Agents](./durable) — resumable runs that pair with goals.
- [Learning](./learning-machine) — continuous improvement / feedback.
- [Agents](./agents) — `agent()` options reference.


# Guide: graph

# Graph Workflows

The graph engine executes arbitrary DAGs — directed acyclic graphs — with node-level retries, conditional edges, parallel fan-out, and durable checkpointing. Use it when a pipeline is no longer enough.

```
import { createGraph } from 'personaforge';       // createGraph is root-exported
import { DAGEngine } from 'personaforge/graph';    // the graph engine lives on the subpath
```

---

## Quick start

```
import { createGraph } from 'personaforge';
import { DAGEngine } from 'personaforge/graph';

const graph = createGraph('content-pipeline', { version: '1.0' })
  .addNode('fetch',    { kind: 'task', execute: (ctx) => fetchContent(ctx.state.variables.input as string) })
  .addNode('analyse',  { kind: 'task', execute: (ctx) => analyseContent(ctx.state.results['fetch']) })
  .addNode('publish',  { kind: 'task', execute: (ctx) => publishContent(ctx.state.results['analyse']) })
  .chain('fetch', 'analyse', 'publish')  // linear shorthand
  .build();

const engine = new DAGEngine(graph);
const execution = await engine.execute({ variables: { input: 'https://example.com/article' } });
// execution.state.results — keyed by node name
```

---

## Node kinds

| Kind | Use for |
|---|---|
| `task` | Any async function |
| `agent` | Run an LLM agent |
| `router` | Route to exactly one of multiple targets |
| `parallel` | Fan out to multiple targets concurrently |
| `join` | Wait for all incoming branches, then merge |
| `start` | Entry point (auto-detected if omitted) |
| `end` | Terminal node (optional) |
| `wait` | Pause for an external event or timer |

---

## Task node

```
.addNode('process', {
  kind: 'task',
  execute: async (ctx) => {
    // ctx.state.variables — initial input passed to execute({ variables })
    // ctx.state.results['nodeName'] — output of a previous node
    return await processData(ctx.state.results['fetch']);
  },
  retry: { maxRetries: 3, backoffMs: 1000, exponentialBase: 2 },
  timeout: { timeoutMs: 30_000 },
})
```

---

## Agent node

Run an LLM agent as a graph node:

```
.addNode('summarise', {
  kind: 'agent',
  instructions: 'Summarise the provided text in 3 bullet points.',
  model: 'gpt-4o-mini',
  tools: [webSearchTool],
  maxSteps: 5,
})
```

---

## Router node

Branch to exactly one target based on state:

```
.addNode('classify', {
  kind: 'router',
  route: (state) => {
    const category = state.results['classifier'] as string;
    if (category === 'billing')   return 'billing-agent';
    if (category === 'technical') return 'tech-agent';
    return 'general-agent';
  },
})
.addEdge('classify', 'billing-agent')
.addEdge('classify', 'tech-agent')
.addEdge('classify', 'general-agent')
```

---

## Parallel fan-out / join

Run multiple nodes concurrently, then merge results:

```
const graph = createGraph('parallel-research')
  .addNode('query',          { kind: 'task', execute: (ctx) => parseQuery(ctx.state.input as string) })
  .addNode('web-search',     { kind: 'task', execute: (ctx) => webSearch(ctx.state.results['query']) })
  .addNode('db-lookup',      { kind: 'task', execute: (ctx) => dbQuery(ctx.state.results['query']) })
  .addNode('docs-search',    { kind: 'task', execute: (ctx) => docSearch(ctx.state.results['query']) })
  .addNode('merge',          {
    kind: 'join',
    merge: (results) => ({
      web:  results['web-search'],
      db:   results['db-lookup'],
      docs: results['docs-search'],
    }),
  })
  .addNode('synthesise',     { kind: 'task', execute: (ctx) => synthesise(ctx.state.results['merge']) })
  .addEdge('query', 'web-search')
  .fanOut('query', ['web-search', 'db-lookup', 'docs-search'])   // parallel edges
  .fanIn(['web-search', 'db-lookup', 'docs-search'], 'merge')    // join
  .addEdge('merge', 'synthesise')
  .build();
```

---

## Conditional edges

Add a condition on any edge:

```
.addEdge('review', 'publish', {
  condition: (state) => (state.results['review'] as string).includes('approved'),
})
.addEdge('review', 'revise', {
  condition: (state) => !(state.results['review'] as string).includes('approved'),
})
```

---

## Graph-level options

```
createGraph('my-workflow')
  .defaultRetry({ maxRetries: 3, backoffMs: 500, exponentialBase: 2 })
  .defaultTimeout({ timeoutMs: 60_000 })
  .maxConcurrency(4)   // max parallel nodes
  .description('Content generation pipeline')
  .version('2.0')
```

---

## Durable execution with `DAGEngine`

Graphs execute through `DAGEngine` — not `AgentRuntime`, which is a single-agent
tool loop, not a graph runner. Pass an `EventStore` to `execute()` and the engine
records every state change, so an interrupted run can be reconstructed and resumed:

```
import { DAGEngine, SqliteEventStore } from 'personaforge/graph';

const engine = new DAGEngine(graph);
const execution = await engine.execute({
  eventStore: new SqliteEventStore('./graph-events.db'),
  checkpointInterval: 10,   // persist a checkpoint every N node completions
  maxConcurrency: 8,
});
// execution.status         — 'completed' | 'failed' | 'running' | 'paused'
// execution.state.results  — node-keyed results map
```

`DurableExecutor` wraps this pattern and adds crash recovery from the log:

```
import { DurableExecutor, SqliteEventStore } from 'personaforge/graph';

const store   = new SqliteEventStore('./graph-events.db');
const durable = new DurableExecutor(graph, store);

const first = await durable.run({ variables: { input: 'my-input' } });
// …process crashes…
const recovered = await durable.resume(first.executionId);   // rebuilt via replayState()
```

---

## Event sourcing, replay, and audit

The graph engine is event-sourced end to end. Everything below is exported from
`personaforge/graph` (`createGraph` and `SqliteEventStore` are also re-exported at
the package root).

### Event stores

```
import { InMemoryEventStore, SqliteEventStore, BatchingEventStore } from 'personaforge/graph';

const dev   = new InMemoryEventStore();          // tests / ephemeral
const store = new SqliteEventStore('./events.db'); // durable, file-backed
const fast  = new BatchingEventStore(store);     // buffer + batch appends off the hot path
```

### Deterministic replay

Re-run a recorded execution with **zero external calls** — recorded LLM results
and tool outputs are served from the log in order (time-travel debugging, sims):

```
import { replay, buildReplayProvider, buildReplayTools, replayState } from 'personaforge/graph';

const result = await replay(store, executionId, {
  name: 'researcher',
  instructions: 'Research the given topic thoroughly.',
});

// …or build the replay provider / tool registry yourself:
const llm   = await buildReplayProvider(store, executionId);
const tools = await buildReplayTools(store, executionId);

// Reconstruct the full GraphState from an event log:
const events = await store.load(executionId);
const state  = replayState(events, graph);
```

### Tamper-evident audit

Record with a hash chain, then verify the log hasn't been altered:

```
import { verifyChain } from 'personaforge/graph';

const events = await store.load(executionId);
const check = verifyChain(events);   // ChainVerification { valid, brokenAt?, reason? }
if (!check.valid) {
  console.error(`Audit log broken at event #${check.brokenAt}: ${check.reason}`);
}
```

### Recording agent runs & right-to-erasure

`RunRecorder` writes an ordinary `agent.run()` into the same durable log, with
optional secret/PII redaction and hash chaining. `EventStore.purge()` erases every
event for one execution (GDPR right-to-erasure):

```
import { RunRecorder, redactSecrets, redactPII, combineRedactors, SqliteEventStore } from 'personaforge/graph';

const store = new SqliteEventStore('./events.db');
const recorder = new RunRecorder(store, {
  hashChain: true,
  redact: combineRedactors(redactSecrets, redactPII),
});

// Drop every event for a single execution.
await store.purge(recorder.executionId);
```

### Distributed execution

Fan a graph across workers with a shared task queue:

```
import {
  DefaultScheduler, GraphWorker, DistributedEngine, RedisTaskQueue, computeWaves,
} from 'personaforge/graph';

const waves     = computeWaves(graph);                  // topological execution waves
const queue     = new RedisTaskQueue('redis://localhost:6379');
const scheduler = new DefaultScheduler(graph, queue);
const engine    = new DistributedEngine({ graph, scheduler, queue });
// GraphWorker instances pull tasks off the shared queue and report results back.
```

### Plugins

Attach cross-cutting telemetry and audit hooks via `execute({ plugins })`:

```
import { TelemetryPlugin, AuditPlugin, OpenTelemetryPlugin } from 'personaforge/graph';

await engine.execute({
  plugins: [
    new TelemetryPlugin(),
    new AuditPlugin({ maxEvents: 10_000 }),
    new OpenTelemetryPlugin({ serviceName: 'graph-engine' }),
  ],
});
```

---

## Where to go next

- [Workflow branching](./workflow-branching) — conditional routing patterns.
- [Compose](./compose) — simpler linear pipelines.
- [Orchestration](./orchestration) — supervisor/consensus patterns for agent teams.


# Guide: guardrails

# Guardrails

Guardrails run before and after each agent step to validate messages, detect unsafe content, and enforce policies. The framework ships a `GuardrailValidator` with composable rules that you pass to `createAgent()`.

## Quick start

```
import { createAgent } from 'personaforge';
import { GuardrailValidator, createPiiDetectionRule, createPromptInjectionRule } from 'personaforge';

const guardrails = new GuardrailValidator({
  rules: [
    createPromptInjectionRule({ threshold: 0.7 }),
    createPiiDetectionRule({ redact: true }),
  ],
});

const agent = createAgent({
  name: 'safe-agent',
  instructions: 'You are a helpful assistant.',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
  guardrails,
});
```

Pass `guardrails: false` to disable all guardrails.

---

## `GuardrailValidator`

The core engine. Compose any combination of built-in and custom rules.

```
import { GuardrailValidator } from 'personaforge';

const guardrails = new GuardrailValidator({
  rules: [rule1, rule2, rule3],
  onViolation: (violation, ctx) => {
    // called when any rule fires
    console.warn('Guardrail violation:', violation.rule, violation.message);
    // return 'block' | 'warn' | 'redact' | 'continue'
  },
});
```

---

## PII detection

Detect and optionally redact personally identifiable information:

```
import { createPiiDetectionRule } from 'personaforge';

const piiRule = createPiiDetectionRule({
  redact: true,           // replace PII with [REDACTED]
  // redact: false        // just flag without modifying

  // PII types to detect (all enabled by default):
  types: ['email', 'phone', 'ssn', 'credit_card', 'jwt', 'aws_key', 'api_key'],
});
```

**Detected PII types:** `email` · `phone` · `ssn` · `credit_card` · `national_insurance` · `passport` · `aws_key` · `api_key` · `jwt` · and more from `PII_PATTERNS`.

```
import { detectPii, PII_PATTERNS } from 'personaforge';

// Use standalone (no agent required) — detectPii is synchronous
const result = detectPii('Contact me at alice@example.com or 555-123-4567', { extract: true });
console.log(result.found);    // true
console.log(result.types);    // ['email', 'phone']
console.log(result.matches);  // { email: ['alice@example.com'], phone: ['555-123-4567'] }
```

---

## Prompt injection detection

Block attempts to hijack the agent via crafted input:

```
import { createPromptInjectionRule, detectPromptInjection } from 'personaforge';

const injectionRule = createPromptInjectionRule({
  threshold: 0.7,    // 0.0–1.0; higher = stricter. Default: 0.7
});

// Standalone usage — detectPromptInjection is synchronous:
const detection = detectPromptInjection('Ignore all previous instructions and...');
console.log(detection.isInjection); // true
console.log(detection.score);       // 0.95
console.log(detection.signals);     // [{ pattern: 'instruction-override', description, weight, match }, ...]
```

### LLM-based injection classifier (higher accuracy)

```
import { createLlmInjectionClassifier } from 'personaforge';
import { OpenAIProvider } from 'personaforge';

const injectionRule = createLlmInjectionClassifier({
  llm: new OpenAIProvider({ apiKey: process.env.OPENAI_API_KEY! }),
  model: 'gpt-4o-mini',
  threshold: 0.8,
});
```

---

## Content moderation

### OpenAI Moderation API

```
import { createOpenAiModerationRule } from 'personaforge';

const moderationRule = createOpenAiModerationRule({
  apiKey: process.env.OPENAI_API_KEY!,
  // Block if any category score exceeds threshold:
  thresholds: {
    hate: 0.7,
    'hate/threatening': 0.5,
    harassment: 0.7,
    'self-harm': 0.5,
    sexual: 0.8,
    violence: 0.7,
  },
});
```

### Forbidden topics

```
import { createForbiddenTopicsRule } from 'personaforge';

const topicsRule = createForbiddenTopicsRule({
  topics: ['competitor pricing', 'internal salary data', 'acquisition plans'],
  action: 'block',  // 'block' | 'warn'
});
```

---

## Content and length rules

```
import {
  createContentRule,
  createMaxLengthRule,
  createAllowlistRule,
  createSensitiveDataRule,
  createUrlValidationRule,
} from 'personaforge';

const rules = [
  // Block responses that contain specific patterns.
  // Signature: createContentRule(name, description, pattern, severity?)
  createContentRule(
    'no-credentials',
    'Blocks responses containing credential patterns.',
    /\b(password|secret|token)\s*[:=]/i,
    'error',
  ),

  // Limit output length.
  // Signature: createMaxLengthRule(name, maxLength, severity?)
  createMaxLengthRule('max-length', 10_000, 'error'),

  // Enforce an allowlist over tools, hosts, paths, outputs, and blocked patterns.
  createAllowlistRule({
    allowedTools: ['search', 'get_order'],
    allowedHosts: ['api.company.com', 'docs.company.com'],
    blockedPatterns: [/\b(password|secret)\b/i],
  }),

  // Flag built-in sensitive data patterns (credit cards, SSNs, API keys). No args.
  createSensitiveDataRule(),

  // Restrict URLs to allowed protocols (and optionally hosts).
  // Signature: createUrlValidationRule(allowedProtocols, allowedHosts?)
  createUrlValidationRule(['https:'], ['api.company.com', 'docs.company.com']),
];
```

---

## Tool allowlist

Restrict which tools the agent can call from within a guardrail rule:

```
import { createToolAllowlistRule } from 'personaforge';

// Signature: createToolAllowlistRule(allowedTools). Any tool not in the list
// is blocked before execution.
const toolRule = createToolAllowlistRule(['search_orders', 'get_product_info']);
```

---

## Custom rules

```
import type { GuardrailRule, GuardrailContext, GuardrailResult } from 'personaforge';

const noProfanityRule: GuardrailRule = {
  name: 'no-profanity',
  description: 'Blocks prohibited language in agent output.',
  severity: 'error',   // 'error' | 'warning'
  check: (ctx: GuardrailContext): GuardrailResult => {
    const text = typeof ctx.output === 'string' ? ctx.output : '';
    const hasProfanity = /\b(badword1|badword2)\b/i.test(text);

    if (hasProfanity) {
      return {
        passed: false,
        rule: 'no-profanity',
        message: 'Response contains prohibited language.',
      };
    }
    return { passed: true, rule: 'no-profanity' };
  },
};

const guardrails = new GuardrailValidator({ rules: [noProfanityRule] });
```

---

## Full example: production guardrail stack

```
import { createAgent } from 'personaforge';
import {
  GuardrailValidator,
  createPromptInjectionRule,
  createPiiDetectionRule,
  createOpenAiModerationRule,
  createForbiddenTopicsRule,
  createMaxLengthRule,
  createToolAllowlistRule,
} from 'personaforge';

const guardrails = new GuardrailValidator({
  rules: [
    createPromptInjectionRule({ threshold: 0.75 }),
    createPiiDetectionRule({ redact: true }),
    createOpenAiModerationRule({ apiKey: process.env.OPENAI_API_KEY! }),
    createForbiddenTopicsRule({ topics: ['competitor pricing', 'legal strategy'] }),
    createMaxLengthRule('max-length', 8_000, 'error'),
    createToolAllowlistRule(['search', 'get_order', 'send_email']),
  ],
  onViolation: (violation) => {
    // Send to your audit log
    auditLogger.warn({ rule: violation.rule, action: violation.action, score: violation.score });
  },
});

const agent = createAgent({
  name: 'customer-service',
  instructions: 'You are a customer service agent for Acme Corp.',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
  guardrails,
  tools: [searchTool, orderTool, emailTool],
});
```

---

## Where to go next

- [HITL](./hitl) — escalate violations to a human instead of auto-blocking.
- [Production](./production) — rate limiting, circuit breakers, and audit logging.
- [Agents](./agents) — how guardrails fit into the full `createAgent()` config.


# Guide: harness

# Harness & Evaluation

`personaforge/harness` is the "best-harness" single command for evaluating any runnable against a golden dataset — score every sample, capture cost/token/latency, and when you pass multiple subjects, compare them head-to-head (A/B, model comparison, prompt variants) and emit a winner per metric.

```
import { evaluate, fromAgent } from 'personaforge/harness';
```

---

## Quick start

```
import { evaluate, fromAgent } from 'personaforge/harness';
import { exactMatchScorer } from 'personaforge/eval';

const report = await evaluate({
  name: 'classifier-ab',
  dataset: [
    { id: '1', input: 'what is 2+2?', expected: '4' },
    { id: '2', input: 'cap of france', expected: 'paris' },
  ],
  subject: {
    baseline: agentA,     // model A
    candidate: agentB,    // model B
  },
  scorers: [exactMatchScorer()],
  concurrency: 4,
});

console.log(report.comparison);   // winner per metric
console.log(report.variants[0].benchmark.summary.passRate);
console.log(report.toJSON());     // JSON-serialisable
```

---

## Subjects

A `HarnessSubject` is any of:

- A **plain function** `(input: string) => unknown`
- An **agent-like** object with `.run(input, options?)` — `CreateAgentResult`, `TaskHandle`, `MockAgent`, …
- A **workflow-like** object with `.execute(input?)`

```
import {
  toHarnessRunner, fromAgent, fromTask, fromWorkflow, fromFn,
} from 'personaforge/harness';

const runAgent = fromAgent(myAgent);                 // CreateAgentResult
const runTask  = fromTask(myTask);                   // TaskHandle → .run()
const runWf    = fromWorkflow(myWorkflow);           // Workflow → .execute()
const runFn    = fromFn(async (q) => q.length);      // plain function

// Normalise any subject into `(input) => Promise<RunOutcome>`:
const runner = toHarnessRunner(myAgent, { sessionId: 'eval-1' });
const outcome = await runner('what is 2+2?');
// outcome.output      → text fed to scorers
// outcome.raw         → full raw result
// outcome.latencyMs   → wall time
// outcome.tokensUsed? → when reported
// outcome.costUsd?    → when reported / costOf provided
```

---

## A/B model comparison

Pass a record of named variants to compare head-to-head:

```
const report = await evaluate({
  name: 'router-bench',
  dataset,
  subject: {
    gpt4o:    agentWith('openai/gpt-4o'),
    mini:     agentWith('openai/gpt-4o-mini'),
    llama:    agentWith('groq/llama-3.3-70b'),
  },
  scorers: [exactMatchScorer()],
  concurrency: 4,
});

// `report.comparison` → [{ metric: 'score', winner: 'gpt4o', … }, …]
// Also compares latency_ms, tokens, cost_usd when captured.
```

---

## Report

`HarnessReport` exposes:

- `variants` — per-variant benchmark + usage summaries
- `comparison` — winner per metric (`score | latency_ms | tokens | cost_usd`)
- `passes` — true when every evaluated variant meets the pass threshold (default `0.7`)
- `toJSON()` — JSON-serialisable snapshot for CI
- `formatMarkdown()` — human-readable markdown report
- `timestamp`, `durationMs`

---

## Options

```
interface EvaluateOptions {
  name: string;
  dataset: BenchmarkSample[];                       // { id?, input, expected? }
  subject: HarnessSubject | Record<string, HarnessSubject>;
  scorers?: Scorer[];                               // from personaforge/eval
  concurrency?: number;                             // default 1
  passThreshold?: number;                           // default 0.7
  sessionId?: string;
  costOf?: (raw: unknown) => number | undefined;    // extract USD cost
  onSample?: (variant, result, index, total) => void;
  only?: string[];                                  // restrict variants by name
}
```

---

## Related pages

- [Evaluation & Benchmarking](./eval) — scorers, datasets, CI baselines.
- [Creating Agents](./agents) — agent subjects.
- [Workflows](./workflows) — workflow subjects.
- [Runbooks](./runbooks/) — operational runbooks for every module.


# Guide: hitl

# Human In The Loop (HITL)

HITL lets an agent pause before performing a high-risk action (send an email, charge a card, delete a record) and wait for a human decision. The approval request is persisted durably so the agent can resume even after a restart.

```
import {
  InMemoryApprovalStore,
  SqliteApprovalStore,
  createSqliteApprovalStore,
  waitForApproval,
  ApprovalRejectedError,
} from 'personaforge/production';
```

---

## Quick start

```
import { createAgent, tool } from 'personaforge';
import { createSqliteApprovalStore, waitForApproval, ApprovalRejectedError } from 'personaforge/production';
import { z } from 'zod';

const approvalStore = createSqliteApprovalStore('./agent.db');

// Wrap a risky tool with an approval gate
const sendInvoice = tool({
  name: 'send_invoice',
  description: 'Send an invoice email to a customer.',
  parameters: z.object({ customerId: z.string(), amount: z.number() }),
  execute: async ({ customerId, amount }, ctx) => {
    // Gate: create a pending approval, then block until a human decides
    const req = await approvalStore.create({
      runId: ctx.runId!,
      agentName: 'billing-agent',
      toolName: 'send_invoice',
      toolArguments: { customerId, amount },
      riskLevel: 'high',
      description: `Send a $${amount} invoice to customer ${customerId}`,
      ttlMs: 24 * 60 * 60 * 1000,  // request expires after 24 hours
    });
    await waitForApproval(approvalStore, req.id, {
      timeoutMs: 24 * 60 * 60 * 1000,  // wait up to 24 hours
    });

    // Only runs after approval
    await emailService.sendInvoice(customerId, amount);
    return { sent: true };
  },
});

const agent = createAgent({
  name: 'billing-agent',
  instructions: 'Handle billing and invoicing tasks.',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
  tools: [sendInvoice],
});

try {
  const result = await agent.run('Send a $500 invoice to customer cust-123.');
} catch (err) {
  if (err instanceof ApprovalRejectedError) {
    console.log('Human rejected the action:', err.comment);
  }
}
```

---

## Approval stores

### InMemoryApprovalStore (testing)

```
import { InMemoryApprovalStore } from 'personaforge/production';

const store = new InMemoryApprovalStore();
```

### SqliteApprovalStore (production)

```
import { createSqliteApprovalStore } from 'personaforge/production';

const store = createSqliteApprovalStore('./agent.db');
```

---

## `ApprovalStore` interface

```
interface ApprovalStore {
  create(request: Omit<HitlRequest, 'id' | 'status' | 'createdAt' | 'expiresAt'> & { ttlMs?: number }): Promise<HitlRequest>;
  get(id: string): Promise<HitlRequest | null>;
  getByRunId(runId: string): Promise<HitlRequest | null>;
  decide(id: string, decision: ApprovalDecision): Promise<HitlRequest>;
  listPending(agentName?: string): Promise<HitlRequest[]>;
  expireStale?(): Promise<number>;
}
```

---

## `HitlRequest` shape

```
interface HitlRequest {
  readonly id: string;
  readonly runId: string;
  readonly agentName: string;
  readonly toolName: string;
  readonly toolArguments: Record<string, unknown>;
  readonly riskLevel: 'low' | 'medium' | 'high' | 'critical';
  readonly description?: string;
  readonly status: 'pending' | 'approved' | 'rejected' | 'expired';
  readonly comment?: string;          // reviewer comment
  readonly createdAt: string;
  readonly expiresAt: string;
  readonly decidedAt?: string;
}
```

---

## HTTP approval endpoint

When you serve your agent with `createHttpService()`, pass an `approvalStore` to expose a built-in REST endpoint:

```
import { createHttpService } from 'personaforge/runtime';
import { createSqliteApprovalStore } from 'personaforge/production';

const approvalStore = createSqliteApprovalStore('./agent.db');

const app = createHttpService({ agents: { agent }, approvalStore });
app.listen(3000);
```

**List pending approvals:**
```
GET /v1/approvals?status=pending
```

**Submit a decision:**
```
POST /v1/approvals/:id
Content-Type: application/json
{ "approved": true, "comment": "Looks good to me" }
```

---

## Manual approval decision (testing)

```
// Simulate an approver submitting a decision
const pending = await approvalStore.listPending();
for (const req of pending) {
  await approvalStore.decide(req.id, {
    approved: true,
    comment: 'Reviewed and approved.',
  });
}
```

---

## Rejection handling

When a human rejects a request, `waitForApproval` throws `ApprovalRejectedError`:

```
import { ApprovalRejectedError } from 'personaforge/production';

try {
  const result = await agent.run(prompt, { runId: 'run-abc' });
} catch (err) {
  if (err instanceof ApprovalRejectedError) {
    console.log('Rejected because:', err.comment);
    // Notify user, log, update UI, etc.
  }
}
```

---

## Where to go next

- [Guardrails](./guardrails) — automatic policy-based blocking without human review.
- [Production](./production) — circuit breakers, audit logs, idempotency.
- [Example 03: Approval tool](../examples/03-approval-tool) — full HITL example.


# Guide: hooks

# Hooks

`AgenticLifecycleHooks` let you attach behavior at key lifecycle points without touching core agent logic. Pass a `hooks` object to `createAgent()`.

```
import { createAgent } from 'personaforge';
import type { AgenticLifecycleHooks } from 'personaforge';
```

---

## All hooks

```
interface AgenticLifecycleHooks {
  // Called before the run starts. Return a modified prompt to rewrite it.
  beforeRun?(prompt: string, config: unknown): Promise<string> | string;

  // Called after the run completes. Return a modified result to post-process it.
  afterRun?(result: AgentRunResult): Promise<AgentRunResult> | AgentRunResult;

  // Called before each LLM step. Return modified messages to inject context.
  beforeStep?(step: number, messages: Message[]): Promise<Message[]> | Message[];

  // Called after each LLM step completes.
  afterStep?(step: number, messages: Message[], text: string): Promise<void> | void;

  // Called before each tool call. Return modified args to transform inputs.
  beforeToolCall?(name: string, args: Record<string, unknown>, step: number): Promise<Record<string, unknown>> | Record<string, unknown>;

  // Called after each tool call with the result.
  afterToolCall?(name: string, result: unknown, args: Record<string, unknown>, step: number): Promise<unknown>;

  // Override the full system prompt construction (instructions + RAG context).
  buildSystemPrompt?(instructions: string, ragContext?: string): Promise<string> | string;

  // Called on any unhandled error inside the run.
  onError?(error: Error, step: number): Promise<void> | void;
}
```

---

## Examples

### Logging every tool call

```
const agent = createAgent({
  name: 'monitored-agent',
  instructions: 'You are a helpful assistant.',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
  hooks: {
    beforeToolCall: (name, args, step) => {
      console.log(`[step ${step}] → calling tool: ${name}`, args);
      return args;  // must return args (can be modified)
    },
    afterToolCall: (name, result, args, step) => {
      console.log(`[step ${step}] ← tool result: ${name}`, result);
      return result;  // must return result (can be modified)
    },
  },
});
```

### Prompt rewriting

```
const agent = createAgent({
  name: 'rewriting-agent',
  instructions: '...',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
  hooks: {
    beforeRun: async (prompt) => {
      // Translate to English before the agent sees it
      const english = await translateToEnglish(prompt);
      return english;
    },
    afterRun: async (result) => {
      // Translate the answer back
      const translated = await translateBack(result.text);
      return { ...result, text: translated };
    },
  },
});
```

### Injecting context on each step

```
const agent = createAgent({
  name: 'context-injecting-agent',
  instructions: '...',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
  hooks: {
    beforeStep: async (step, messages) => {
      // Prepend a real-time context message at step 0 only
      if (step === 0) {
        return [
          { role: 'system', content: `Current time: ${new Date().toISOString()}` },
          ...messages,
        ];
      }
      return messages;
    },
  },
});
```

### Custom system prompt builder

```
const agent = createAgent({
  name: 'custom-prompt-agent',
  instructions: 'You are a customer service agent for Acme Corp.',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
  hooks: {
    buildSystemPrompt: async (instructions, ragContext) => {
      const user = await loadCurrentUser();
      return [
        instructions,
        ragContext ? `\n\nRelevant documentation:\n${ragContext}` : '',
        `\n\nCurrent user: ${user.name} (plan: ${user.plan})`,
      ].join('');
    },
  },
});
```

### Error handling and alerting

```
const agent = createAgent({
  name: 'resilient-agent',
  instructions: '...',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
  hooks: {
    onError: async (error, step) => {
      await sendAlert({
        severity: 'high',
        message: `Agent error at step ${step}: ${error.message}`,
        stack: error.stack,
      });
    },
  },
});
```

### Step timing and tracing

```
const stepStartTimes = new Map<number, number>();

const agent = createAgent({
  name: 'timed-agent',
  instructions: '...',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
  hooks: {
    beforeStep: (step, messages) => {
      stepStartTimes.set(step, Date.now());
      return messages;
    },
    afterStep: (step, _messages, text) => {
      const start = stepStartTimes.get(step) ?? Date.now();
      console.log(`Step ${step} took ${Date.now() - start}ms. Output: ${text.slice(0, 80)}...`);
    },
  },
});
```

---

## Streaming events

For non-blocking real-time observation, use `agent.streamEvents()` instead:

```
for await (const event of agent.streamEvents('Summarise the report.')) {
  switch (event.type) {
    case 'text-delta':   process.stdout.write(event.delta); break;
    case 'tool-call':    console.log('calling', event.tool?.name); break;
    case 'tool-result':  console.log('result', event.tool?.output); break;
    case 'step-finish':  console.log(`step ${event.stepNumber} done`); break;
    case 'run-finish':   console.log('complete in', event.run?.steps, 'steps'); break;
    case 'error':        console.error(event.error); break;
  }
}
```

---

## Where to go next

- [Observability](./observability) — OTLP tracing, Prometheus metrics, Langfuse.
- [Guardrails](./guardrails) — block bad inputs/outputs with policy rules.


# Guide: introduction

# Introduction

`personaforge` gives you three primitives for building AI systems in TypeScript: **agents**, **teams**, and **workflows**. The goal is not to make the first version look like a platform. The goal is to make the first useful version small, then let it grow into a real system without changing frameworks.

## One quick example

This is the kind of example that should feel immediately understandable: one focused tool, one agent, one useful response.

```
import { agent, tool } from 'personaforge';
import { z } from 'zod/v3';

const getQuote = tool({
	name: 'get_quote',
	description: 'Return a stock quote for a ticker symbol.',
	parameters: z.object({ symbol: z.string() }),
	execute: async ({ symbol }) => ({
		symbol,
		price: 927.5,
		changePct: 1.4,
	}),
});

const financeAgent = agent({
	name: 'finance-agent',
	model: 'gpt-4o-mini',
	instructions: 'Use the tool to answer market questions in one concise sentence.',
	tools: [getQuote],
});

const result = await financeAgent.run("What's NVDA trading at today?");
console.log(result.text);
```

That is the intended feel of the framework: plain application code, explicit capabilities, and a direct path from concept to working behavior.

## The three main primitives

| Primitive | Use it when |
|---|---|
| Agent | one model-backed worker can handle the task with instructions, tools, and optional state |
| Team | the work should be split across specialists, handoffs, or supervised roles |
| Workflow | the execution path itself should be deterministic, staged, or branch-aware |

Most applications should begin with one agent. Teams and workflows become useful only when one agent stops being the right unit of control.

## Why personaforge

The framework is organized around a small set of engineering choices that are easy to explain and hard to outgrow.

### One package, clear public subpaths

Install `personaforge` once. Use focused public subpaths only when a module has its own runtime surface, such as sessions, orchestration, scheduling, or observability.

### Plain TypeScript, not a separate DSL

You write ordinary application code. Agents, tools, sessions, serving, and orchestration live close to the rest of your system instead of in a parallel configuration language.

### Production layers are built in, not bolted on later

Sessions, retrieval, serving, observability, evaluation, approvals, and runtime controls are available when you need them. They are not prerequisites for the first hello world.

### Start small, then add capability in layers

The intended progression is:

1. one agent
2. one missing capability at a time
3. one runtime surface at a time

## The main layers

| Layer | What it adds | Typical reason to add it |
|---|---|---|
| Agent | instructions, model selection, tool access, execution behavior | the first useful version |
| Tools | live data access and side effects | the model should stop guessing and use real systems |
| Sessions and memory | continuity and retained facts | the interaction spans several turns or should remember preferences |
| Knowledge and storage | document-backed context and durable state | answers should come from source material or persisted data |
| Runtime | HTTP delivery and scheduled execution | the agent becomes a service or job |
| Coordination | composition, teams, supervision, reasoning | the control flow or responsibility split becomes important |
| Operations | guardrails, approvals, resilience, traces, evals | the system needs production controls |

## Capabilities

The point of the framework is not just to give you a prompt wrapper. It is to give you a path from useful prototype to production system.

| Capability | What it gives you |
|---|---|
| Models | one public authoring surface with broad provider support |
| Tools | custom tools, wrappers, composition, and broader tool infrastructure |
| Sessions | continuity across runs |
| Memory | retained facts and selective recall |
| Knowledge | retrieval-backed answers from indexed source material |
| Storage | durable state outside the conversation loop |
| Orchestration | teams, supervisors, roles, tasks, and handoffs |
| Reasoning | explicit step-by-step reasoning loops when the task needs them |
| Scheduling | cron-like or queued runtime execution |
| Observability | traces, metrics, and run-level visibility |
| Evals | regression-oriented quality checks |
| Guardrails and HITL | validation, approval, and policy-driven runtime control |
| Serve | HTTP runtime for production-facing agents |

## How to approach your first build

The most reliable first pass looks like this:

1. Pick one narrow task.
2. Build one agent that handles that task.
3. Validate one successful run.
4. Add only the next missing capability.

That sequence matters because it keeps failures local. When a new layer is introduced, you should be able to tell what changed.

## What people usually add too early

Most early complexity comes from adding advanced layers before the base agent behavior is stable. The usual examples are:

- multi-agent orchestration before one agent has proven useful
- several tools before one tool has proven necessary
- runtime controls before the core prompt behavior is known
- multiple providers before the task shape is clear

Those features are valuable later. They are just not the right place to start.

## Where to go next

- Read `getting-started.md` for the first implementation path.
- Read `concepts.md` for the layered mental model.
- Read [Framework Comparisons](./comparisons) to see how personaforge compares to LangChain, CrewAI, LangGraph, Mastra, and Agno.
- Read [Trust & Reliability](./trust) for security policy, test coverage, benchmarks, and governance.
- Read `examples/index.md` for runnable patterns by difficulty.
- Read `api/` when you want a compact map of the public surfaces.


# Guide: learning-machine

# Learning Machine

`LearningMachine` coordinates multiple memory store types under a single API. It retrieves relevant context before each LLM call and extracts new learnings after each turn — keeping agents adaptive without making the system opaque.

```
import { LearningMachine } from 'personaforge';
```

---

## Quick start

```
import { LearningMachine } from 'personaforge';
import { SqliteAgentDb } from 'personaforge';
import { createAgent } from 'personaforge';

const db = new SqliteAgentDb({ path: './agent.db' });

// All five stores auto-created using the db backend
const machine = new LearningMachine({ db });

const agent = createAgent({
  name: 'adaptive-assistant',
  instructions: 'Help users. Use your memory to personalise responses.',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
  hooks: {
    buildSystemPrompt: async (base, ctx) => {
      // Inject remembered context into every run
      const memory = await machine.buildContext({
        userId:    ctx.userId,
        sessionId: ctx.sessionId,
        message:   ctx.prompt,
      });
      return memory ? `${base}\n\n${memory}` : base;
    },
    afterRun: async (result) => {
      // Extract and persist new learnings from this turn
      await machine.process({
        messages:  result.messages,
        userId:    result.userId,
        sessionId: result.sessionId,
      });
      return result;
    },
  },
});
```

---

## Store types

Each store is opt-in — use only what the task requires:

| Store | Purpose |
|---|---|
| `userProfile` | Structured user attributes (name, preferences, language) |
| `userMemory` | Unstructured user memories (free-text facts per user) |
| `sessionContext` | Per-session summary, current goal, and plan |
| `entityMemory` | Memories about companies, projects, people (named entities) |
| `learnedKnowledge` | Reusable insights and patterns across all users |
| `decisionLog` | Log of agent decisions for auditability and reflection |

---

## `LearningMachine` API

```
// Build a context string to inject into the system prompt
const context = await machine.buildContext({
  userId: 'user-42',
  sessionId: 'sess-xyz',
  message: 'What were we working on last time?',
  namespace: 'default',     // optional: scope entity/knowledge queries
});

// Raw recall — returns one key per store (useful for inspection/testing)
const recalled = await machine.recall({ userId: 'user-42', sessionId: 'sess-xyz' });
// recalled.userProfile, recalled.userMemory, recalled.sessionContext...

// Process a completed turn and persist learnings.
// `messages` is required. The base implementation is effectively a no-op —
// extend it with custom stores to plug in LLM-based extraction.
await machine.process({
  messages,
  userId: 'user-42',
  sessionId: 'sess-xyz',
});

// Get callable tools the agent can use to update its own memory.
// getTools() is synchronous and returns bare callables (LearningTool[]) —
// plain functions, NOT framework Tools.
const tools = machine.getTools({ userId: 'user-42' });
// Returns: [addMemory, updateMemory, deleteMemory, updateContext, addEntityFact,
//   addEntityEvent, saveKnowledge, searchKnowledge, logDecision, searchDecisions]
```

---

## `LearningMachineConfig`

```
interface LearningMachineConfig {
  /** Structured user profile store */
  userProfile?: UserProfileStore;
  /** Unstructured user memory store */
  userMemory?: UserMemoryStore;
  /** Per-session context store */
  sessionContext?: SessionContextStore;
  /** Entity memory store */
  entityMemory?: EntityMemoryStore;
  /** Learned knowledge store */
  learnedKnowledge?: LearnedKnowledgeStore;
  /** Decision log store */
  decisionLog?: DecisionLogStore;
  /** Curator for pruning and deduplicating memories */
  curator?: Curator;
  /** Optional db backend — any unspecified store is auto-created */
  db?: AgentDb;
  /** Default namespace for entity/knowledge stores (default: 'global') */
  namespace?: string;
  debug?: boolean;
}
```

---

## Self-updating memory tools

Give the agent tools to update its own memory during a run:

```
import { tool } from 'personaforge';
import { z } from 'zod';

const machine = new LearningMachine({ db });

// getTools() is synchronous and returns bare callables (LearningTool[]), not
// framework Tools — wrap each with tool() before passing to createAgent.
// (Most take a single string argument; widen the schema per tool as needed.)
const memoryTools = machine.getTools({ userId: 'user-42' }).map((fn) =>
  tool({
    name: fn.name,                          // e.g. 'addMemory', 'saveKnowledge'
    description: `Learning memory tool: ${fn.name}`,
    parameters: z.object({ input: z.string() }),
    execute: async ({ input }) => String(await fn(input)),
  }),
);

const agent = createAgent({
  name: 'personal-assistant',
  instructions: 'Help the user. Use memory tools to save important facts.',
  model: 'gpt-4o',
  apiKey: process.env.OPENAI_API_KEY!,
  tools: memoryTools,   // agent can call addMemory, addEntityFact, saveKnowledge, etc.
});
```

---

## Where to go next

- [Memory](./memory) — underlying memory stores (InMemoryMemoryStore, DbMemoryStore).
- [Session](./session) — session continuity underlying `sessionContext`.
- [Eval](./eval) — measure whether learning is improving outcomes.


# Guide: llm-router

# LLM Router

`LLMRouter` implements `LLMProvider`, so it drops in wherever a provider is expected. It classifies each request by task type and complexity (locally, with no extra API calls) and selects the best entry from a configured table.

## Quick start

```
import { createAgent, AnthropicProvider, OpenAIProvider, createSmartRouter } from 'personaforge';

const openai    = new OpenAIProvider({ apiKey: process.env.OPENAI_API_KEY! });
const anthropic = new AnthropicProvider({ apiKey: process.env.ANTHROPIC_API_KEY! });

const router = createSmartRouter([
  {
    provider: openai,
    model: 'gpt-4o-mini',
    capabilities: ['simple', 'coding', 'tool_use'],
    costTier: 'small',
    speedTier: 'fast',
    contextWindow: 128_000,
  },
  {
    provider: anthropic,
    model: 'claude-sonnet-4-20250514',
    capabilities: ['reasoning', 'coding', 'long_context'],
    costTier: 'large',
    speedTier: 'medium',
    contextWindow: 200_000,
  },
]);

const agent = createAgent({
  name: 'smart-assistant',
  instructions: 'You are a helpful assistant.',
  llm: router,   // ← drop-in replacement for any provider
});

const result = await agent.run('Design a TypeScript retry utility with exponential backoff.');
console.log(result.text);
console.log(router.getLastRouteDecision());
// { model: 'claude-sonnet-4-20250514', detectedTask: 'coding', strategy: 'adaptive', ... }
```

---

## Router entry fields

| Field | Required | Description |
|---|---|---|
| `provider` | ✓ | Any `LLMProvider` instance |
| `model` | ✓ | Human-readable model name used in logs and decisions |
| `capabilities` | ✓ | Task types this model handles well (see below) |
| `costTier` | — | `nano` · `small` · `medium` · `large` · `frontier` |
| `speedTier` | — | `fast` · `medium` · `slow` |
| `contextWindow` | — | Max tokens; requests exceeding this skip the entry (default: 128 000) |
| `qualityScore` | — | Override quality 0–10; inferred from `costTier` when omitted |

### Task types

```
type TaskType =
  | 'simple'        // factual Q&A, short replies
  | 'coding'        // code generation, debugging, review
  | 'reasoning'     // multi-step logic, math, analysis
  | 'creative'      // stories, marketing, brainstorming
  | 'tool_use'      // requests that supply tools / function-calling
  | 'long_context'  // large prompts (RAG, document analysis)
  | 'multimodal';   // image / audio / video content
```

---

## Routing strategies

Pass `strategy` to `new LLMRouter({ ... })` or use a factory:

| Strategy | Factory | Behaviour |
|---|---|---|
| `adaptive` | `createSmartRouter()` | Weighted score across quality, cost, speed, and task fit. **Recommended default.** |
| `balanced` | `createBalancedRouter()` | Cheapest viable model; auto-escalates for demanding tasks |
| `cost` | `createCostOptimizedRouter()` | Always picks the cheapest capable entry |
| `quality` | `createQualityFirstRouter()` | Always picks the highest quality capable entry |
| `speed` | `createSpeedOptimizedRouter()` | Always picks the fastest capable entry |

### Adaptive weights (optional tuning)

```
const router = createSmartRouter(entries, {
  adaptiveWeights: {
    quality:       0.5,   // default: 0.35
    cost:          0.2,   // default: 0.28
    speed:         0.15,  // default: 0.27
    capabilityFit: 0.15,  // default: 0.25
  },
});
```

---

## Factory functions

### `createSmartRouter` — adaptive (recommended)

```
import { createSmartRouter } from 'personaforge';

const router = createSmartRouter(entries, {
  debug: true,               // log every decision
  rules: [...],              // override rules evaluated first
  adaptiveWeights: { ... },  // tune quality/cost/speed/fit tradeoffs
  classifyTask:      (ctx) => 'coding',    // custom task classifier
  classifyComplexity:(ctx) => 'high',      // custom complexity classifier
});
```

### `createBalancedRouter`

```
import { createBalancedRouter } from 'personaforge';

const router = createBalancedRouter(entries, { debug: true });
```

### `createCostOptimizedRouter` / `createQualityFirstRouter` / `createSpeedOptimizedRouter`

```
import { createCostOptimizedRouter, createQualityFirstRouter, createSpeedOptimizedRouter } from 'personaforge';

const cheap   = createCostOptimizedRouter(entries);
const quality = createQualityFirstRouter(entries);
const fast    = createSpeedOptimizedRouter(entries);
```

### `new LLMRouter` — full config control

```
import { LLMRouter } from 'personaforge';

const router = new LLMRouter({
  strategy: 'balanced',
  entries: [
    { provider: openai,    model: 'gpt-4.1-nano',    capabilities: ['simple'],          costTier: 'nano',     speedTier: 'fast',   contextWindow: 8_000   },
    { provider: openai,    model: 'gpt-4o-mini',     capabilities: ['simple','coding'], costTier: 'small',    speedTier: 'fast',   contextWindow: 128_000 },
    { provider: openai,    model: 'gpt-4.1',         capabilities: ['coding','creative'],costTier: 'medium',  speedTier: 'medium', contextWindow: 128_000 },
    { provider: anthropic, model: 'claude-sonnet-4', capabilities: ['coding','reasoning'],costTier: 'large',  speedTier: 'medium', contextWindow: 200_000 },
    { provider: anthropic, model: 'claude-opus-4',   capabilities: ['reasoning'],       costTier: 'frontier', speedTier: 'slow',   contextWindow: 200_000 },
  ],
  fallbackEntryIndex: 3,  // default fallback when all else fails
  debug: true,
});
```

---

## Override rules

Rules are evaluated before any strategy. The first matching rule wins.

```
import { createSmartRouter } from 'personaforge';

const router = createSmartRouter(entries, {
  rules: [
    {
      name: 'medical-query',
      match: (ctx) => ctx.messages.some(
        (m) => typeof m.content === 'string' && /diagnosis|symptom|medication/i.test(m.content)
      ),
      useEntry: 4,  // always use the frontier model for medical topics
    },
    {
      name: 'short-simple',
      match: (ctx) => ctx.estimatedTokens < 200 && ctx.detectedTask === 'simple',
      useEntry: 0,  // cheapest entry for trivial requests
    },
  ],
});
```

**`RouteContext` fields available inside `match`:**

```
interface RouteContext {
  messages: Message[];
  options?: GenerateOptions;
  detectedTask: TaskType;
  detectedComplexity: 'low' | 'medium' | 'high';
  estimatedTokens: number;
  hasTools: boolean;
  hasMultimodal: boolean;
}
```

---

## Inspecting decisions

```
// Last request's decision
const decision = router.getLastRouteDecision();
// {
//   model: 'claude-sonnet-4-20250514',
//   detectedTask: 'reasoning',
//   detectedComplexity: 'high',
//   strategy: 'adaptive',
//   reason: 'task=reasoning, complexity=high, strategy=adaptive, tokens≈1420',
//   estimatedTokens: 1420,
//   entryIndex: 3,
// }

// Full history (newest last, capped at 1000)
const history = router.getDecisionHistory();

// Reset
router.clearHistory();
```

---

## Complete example: five-model table

```
import {
  createSmartRouter,
  OpenAIProvider,
  AnthropicProvider,
  createGroqProvider,
  createAgent,
} from 'personaforge';

const openai    = new OpenAIProvider({ apiKey: process.env.OPENAI_API_KEY! });
const anthropic = new AnthropicProvider({ apiKey: process.env.ANTHROPIC_API_KEY! });
const groq      = createGroqProvider({ model: 'llama-3.1-8b-instant' });

const router = createSmartRouter([
  { provider: groq,      model: 'llama-3.1-8b-instant',    capabilities: ['simple'],                     costTier: 'nano',     speedTier: 'fast',   contextWindow: 8_000   },
  { provider: openai,    model: 'gpt-4o-mini',             capabilities: ['simple', 'coding', 'tool_use'],costTier: 'small',   speedTier: 'fast',   contextWindow: 128_000 },
  { provider: openai,    model: 'gpt-4o',                  capabilities: ['coding', 'creative'],          costTier: 'medium',  speedTier: 'medium', contextWindow: 128_000 },
  { provider: anthropic, model: 'claude-sonnet-4-20250514',capabilities: ['reasoning', 'long_context'],   costTier: 'large',   speedTier: 'medium', contextWindow: 200_000 },
  { provider: anthropic, model: 'claude-opus-4-20250514',  capabilities: ['reasoning'],                   costTier: 'frontier',speedTier: 'slow',   contextWindow: 200_000 },
], { debug: true });

const agent = createAgent({
  name: 'router-demo',
  instructions: 'You are a helpful assistant.',
  llm: router,
});

// Simple → groq or gpt-4o-mini
await agent.run('What is the capital of France?');

// Coding → gpt-4o or claude-sonnet
await agent.run('Write a TypeScript generic debounce function with proper types.');

// Complex reasoning → claude-sonnet or claude-opus
await agent.run('Analyse the philosophical implications of the ship of Theseus paradox across 4 ethical frameworks.');

for (const d of router.getDecisionHistory()) {
  console.log(`${d.model.padEnd(30)} task=${d.detectedTask} complexity=${d.detectedComplexity}`);
}
```

---

## Where to go next

- [Providers](./providers) — all supported LLM providers.
- [Production](./production) — circuit breakers, budget enforcement, and rate limiting.


# Guide: loaders

# Document Loaders

Loaders convert external data sources into `Document[]` objects ready for ingestion. All loaders share the same output shape, so you can mix sources and send them to the same vector store.

```
import {
  loadPdf, loadCsv, loadUrl,                           // original loaders
  loadMarkdown, loadMarkdownText,
  loadHtml, loadHtmlText,
  loadJson,
  loadDocx,
  loadSitemap,
  loadGithubRepo,
  loadS3,
} from 'personaforge/knowledge';
```

---

## Markdown

Splits on headings. Each heading section becomes a separate document, with the heading text in `metadata.heading`.

```
const docs = await loadMarkdown('./README.md');
// Or from a string:
const docs = loadMarkdownText(rawString, { source: 'README.md' });
```

---

## HTML

Strips `<script>`, `<style>`, all tags, and decodes HTML entities.

```
const docs = await loadHtml('./page.html');
// Or from a string:
const doc = loadHtmlText(rawHtml, { source: 'page.html' });
```

---

## JSON / JSONL

Each object in the array (or each line for `.jsonl`) becomes a `Document`.

```
const docs = await loadJson('./data.json', { contentField: 'description' });
const docs = await loadJson('./events.jsonl', { contentField: 'text' });
```

When `contentField` is omitted the full object is JSON-stringified as content.

---

## DOCX

Extracts text from `.docx` files. Uses the `mammoth` peer dependency when available; falls back to basic XML extraction.

```
const docs = await loadDocx('./report.docx');
```

Install `mammoth` for better extraction: `npm i mammoth`. The basic XML fallback uses `adm-zip` (`npm i adm-zip`).

---

## Sitemap

Fetches a `sitemap.xml`, crawls each `<loc>` URL (up to `maxPages`), strips HTML.

```
const docs = await loadSitemap('https://example.com/sitemap.xml', {
  maxPages: 100,
});
```

---

## GitHub repository

Fetches a repository's file tree from the GitHub API and loads text files.

```
const docs = await loadGithubRepo({
  owner: 'personaforge',
  repo: 'personaforge',
  branch: 'main',
  extensions: ['.md', '.ts'],
  maxFiles: 200,
  token: process.env.GITHUB_TOKEN,
});
```

---

## S3

Loads text objects from an S3 bucket. Requires `@aws-sdk/client-s3` (`npm i @aws-sdk/client-s3`).

```
const docs = await loadS3({
  bucket: 'my-docs-bucket',
  prefix: 'knowledge/',
  region: 'us-east-1',
});
```

---

## PDF / CSV / URL

The original loaders (`loadPdf`, `loadCsv`, `loadUrl`) are documented in the [RAG guide](/guide/rag).

---

## Metadata

Every loader accepts a `metadata?: Record<string, unknown>` option that is merged into each document's `metadata`. Loaders also add a `source` field automatically.

---

## After loading

Pass documents through a [text splitter](/guide/retrieval-advanced) and then into a vector store:

```
import { RecursiveCharacterSplitter, createKnowledgeEngine } from 'personaforge/knowledge';

const docs = await loadMarkdown('./docs.md');
const splitter = new RecursiveCharacterSplitter({ chunkSize: 800 });
const chunks = splitter.splitDocuments(docs.map((d) => ({ content: d.content, metadata: d.metadata })));

const engine = createKnowledgeEngine({ embed });
await engine.addDocuments(chunks.map((c) => ({
  id: crypto.randomUUID(),
  content: c.content,
  metadata: c.metadata,
})));
```

---

## Related pages

- [RAG / Knowledge](/guide/rag) — the KnowledgeEngine and vector stores.
- [Advanced Retrieval](/guide/retrieval-advanced) — splitters, BM25, hybrid, rerankers.


# Guide: mcp

# MCP (Model Context Protocol)

The framework ships a full MCP implementation: an HTTP client to consume remote tools, an HTTP server to expose your tools to other agents and clients, and a streamable SSE transport.

```
import {
  loadMcpToolsFromUrl,         // simplest: load tools from any MCP URL
  HttpMcpClient,               // full HTTP JSON-RPC 2.0 client
  connectMcpServer,            // SSE streamable transport client
  StreamableMcpClient,         // lower-level SSE client
  createMcpServer,             // expose your tools as an MCP server
  McpHttpServer,
  runMcpStdioToolServer,       // stdio transport (for Claude Desktop, etc.)
} from 'personaforge';
```

---

## Load tools from an MCP server (client)

The easiest way to use a remote MCP server — load its tools and hand them straight to `createAgent()`:

```
import { createAgent, loadMcpToolsFromUrl } from 'personaforge';

// Load all tools exposed by a remote MCP HTTP server
const mcpTools = await loadMcpToolsFromUrl(
  'https://mcp.example.com/tools',
  { 'Authorization': `Bearer ${process.env.MCP_TOKEN}` },  // optional headers
);

const agent = createAgent({
  name: 'mcp-agent',
  instructions: 'Use the available tools to complete tasks.',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
  tools: mcpTools,             // drop-in: MCP tools work exactly like local tools
});

const result = await agent.run('Run the analysis job for project #42.');
```

---

## Full HTTP client (`HttpMcpClient`)

```
import { HttpMcpClient } from 'personaforge';

const client = new HttpMcpClient({
  url: 'https://mcp.example.com',
  headers: { 'Authorization': `Bearer ${process.env.MCP_TOKEN}` },
  timeoutMs: 30_000,
});

// List available tools
const tools = await client.getTools();

// Call a tool directly
const result = await client.callTool('run_analysis', { projectId: '42', format: 'json' });
```

---

## SSE streamable transport (`connectMcpServer`)

Use the [MCP Streamable HTTP 2024-11-05](https://modelcontextprotocol.io/) transport for servers that push real-time notifications:

```
import { connectMcpServer } from 'personaforge';

const { client, tools } = await connectMcpServer('https://mcp.example.com/mcp', {
  headers: { 'Authorization': `Bearer ${process.env.MCP_TOKEN}` },
  preferStreaming: true,   // use SSE for streamed responses (default: true)
});

// Subscribe to server notifications (e.g. progress events)
client.onNotification('progress', (notification) => {
  console.log('Progress:', notification.params);
});

// Use tools with the agent
const agent = createAgent({
  name: 'streaming-mcp-agent',
  instructions: '...',
  model: 'gpt-4o',
  apiKey: process.env.OPENAI_API_KEY!,
  tools,
});
```

---

## Expose your tools as an MCP server

Turn any local tools into an MCP server that Claude Desktop, other agents, or any MCP client can connect to:

```
import { createMcpServer } from 'personaforge';
import { searchTool, analysisTool, calculatorTool } from './tools.js';

const server = createMcpServer(
  [searchTool, analysisTool, calculatorTool],
  {
    name: 'my-agent-tools',
    version: '1.0.0',
    port: 3100,
    auth: {
      type: 'bearer',
      token: process.env.MCP_SERVER_TOKEN!,
    },
    cors: { allowedOrigins: ['https://claude.ai', 'https://my-app.example.com'] },
    toolTimeoutMs: 60_000,
  },
);

await server.start();
console.log('MCP server running on http://localhost:3100/mcp');

// Graceful stop
process.on('SIGTERM', () => server.stop());
```

The server exposes these JSON-RPC 2.0 methods:
- `initialize` — handshake and capabilities
- `tools/list` — list all registered tools
- `tools/call` — execute a tool by name

---

## Stdio server (Claude Desktop)

For Claude Desktop and other stdio-based MCP clients:

```
import { runMcpStdioToolServer } from 'personaforge';
import { searchTool, calculatorTool } from './tools.js';

// Reads from stdin, writes to stdout — launch from claude_desktop_config.json
await runMcpStdioToolServer({
  tools: [searchTool, calculatorTool],
  name: 'my-tools-server',
  version: '1.0.0',
});
```

`claude_desktop_config.json` entry:
```
{
  "mcpServers": {
    "my-tools": {
      "command": "node",
      "args": ["/path/to/my-mcp-server.js"]
    }
  }
}
```

---

## MCP resources and prompts

```
import { McpResourceRegistry, McpPromptRegistry } from 'personaforge';

const resources = new McpResourceRegistry();
resources.define({
  uri: 'file://config.json',
  name: 'App Config',
  description: 'Current application configuration',
  mimeType: 'application/json',
  fetch: async () => JSON.stringify(await loadConfig()),
});

const prompts = new McpPromptRegistry();
prompts.define({
  name: 'summarise',
  description: 'Summarise a document',
  arguments: [{ name: 'document', description: 'The document to summarise', required: true }],
  render: async ({ document }) => [
    { role: 'user', content: `Summarise this document:\n\n${document}` },
  ],
});
```

---

## Where to go next

- [Tools](./tools) — local tool authoring with `tool()`.
- [Custom tools](./custom-tools) — extend or wrap existing tools.
- [Example 14: MCP](../examples/14-mcp) — full client + server example.


# Guide: memory

# Memory

Memory lets an agent retain and recall selected facts across runs. The framework ships multiple store backends and a distiller for compressing conversation history into compact summaries.

## Quick start

```
import { createAgent, InMemoryStore } from 'personaforge';

const agent = createAgent({
  name: 'personal-assistant',
  instructions: 'You are a personal assistant. Remember user preferences.',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
  memoryStore: new InMemoryStore(),
  enableAgenticMemory: true,    // gives agent remember() and recall() tools
  addMemoriesToContext: true,   // auto-prepend recalled memories to each run
  numMemories: 5,               // max memories added to context (default: 5)
});

await agent.run('I prefer TypeScript and dark mode.', { userId: 'alice' });

// Later session — agent recalls these facts automatically
const result = await agent.run('What languages do I use?', { userId: 'alice' });
console.log(result.text);  // references TypeScript
```

---

## The unified `Memory` layer

`Memory` is the modern, all-in-one way to give agents durable memory — message history, working memory, semantic recall, observational memory, and even a mem0-style fact engine. It mirrors Mastra's `@mastra/memory` API and is **production-ready by default** (libSQL persistence, zero-config semantic recall).

```
import { createAgent, Memory } from 'personaforge';

const memory = new Memory({
  // storage defaults to libSQL (`:memory:`, or `file:`/remote when LIB_SQL_URL is set)
  options: {
    lastMessages: 20,                       // history window
    workingMemory: { template: '# Profile\n- Name:\n- Location:' },
  },
});

const agent = createAgent({
  name: 'assistant',
  instructions: 'You are a helpful assistant.',
  model: 'gpt-4o',
  apiKey: process.env.OPENAI_API_KEY!,
  memory,                                   // ← attaches threads/working-memory/tools
});

// Scope every run with a thread + resource (user/entity).
await agent.run('Remember I like dark mode.', { memory: { thread: 't1', resource: 'alice' } });
```

When `memory` is set, `createAgent`:

- loads/persists message history through the `Memory` bundle (instead of the legacy `sessionStore` blob),
- injects working memory and observational memory as system context,
- registers the memory agent tools (`updateWorkingMemory`, `recall_memory`, mem0 tools),
- automatically runs observational buffering, mem0 extraction and semantic indexing after each turn.

### Thread stores — libSQL by default

Threads and messages persist through a `ThreadStore`. The default is **libSQL** (`@libsql/client`) — local `file:` databases, shared `:memory:`, or Turso cloud (`libsql://`). If libSQL isn't installed it falls back to in-memory; `better-sqlite3` is also supported.

```
import { createAgent, Memory, createThreadStore } from 'personaforge';

const storage = createThreadStore({ url: 'file:./memory.db' }); // durable, recommended
const memory  = new Memory({ storage });

// or equivalently via env:
//   LIB_SQL_URL=file:./memory.db

// available stores
import { InMemoryThreadStore, LibSqlThreadStore, SqliteThreadStore } from 'personaforge';
new Memory({ storage: new InMemoryThreadStore() });   // dev / tests
new Memory({ storage: new LibSqlThreadStore({ url: 'libsql://my-db-org.turso.io', authToken }) });
new Memory({ storage: new SqliteThreadStore({ path: './memory.db' }) }); // better-sqlite3
```

### Working memory

A persistent, always-available block (user profile / task state) injected as a system message every turn. Two styles:

```
// Markdown template — the agent rewrites the whole block.
new Memory({
  options: {
    workingMemory: { template: `
# User Profile
- Name:
- Location:
- Communication style:
` },
  },
});

// Structured JSON (deep-merge updates; null deletes fields; arrays replace).
import { z } from 'zod';
new Memory({
  options: {
    workingMemory: {
      schema: z.object({ name: z.string(), prefs: z.record(z.any()) }),
      scope: 'resource',   // shared across all threads of the user (default)
    },
  },
});
```

Programmatic API:

```
await memory.updateWorkingMemory({ threadId: 't1', resourceId: 'alice', workingMemory: '...' });
const block = await memory.getWorkingMemory({ threadId: 't1', resourceId: 'alice' });
```

### Semantic recall

RAG over past messages. Enable with `semanticRecall: true` — zero-config: a deterministic local hashing embedder + in-memory vector store work out of the box. Bring a real embedder + vector store for production-grade similarity.

```
import { createAgent, Memory, InMemoryVectorStore, OpenAIEmbeddingProvider } from 'personaforge';

const memory = new Memory({
  vector: new InMemoryVectorStore(),
  embedder: new OpenAIEmbeddingProvider({ apiKey: process.env.OPENAI_API_KEY! }),
  options: { semanticRecall: { topK: 3, scope: 'resource' } },
});

// Direct recall anywhere
const { messages } = await memory.recall({
  threadId: 't1',
  vectorSearchString: 'what code theme do I prefer?',
  perPage: 5,
});
```

### Observational memory

Compress very long conversations into a dense observation log with background Observer/Reflector agents — the context window stays bounded no matter how long the thread runs.

```
const memory = new Memory({
  llm, // agent-level LLM is bound automatically when using createAgent
  options: {
    observationalMemory: {
      messageTokens: 30_000,     // when to compress history (default 30k)
      observationTokens: 40_000, // when to reflect/condense the log (default 40k)
      bufferTokens: 0.2,         // background buffering cadence
      observation: {
        manageWorkingMemory: true, // Observer keeps the user profile fresh
        extract: [new Extractor({ name: 'Blockers', instructions: 'What is blocking the user?' })],
      },
    },
  },
});
```

When activated, observed messages leave the context window and a compact `[Observational Memory]` system block (plus a continuation hint) takes their place. Raw messages stay in storage for the `recall_memory` tool.

### mem0-style memory

An LLM extracts discrete, editable **facts** from each turn (mem0's `ADD / UPDATE / NONE / DELETE` pipeline), stored and searchable with a CRUD API + agent tools.

```
import { createAgent, Memory } from 'personaforge';

const memory = new Memory({
  llm,
  options: {
    mem0: { autoExtract: true }, // extract + store facts after every run
  },
});

// programmatic API
await memory.mem0?.add('Alice prefers dark mode', { userID: 'alice' });
const facts = await memory.mem0?.search('theme preference', { userID: 'alice' });

// standalone engine (anywhere)
import { Mem0Memory, InMemoryMem0Store } from 'personaforge';
const mem0 = new Mem0Memory({
  llm,
  store: new InMemoryMem0Store(),
  embedder: undefined, // optional semantic search
  vectorStore: undefined, // optional semantic search
});
await mem0.processMessages(conversation, { userID: 'alice' });
```

### Memory processors (Mastra parity)

The individual pieces are also available as standalone `Processor`s for the agent processor pipeline: `MessageHistoryProcessor`, `SemanticRecallProcessor`, `WorkingMemoryProcessor`, `TokenLimiterProcessor`, `ObservationalMemoryProcessor`, and `Mem0ExtractionProcessor`. You can compose them manually with `memory.getProcessors()` or reference them directly when building a custom runner.

### Scoped, multi-user threads

Every thread belongs to a single `resourceId` (user/entity); a resource can own many threads. Threads are isolated first-class records — use one `thread` per conversation and one `resource` per user:

```
await agent.run('...', { memory: { thread: 'support-42', resource: 'user-7' } });
await memory.listThreads({ resourceId: 'user-7' }); // all of user-7's conversations
```

---

## Memory stores

### `InMemoryStore`

In-process store. Cleared when the process restarts. Good for prototyping.

```
import { InMemoryStore } from 'personaforge';

const memoryStore = new InMemoryStore();
```

### `VectorMemoryStore`

Semantic search over stored memories using embeddings.

```
import { VectorMemoryStore, InMemoryVectorStore, OpenAIEmbeddingProvider } from 'personaforge';

const memoryStore = new VectorMemoryStore({
  vectorStore: new InMemoryVectorStore(),
  embeddingProvider: new OpenAIEmbeddingProvider({
    apiKey: process.env.OPENAI_API_KEY!,
    model: 'text-embedding-3-small',
  }),
});
```

### Pinecone

```
import { VectorMemoryStore, PineconeVectorStore, OpenAIEmbeddingProvider } from 'personaforge';

const memoryStore = new VectorMemoryStore({
  vectorStore: new PineconeVectorStore({
    apiKey: process.env.PINECONE_API_KEY!,
    indexName: 'agent-memories',
  }),
  embeddingProvider: new OpenAIEmbeddingProvider({ apiKey: process.env.OPENAI_API_KEY! }),
});
```

### Qdrant

```
import { VectorMemoryStore, QdrantVectorStore, OpenAIEmbeddingProvider } from 'personaforge';

const memoryStore = new VectorMemoryStore({
  vectorStore: new QdrantVectorStore({
    url: process.env.QDRANT_URL!,
    collectionName: 'memories',
  }),
  embeddingProvider: new OpenAIEmbeddingProvider({ apiKey: process.env.OPENAI_API_KEY! }),
});
```

### PgVector (PostgreSQL)

```
import { VectorMemoryStore, PgVectorStore, OpenAIEmbeddingProvider } from 'personaforge';

const memoryStore = new VectorMemoryStore({
  vectorStore: new PgVectorStore({
    connectionString: process.env.DATABASE_URL!,
    tableName: 'agent_memories',
  }),
  embeddingProvider: new OpenAIEmbeddingProvider({ apiKey: process.env.OPENAI_API_KEY! }),
});
```

### Database-backed store (`DbMemoryStore`)

Persists to the framework's built-in SQLite/Postgres AgentDb:

```
import { createDbMemoryStore } from 'personaforge/memory';
import { SqliteAgentDb } from 'personaforge/db';

// Pass an AgentDb instance positionally; options are optional.
const db = new SqliteAgentDb({ path: './agent.db' });
const memoryStore = createDbMemoryStore(db, { agentId: 'my-agent' });
```

---

## Agentic memory tools

When `enableAgenticMemory: true`, the agent gets two tools:

- **`remember(fact: string)`** — explicitly stores a fact
- **`recall(query: string)`** — retrieves relevant memories

The agent decides when to call these. Pair with `addMemoriesToContext: true` to also automatically prepend relevant memories before each run.

```
const agent = createAgent({
  name: 'assistant',
  instructions: `
    You are a personal assistant.
    Use remember() to store any user preference, fact, or important detail.
    Recalled memories will appear at the top of each conversation.
  `,
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
  memoryStore: new InMemoryStore(),
  enableAgenticMemory: true,
  addMemoriesToContext: true,
});
```

---

## Memory distiller

Compress conversation history into concise summaries to prevent context overflow:

```
import { MemoryDistiller, summariseMemories, summariseConversation } from 'personaforge/memory';
import { InMemoryStore } from 'personaforge';
import { OpenAIProvider } from 'personaforge';

const llm = new OpenAIProvider({ apiKey: process.env.OPENAI_API_KEY! });
const store = new InMemoryStore();

const distiller = new MemoryDistiller({
  store,                 // the MemoryStore to read short-term entries from and write summaries to
  llm,
  agentId: 'agent-123',  // optional: scope distillation to one agent
  triggerThreshold: 20,  // auto-distill once this many short-term entries accumulate (default: 20)
  batchSize: 30,         // max entries consumed per pass (default: 30)
  // intervalMs: 60_000, // optional background polling; omit to distill manually
});

// Run a distillation pass now. Returns DistillationResult { consumed, summary, skipped }.
const result = await distiller.distillNow(true);  // force = true ignores the threshold
if (result.summary) console.log(result.consumed, result.summary.content);

// One-shot helpers (entries/messages first, llm second; each returns a string)
const memorySummary = await summariseMemories(memories, llm);
const conversationSummary = await summariseConversation(messages, llm);
```

---

## Summary buffer middleware

Automatically compress conversation history when it grows too long:

```
import { createAgent } from 'personaforge';
import { createSummaryBufferHook } from 'personaforge/memory';
import { OpenAIProvider } from 'personaforge';

const llm = new OpenAIProvider({ apiKey: process.env.OPENAI_API_KEY! });
const summaryHook = createSummaryBufferHook({
  llm,
  maxMessages: 20,   // compress when history exceeds this many messages (default: 30)
  keepLastN: 5,      // always keep the last N messages verbatim (default: 10)
  // summarizePrompt: '...' // optional: override the summarisation system prompt
});

const agent = createAgent({
  name: 'long-chat-agent',
  instructions: 'You are a long-running assistant.',
  llm,
  hooks: { beforeStep: summaryHook },
});
```

---

## Direct memory store usage

You can read and write the memory store directly without an agent:

```
import { InMemoryStore, MemoryType } from 'personaforge';

const store = new InMemoryStore();

// Write — store(entry), where entry is { type, content, metadata }.
// The id and createdAt are assigned for you and returned on the entry.
const entry = await store.store({
  type: MemoryType.LONG_TERM,
  content: 'Prefers TypeScript',
  metadata: { agentId: 'alice', tags: ['user-pref'] },
});

// Retrieve — retrieve(query) with { query, type?, limit?, threshold?, filter? }.
// Scope with `filter` (agentId, sessionId, tags, …). Semantic stores rank by
// similarity; InMemoryStore uses keyword/substring matching.
const results = await store.retrieve({
  query: 'programming language',
  limit: 5,
  filter: { agentId: 'alice' },
});
console.log(results);  // MemorySearchResult[] — each { entry, score }

// Delete by id
await store.delete(entry.id);
```

---

## Self-editing tiered memory (MemGPT / Letta style)

`TieredMemory` gives an agent two tiers it manages itself:

- **Core memory** — small labelled blocks (`persona`, `human`, …) always rendered into the prompt via `renderCore()`. Each block is character-limited; the default ceiling is `DEFAULT_BLOCK_LIMIT` (2 000 chars).
- **Archival memory** — an unbounded `MemoryStore` searched on demand.

`createTieredMemoryTools(memory)` returns the four LLM-callable tools (`core_memory_append`, `core_memory_replace`, `archival_memory_insert`, `archival_memory_search`) so the agent edits both tiers on its own.

```
import { createAgent, TieredMemory, createTieredMemoryTools, InMemoryStore, DEFAULT_BLOCK_LIMIT } from 'personaforge';

const tiered = new TieredMemory({
  blocks: [
    { label: 'persona', value: 'I am a helpful research assistant.' },
    { label: 'human',   value: '' },
    { label: 'scratchpad', value: '', limit: DEFAULT_BLOCK_LIMIT },
  ],
  archival: new InMemoryStore(),   // backs the archival_memory_* tools
});

const agent = createAgent({
  name: 'Letta',
  instructions: `You are an assistant.\n\n${tiered.renderCore()}`,
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
  tools: Object.values(createTieredMemoryTools(tiered)),
});
```

---

## Graph (entity) memory

`GraphMemory` stores typed entities and labelled relations the agent can traverse ("who works where", "what depends on what") instead of only fuzzy-matching by embedding. `createGraphMemoryTools(graph)` exposes `add_entity`, `add_relation`, and `search_graph` so the agent builds and queries the graph itself.

```
import { createAgent, GraphMemory, createGraphMemoryTools } from 'personaforge';

const graph = new GraphMemory();
graph.addRelation('Jordan', 'works_at', 'AcmeCorp');
graph.addRelation('Jordan', 'lives_in', 'Lisbon');

graph.search('Jordan');  // → ['Jordan works_at AcmeCorp', 'Jordan lives_in Lisbon']
graph.toFacts();         // every relation as a fact line — handy for dumping into a prompt

const agent = createAgent({
  name: 'graph-agent',
  instructions: 'Track facts about people and organisations.',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
  tools: Object.values(createGraphMemoryTools(graph)),
});
```

---

## `remember` / `recall` tools

`createAgentMemoryTools({ store })` returns two ready-to-register tools — `remember(fact, tags?)` and `recall(query, limit?)` — backed by any `MemoryStore`. This is the explicit, tool-based alternative to the `enableAgenticMemory` shortcut used in the quick start.

```
import { createAgent, InMemoryStore } from 'personaforge';
import { createAgentMemoryTools } from 'personaforge/memory';

const { remember, recall } = createAgentMemoryTools({ store: new InMemoryStore() });

const agent = createAgent({
  name: 'ResearchBot',
  instructions: 'Remember useful facts and recall them when relevant.',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
  tools: [remember, recall],
});
```

---

## Where to go next

- [RAG](./rag) — retrieve from indexed documents (different from persisted memories).
- [Session](./session) — per-conversation turn history.
- [Agents](./agents) — how to attach a memory store to `createAgent()`.


# Guide: migration-agno

# Migrate From Agno

Agno is a Python-first agent framework with strong multi-agent and reasoning-tool patterns. `personaforge` provides the same concepts in **TypeScript-native** form — with durable execution, guardrails, budget enforcement, and OTLP tracing built in.

> **Note:** Agno runs on Python; personaforge runs on TypeScript/Node/Bun. This guide maps **concepts and patterns**, not a line-for-line port.

---

## Quick comparison

| Agno concept | personaforge equivalent |
|---|---|
| `Agent(model, instructions, tools)` | `createAgent({ model, instructions, tools })` |
| `Team(members, mode)` | `createSupervisor()` or `createOrchestrator()` |
| `Knowledge` / vector DB | `createKnowledgeBase()` + `contextProviders` |
| `Memory` / session history | `agent.createSession({ sessionId })` |
| `Storage` (SQLite, Postgres) | [Storage](./storage) + [Session](./session) |
| `think` / `analyze` tools | [Reasoning Tools](./reasoning-tools) — `createReasoningTools()` |
| `Workflow` | `compose()` / `pipe()` or [Graph Engine](./graph) |
| `AgentOS` / REST serving | [Production](./production) + automatic REST API |
| `ReasoningAgent` | `createAgent` + reasoning tools or [Reasoning (CoT)](./reasoning) |
| `DeepResearch` | [Deep Research Agent](./deep-research) |

---

## Agent migration

```python
# Agno
from agno.agent import Agent
from agno.models.openai import OpenAIChat

agent = Agent(
    model=OpenAIChat(id="gpt-4o"),
    instructions="You are a helpful research assistant.",
    tools=[search_tool],
)
response = agent.run("What are the latest AI trends?")
```

```
// personaforge
import { createAgent } from 'personaforge';
import { webSearchTool } from 'personaforge';

const agent = createAgent({
  name: 'researcher',
  instructions: 'You are a helpful research assistant.',
  model: 'gpt-4o',
  apiKey: process.env.OPENAI_API_KEY!,
  tools: [webSearchTool],
});

const result = await agent.run('What are the latest AI trends?');
// result.text
```

---

## Team → supervisor

```python
# Agno
from agno.team import Team

team = Team(
    members=[researcher, writer],
    mode="coordinate",
)
response = team.run("Write a report on quantum computing.")
```

```
// personaforge
import { createSupervisor } from 'personaforge';

const supervisor = createSupervisor({
  name: 'project-lead',
  instructions: 'Coordinate the research and writing agents to complete the task.',
  model: 'gpt-4o',
  apiKey: process.env.OPENAI_API_KEY!,
  workers: [researcher, writer],
});

const result = await supervisor.run('Write a report on quantum computing.');
```

See [Team Modes](./team-modes) for all six coordination patterns (supervisor, handoff, consensus, and more).

---

## Knowledge / RAG

```python
# Agno
from agno.knowledge import Knowledge
from agno.vectordb.pgvector import PgVector

knowledge = Knowledge(vector_db=PgVector(...))
agent = Agent(knowledge=knowledge, ...)
```

```
// personaforge
import { createKnowledgeBase } from 'personaforge';

const kb = await createKnowledgeBase({
  type: 'memory',  // or 'pgvector', 'chroma', etc.
  embedder: 'openai',
  apiKey: process.env.OPENAI_API_KEY!,
});
await kb.add(documents);

const agent = createAgent({
  name: 'support-agent',
  instructions: 'Answer questions using the knowledge base.',
  model: 'gpt-4o',
  apiKey: process.env.OPENAI_API_KEY!,
  contextProviders: [kb.asContextProvider()],
});
```

---

## Reasoning tools (`think` / `analyze`)

Agno's reasoning-as-tools pattern is a first-class module in personaforge:

```
import { agent } from 'personaforge';
import { ReasoningScratchpad, createReasoningTools } from 'personaforge/reasoning';

const scratchpad = new ReasoningScratchpad();
const { think, analyze } = createReasoningTools(scratchpad);

const researcher = agent({
  name: 'researcher',
  model: 'gpt-4o-mini',
  instructions: 'Use think to plan before acting, and analyze to review your reasoning.',
  tools: [think, analyze, webSearchTool],
});

await researcher.run('Compare three database options for our workload.');
console.log(scratchpad.render());
```

See [Reasoning Tools](./reasoning-tools) for the full API.

---

## Memory & sessions

```python
# Agno — session persists across runs
agent = Agent(storage=SqliteStorage(...), session_id="user-123")
agent.run("My name is Alice")
agent.run("What is my name?")  # remembers Alice
```

```
// personaforge
const session = agent.createSession({ sessionId: 'user-123' });
await session.run('My name is Alice');
const result = await session.run('What is my name?'); // remembers Alice
```

---

## Custom tools

```python
# Agno
from agno.tools import tool

@tool
def get_stock_price(ticker: str) -> str:
    """Get the current stock price for a ticker."""
    return fetch_price(ticker)
```

```
// personaforge
import { tool } from 'personaforge';
import { z } from 'zod';

const getStockPrice = tool({
  name: 'get_stock_price',
  description: 'Get the current stock price for a ticker symbol.',
  schema: z.object({ ticker: z.string().describe('Stock ticker, e.g. AAPL') }),
  execute: async ({ ticker }) => fetchPrice(ticker),
});
```

---

## What you gain by switching

| Agno gap | personaforge answer |
|---|---|
| Python-only runtime | TypeScript-native — same language as your app |
| No built-in budget caps | [Budget Enforcement](./production#budget-enforcement) |
| Limited OTLP tracing | [Observability & OTLP](./observability) — OpenTelemetry-native |
| No control-plane dashboard | [Control Plane](./control-plane) |
| Add-on guardrails | [Guardrails & Safety](./guardrails) — PII, prompt injection, moderation |

---

## Where to go next

- [Framework Comparisons](./comparisons) — full capability matrix vs all frameworks.
- [Agents](./agents) — `createAgent` in full.
- [Orchestration](./orchestration) — supervisors, handoffs, consensus.
- [Reasoning Tools](./reasoning-tools) — Agno-style `think` / `analyze`.
- [Evaluation & Benchmarking](./eval) — run `examples/agno-vs-personaforge.ts` head-to-head.


# Guide: migration-crewai

# Migrate From CrewAI

CrewAI's role-based crew model maps cleanly onto `personaforge`. The main change is moving from a framework-defined crew class to explicit agents, pipelines, or orchestrators.

---

## Quick comparison

| CrewAI concept | personaforge equivalent |
|---|---|
| `Agent(role, goal, backstory)` | `createAgent({ name, instructions })` |
| `Task(description, agent)` | An `agent.run()` call — or a graph `task` node |
| `Crew([agents], [tasks])` | `compose(agent1, agent2)` or `createOrchestrator` |
| `Task.tools` | `createAgent({ tools: [...] })` |
| `Process.sequential` | `compose(a, b, c)` |
| `Process.hierarchical` | `createSupervisor(manager, [workers])` |
| `Crew.kickoff()` | `pipeline.run(prompt)` |

---

## Agent migration

```
// CrewAI
from crewai import Agent
researcher = Agent(
    role='Senior Research Analyst',
    goal='Uncover cutting-edge developments in AI',
    backstory='You work at a leading tech think tank...',
    tools=[search_tool],
)

// personaforge
import { createAgent } from 'personaforge';
import { webSearchTool } from 'personaforge';

const researcher = createAgent({
  name: 'researcher',
  instructions: `You are a Senior Research Analyst at a leading tech think tank.
Your goal is to uncover cutting-edge developments in AI.
Be analytical and precise.`,
  model: 'gpt-4o',
  apiKey: process.env.OPENAI_API_KEY!,
  tools: [webSearchTool],
});
```

---

## Sequential crew → `compose`

```
// CrewAI
crew = Crew(
    agents=[researcher, writer, editor],
    tasks=[research_task, write_task, edit_task],
    process=Process.sequential,
)
result = crew.kickoff()

// personaforge
import { compose } from 'personaforge';

const pipeline = compose(researcher, writer, editor, {
  transform: (result) => result.text,
});

const result = await pipeline.run('AI trends in 2025');
```

---

## Hierarchical crew → `createSupervisor`

```
// CrewAI (hierarchical with manager_llm)
crew = Crew(agents=[writer, researcher], process=Process.hierarchical, manager_llm=gpt4)

// personaforge
import { createSupervisor } from 'personaforge';

const supervisor = createSupervisor({
  name: 'project-manager',
  instructions: 'Coordinate the research and writing agents to complete the task.',
  model: 'gpt-4o',
  apiKey: process.env.OPENAI_API_KEY!,
  workers: [researcher, writer],
});

const result = await supervisor.run('Produce a detailed report on quantum computing.');
```

---

## Task with expected output → tool + output format

```
// CrewAI
task = Task(
    description='Research the market for electric vehicles',
    expected_output='A detailed 3-paragraph report',
    agent=researcher,
)

// personaforge — bake the output format into instructions
const researcher = createAgent({
  name: 'ev-researcher',
  instructions: `Research the given market and produce a detailed 3-paragraph report.
Always structure your output with an introduction, key findings, and conclusion.`,
  model: 'gpt-4o',
  apiKey: process.env.OPENAI_API_KEY!,
  tools: [webSearchTool],
});

const result = await researcher.run('Research the market for electric vehicles.');
```

---

## Custom tools

```
// CrewAI
from crewai import tool

@tool("Get stock price")
def get_stock_price(ticker: str) -> str:
    """Get the current stock price for a ticker."""
    return fetch_price(ticker)

// personaforge
import { tool } from 'personaforge';
import { z } from 'zod';

const getStockPrice = tool({
  name: 'get_stock_price',
  description: 'Get the current stock price for a ticker symbol.',
  schema: z.object({ ticker: z.string().describe('Stock ticker, e.g. AAPL') }),
  execute: async ({ ticker }) => fetchPrice(ticker),
});
```

---

## Where to go next

- [Framework Comparisons](./comparisons) — full capability matrix vs all frameworks.
- [Agents](./agents) — `createAgent` in full.
- [Orchestration](./orchestration) — `createSupervisor`, handoffs, consensus.
- [Compose](./compose) — `compose()` and `pipe()` sequential pipelines.


# Guide: migration-langchain

# Migrate From LangChain

`personaforge` replaces LangChain's broad toolkit with purpose-built modules. The table below gives the mapping, followed by side-by-side code examples.

---

## Quick comparison

| LangChain concept | personaforge equivalent |
|---|---|
| `ChatOpenAI`, `ChatAnthropic` | Model string in `createAgent({ model })` |
| `LLMChain` | `compose(a, b)` or single `createAgent` call |
| `SequentialChain` | `compose(a, b, c)` |
| `AgentExecutor` | `createAgent({ tools })` |
| `Tool`, `StructuredTool` | `tool({ name, description, schema, execute })` |
| `ConversationBufferMemory` | `createAgent({ sessionId })` — managed by session store |
| `VectorStoreRetriever` | `ContextProvider` or RAG via `createKnowledgeBase` |
| `RunnableSequence` | `pipe(a).then(b).then(c)` |
| `LCEL pipe (|)` | `pipe(a).then(b)` |
| `Callbacks` | [Hooks](./hooks) and [Observability](./observability) |
| `ConversationalRetrievalChain` | `createAgent` with a `ContextProvider` tool |
| `Document`, `loader.load()` | `ContextProvider.update(documents)` |
| `LangSmith` tracing | [Observability](./observability) — OpenTelemetry-native |

---

## Simple LLM call

```
// LangChain
import { ChatOpenAI } from '@langchain/openai';
const llm = new ChatOpenAI({ model: 'gpt-4o' });
const result = await llm.invoke('What is the capital of France?');

// personaforge
import { createAgent } from 'personaforge';
const agent = createAgent({
  name: 'assistant',
  instructions: 'You are a helpful assistant.',
  model: 'gpt-4o',
  apiKey: process.env.OPENAI_API_KEY!,
});
const result = await agent.run('What is the capital of France?');
// result.text — the model output
```

---

## LLMChain → `createAgent`

```
// LangChain
import { LLMChain } from 'langchain/chains';
import { PromptTemplate } from '@langchain/core/prompts';
const chain = new LLMChain({
  llm,
  prompt: PromptTemplate.fromTemplate('Summarise this: {text}'),
});
await chain.call({ text: document });

// personaforge
const summarizer = createAgent({
  name: 'summarizer',
  instructions: 'Summarise the provided text concisely.',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
});
await summarizer.run(`Summarise this: ${document}`);
```

---

## LCEL pipeline → `pipe`

```
// LangChain (LCEL)
const chain = prompt | llm | outputParser;
const result = await chain.invoke({ input: 'Hello' });

// personaforge
import { pipe } from 'personaforge';

const result = await pipe(researchAgent)
  .then(summaryAgent,  { transform: (r) => `Summarise:\n${r.text}` })
  .then(formatAgent,   { transform: (r) => `Format for markdown:\n${r.text}` })
  .run('Latest AI developments');
```

---

## Tools

```
// LangChain
import { DynamicStructuredTool } from '@langchain/core/tools';
import { z } from 'zod';
const searchTool = new DynamicStructuredTool({
  name: 'search',
  description: 'Search the web',
  schema: z.object({ query: z.string() }),
  func: async ({ query }) => fetchSearchResults(query),
});

// personaforge
import { tool } from 'personaforge';
import { z } from 'zod';
const searchTool = tool({
  name: 'search',
  description: 'Search the web for up-to-date information.',
  schema: z.object({ query: z.string().describe('The search query') }),
  execute: async ({ query }) => fetchSearchResults(query),
});
```

---

## Agent with tools

```
// LangChain
import { createOpenAIFunctionsAgent, AgentExecutor } from 'langchain/agents';
const agent = await createOpenAIFunctionsAgent({ llm, tools, prompt });
const executor = new AgentExecutor({ agent, tools });
const result = await executor.invoke({ input: 'What is the weather in London?' });

// personaforge
const weatherAgent = createAgent({
  name: 'weather-agent',
  instructions: 'Answer questions about weather using the available tools.',
  model: 'gpt-4o',
  apiKey: process.env.OPENAI_API_KEY!,
  tools: [weatherTool, locationTool],
});
const result = await weatherAgent.run('What is the weather in London?');
```

---

## Conversation memory → session

```
// LangChain
import { ConversationChain } from 'langchain/chains';
import { ConversationBufferMemory } from 'langchain/memory';
const chain = new ConversationChain({ llm, memory: new ConversationBufferMemory() });
await chain.call({ input: 'My name is Alice' });
await chain.call({ input: 'What is my name?' }); // remembers Alice

// personaforge — sessions auto-persist history
const agent = createAgent({ name: 'chat', instructions: 'You are a helpful assistant.', model: 'gpt-4o', apiKey: ... });
const session = agent.createSession({ sessionId: 'user-123' });
await session.run('My name is Alice');
const result = await session.run('What is my name?'); // remembers Alice
```

---

## RAG / retrieval

```
// LangChain
const vectorStore = await MemoryVectorStore.fromTexts(texts, metadata, new OpenAIEmbeddings());
const chain = new ConversationalRetrievalChain({ retriever: vectorStore.asRetriever(), llm });
const result = await chain.call({ question: 'What is the return policy?' });

// personaforge
import { createKnowledgeBase } from 'personaforge';

const kb = await createKnowledgeBase({ type: 'memory', embedder: 'openai', apiKey: process.env.OPENAI_API_KEY! });
await kb.add(documents);

const agent = createAgent({
  name: 'support-agent',
  instructions: 'Answer questions using the knowledge base.',
  model: 'gpt-4o',
  apiKey: process.env.OPENAI_API_KEY!,
  contextProviders: [kb.asContextProvider()],
});
const result = await agent.run('What is the return policy?');
```

---

## Callbacks → hooks

```
// LangChain
const handler = new BaseCallbackHandler({
  handleLLMStart: () => console.log('LLM started'),
  handleLLMEnd: (output) => console.log('LLM done', output),
});

// personaforge
const agent = createAgent({
  name: 'traced-agent',
  instructions: '...',
  model: 'gpt-4o',
  apiKey: process.env.OPENAI_API_KEY!,
  hooks: {
    beforeRun: (ctx) => console.log('Run started', ctx.runId),
    afterRun:  (ctx, result) => console.log('Run done', result.text),
  },
});
```

---

## Where to go next

- [Framework Comparisons](./comparisons) — full capability matrix vs all frameworks.
- [Agents](./agents) — full `createAgent` API.
- [Tools](./tools) — `tool()` authoring.
- [RAG](./rag) — knowledge base and context providers.
- [Hooks](./hooks) — lifecycle events for observability.


# Guide: migration-langgraph

# Migrate From LangGraph

LangGraph's state-machine model maps directly onto `personaforge`'s graph engine and checkpoint module — in **TypeScript**, with budget enforcement, guardrails, eval, and OTLP tracing included.

---

## Quick comparison

| LangGraph concept | personaforge equivalent |
|---|---|
| `StateGraph` | `createGraph()` + `DAGEngine` |
| `add_node(name, fn)` | `.addNode(name, { kind: 'task', execute })` |
| `add_edge(a, b)` | `.addEdge(a, b)` or `.chain(a, b, c)` |
| `add_conditional_edges` | `router` node or [Workflow Branching](./workflow-branching) |
| `MessagesState` / typed state | `ctx.state.variables` + `ctx.state.results` |
| `MemorySaver` / checkpointer | [Durable Interrupt & Resume](./checkpoint) — `CheckpointStore` |
| `interrupt()` / `Command(resume=...)` | `ctx.interrupt()` / `exec.resume(threadId, value)` |
| `stream_mode=["values","updates"]` | [Event Streaming](./event-streaming) — `values \| updates \| messages \| debug \| custom` |
| `create_react_agent` | `createAgent({ tools, maxSteps })` |
| `Send` / fan-out | `parallel` + `join` nodes |
| `SqliteSaver` | Implement `CheckpointStore` (SQLite pattern in checkpoint guide) |

---

## StateGraph → `createGraph`

```python
# LangGraph (Python)
from langgraph.graph import StateGraph, END

graph = StateGraph(State)
graph.add_node("fetch", fetch_node)
graph.add_node("analyse", analyse_node)
graph.add_edge("fetch", "analyse")
graph.add_edge("analyse", END)
app = graph.compile()
result = app.invoke({"input": "https://example.com"})
```

```
// personaforge
import { createGraph } from 'personaforge';
import { DAGEngine } from 'personaforge/graph';

const graph = createGraph('content-pipeline')
  .addNode('fetch', {
    kind: 'task',
    execute: (ctx) => fetchContent(ctx.state.variables.input as string),
  })
  .addNode('analyse', {
    kind: 'task',
    execute: (ctx) => analyseContent(ctx.state.results['fetch']),
  })
  .chain('fetch', 'analyse')
  .build();

const engine = new DAGEngine(graph);
const execution = await engine.execute({ variables: { input: 'https://example.com' } });
// execution.state.results
```

---

## Conditional edges → router node

```python
# LangGraph
graph.add_conditional_edges(
    "classify",
    route_fn,
    {"billing": "billing_agent", "technical": "tech_agent", "general": "general_agent"},
)
```

```
// personaforge
const graph = createGraph('support-routing')
  .addNode('classify', {
    kind: 'task',
    execute: async (ctx) => {
      const category = await classifier.run(ctx.state.variables.input as string);
      return { category: category.text.trim() };
    },
  })
  .addNode('billing-agent',   { kind: 'task', execute: (ctx) => billingAgent.run(ctx.state.input as string) })
  .addNode('technical-agent', { kind: 'task', execute: (ctx) => techAgent.run(ctx.state.input as string) })
  .addNode('general-agent',   { kind: 'task', execute: (ctx) => generalAgent.run(ctx.state.input as string) })
  .addNode('router', {
    kind: 'router',
    route: (state) => {
      const category = (state.results['classify'] as { category: string }).category;
      if (category.includes('billing'))   return 'billing-agent';
      if (category.includes('technical')) return 'technical-agent';
      return 'general-agent';
    },
  })
  .addEdge('classify', 'router')
  .build();
```

See [Workflow Branching](./workflow-branching) for pipeline-level `when` predicates too.

---

## ReAct agent

```python
# LangGraph
from langgraph.prebuilt import create_react_agent
agent = create_react_agent(model, tools)
result = agent.invoke({"messages": [("user", "What is the weather in London?")]})
```

```
// personaforge
const agent = createAgent({
  name: 'weather-agent',
  instructions: 'Answer questions using the available tools.',
  model: 'gpt-4o',
  apiKey: process.env.OPENAI_API_KEY!,
  tools: [getWeather],
  maxSteps: 10,
});

const result = await agent.run('What is the weather in London?');
```

---

## Checkpointer → `CheckpointStore`

```python
# LangGraph
from langgraph.checkpoint.memory import MemorySaver
checkpointer = MemorySaver()
app = graph.compile(checkpointer=checkpointer)
config = {"configurable": {"thread_id": "user-123"}}
app.invoke(input, config)
```

```
// personaforge
import { DurableExecutor, InMemoryCheckpointStore } from 'personaforge/checkpoint';

const exec = new DurableExecutor({
  nodes: [['ask', askApproval], ['execute', executeNode]],
  store: new InMemoryCheckpointStore(),
});

const r1 = await exec.run({ amount: 500 }, { threadId: 'user-123' });
// r1.interrupted === true when a node calls ctx.interrupt()

const r2 = await exec.resume(r1.threadId, { ok: true });
```

For production, implement `CheckpointStore` with SQLite or Postgres. See [Durable Interrupt & Resume](./checkpoint).

---

## `interrupt()` / resume

```python
# LangGraph
from langgraph.types import interrupt, Command

def approval_node(state):
    value = interrupt({"question": "Approve this transfer?"})
    return {"approved": value}

# Resume: app.invoke(Command(resume=True), config)
```

```
// personaforge
import type { NodeFn } from 'personaforge/checkpoint';

const askApproval: NodeFn = (input, ctx) => {
  const value = ctx.interrupt({ question: 'Approve this transfer?' });
  return { input, approved: value };
};

const r1 = await exec.run({ amount: 500 });
// r1.interrupted === true, r1.interruptPayload === { question: '...' }

const r2 = await exec.resume(r1.threadId, { ok: true });
// Execution continues with approved value
```

---

## Stream modes

```python
# LangGraph
for event in app.stream(input, stream_mode=["updates", "messages"]):
    print(event)
```

```
// personaforge
import { createStreamableRun } from 'personaforge/streaming';

const { events, result } = createStreamableRun(async (ctx) => {
  ctx.update({ step: 'fetching' });
  const data = await fetchContent(url);
  ctx.token('Processing...');
  return { data };
}, { streamMode: ['updates', 'messages'] });

for await (const event of events) {
  if (event.type === 'token')  process.stdout.write(event.data);
  if (event.type === 'update') console.log('Update:', event.data);
}
```

| LangGraph `stream_mode` | personaforge mode |
|---|---|
| `values` | `values` |
| `updates` | `updates` |
| `messages` | `messages` |
| `debug` | `debug` |
| `custom` | `custom` (via `ctx.emit()`) |

See [Event Streaming](./event-streaming) for the full protocol.

---

## Parallel fan-out

```python
# LangGraph — Send for map-reduce
from langgraph.types import Send
```

```
// personaforge
const graph = createGraph('parallel-research')
  .addNode('split',    { kind: 'task', execute: splitTopics })
  .addNode('research', { kind: 'task', execute: researchTopic })
  .addNode('fan-out',  { kind: 'parallel', targets: ['research-a', 'research-b', 'research-c'] })
  .addNode('merge',    { kind: 'join', execute: mergeResults })
  .addEdge('split', 'fan-out')
  .addEdge('fan-out', 'merge')
  .build();
```

---

## What you gain by switching

| LangGraph gap | personaforge answer |
|---|---|
| Python-first (JS port is separate) | TypeScript-native graph engine |
| No budget enforcement | [Budget Enforcement](./production#budget-enforcement) |
| Add-on observability | [Observability & OTLP](./observability) — built-in |
| No built-in eval | [Evaluation & Benchmarking](./eval) + τ-bench harness |
| No guardrails module | [Guardrails & Safety](./guardrails) |
| No control-plane dashboard | [Control Plane](./control-plane) |

---

## Where to go next

- [Framework Comparisons](./comparisons) — full capability matrix vs all frameworks.
- [Graph Engine](./graph) — node kinds, retries, event sourcing.
- [Durable Interrupt & Resume](./checkpoint) — `interrupt()`, `resume()`, fork-from-checkpoint.
- [Event Streaming](./event-streaming) — LangGraph-compatible stream modes.
- [Workflow Branching](./workflow-branching) — conditional routing patterns.


# Guide: migration-mastra

# Migrate From Mastra

Mastra is a TypeScript agent framework focused on typed workflows, MCP integration, and developer experience. `personaforge` covers the same surface area and adds **durable DAG execution, circuit breakers, budget enforcement, eval, and a control-plane dashboard** in a single package.

---

## Quick comparison

| Mastra concept | personaforge equivalent |
|---|---|
| `new Agent({ name, instructions, model, tools })` | `createAgent({ name, instructions, model, tools })` |
| `createWorkflow().then().commit()` | `createGraph()` + `DAGEngine` or `pipe().then()` |
| `createStep()` | Graph `task` node or `pipe().then()` stage |
| `tool()` | `tool({ name, description, schema, execute })` |
| `MCPClient` | [MCP Client & Server](./mcp) |
| `Memory` / thread storage | `agent.createSession({ sessionId })` |
| `createTool()` with Zod | `tool()` with Zod schema |
| `generate()` / `stream()` | `agent.run()` / `agent.stream()` |
| `evals` | [Evaluation & Benchmarking](./eval) |
| `deploy()` / server | [Production](./production) + automatic REST API |
| `Telemetry` | [Observability & OTLP](./observability) — OpenTelemetry-native |

---

## Agent migration

```
// Mastra
import { Agent } from '@mastra/core/agent';

const agent = new Agent({
  name: 'weather-agent',
  instructions: 'Answer weather questions using the available tools.',
  model: openai('gpt-4o'),
  tools: { getWeather },
});

const result = await agent.generate('What is the weather in London?');
```

```
// personaforge
import { createAgent } from 'personaforge';

const agent = createAgent({
  name: 'weather-agent',
  instructions: 'Answer weather questions using the available tools.',
  model: 'gpt-4o',
  apiKey: process.env.OPENAI_API_KEY!,
  tools: [getWeather],
});

const result = await agent.run('What is the weather in London?');
// result.text
```

---

## Typed step workflow → graph engine

```
// Mastra
import { createWorkflow, createStep } from '@mastra/core/workflows';

const fetchStep = createStep({
  id: 'fetch',
  execute: async ({ inputData }) => fetchContent(inputData.url),
});

const analyseStep = createStep({
  id: 'analyse',
  execute: async ({ inputData }) => analyseContent(inputData),
});

const workflow = createWorkflow({ id: 'content-pipeline' })
  .then(fetchStep)
  .then(analyseStep)
  .commit();

const result = await workflow.execute({ url: 'https://example.com' });
```

```
// personaforge
import { createGraph } from 'personaforge';
import { DAGEngine } from 'personaforge/graph';

const graph = createGraph('content-pipeline', { version: '1.0' })
  .addNode('fetch', {
    kind: 'task',
    execute: (ctx) => fetchContent(ctx.state.variables.url as string),
  })
  .addNode('analyse', {
    kind: 'task',
    execute: (ctx) => analyseContent(ctx.state.results['fetch']),
  })
  .chain('fetch', 'analyse')
  .build();

const engine = new DAGEngine(graph);
const execution = await engine.execute({ variables: { url: 'https://example.com' } });
// execution.state.results
```

For simpler linear pipelines without full graph semantics, use `pipe()`:

```
import { pipe } from 'personaforge';

const pipeline = pipe(fetchAgent)
  .then(analyseAgent, { transform: (r) => r.text })
  .then(publishAgent);

const result = await pipeline.run('https://example.com/article');
```

---

## Tools

```
// Mastra
import { createTool } from '@mastra/core/tools';
import { z } from 'zod';

const getWeather = createTool({
  id: 'get_weather',
  description: 'Get weather for a city.',
  inputSchema: z.object({ city: z.string() }),
  execute: async ({ context }) => fetchWeather(context.city),
});
```

```
// personaforge
import { tool } from 'personaforge';
import { z } from 'zod';

const getWeather = tool({
  name: 'get_weather',
  description: 'Get weather for a city.',
  schema: z.object({ city: z.string().describe('City name') }),
  execute: async ({ city }) => fetchWeather(city),
});
```

---

## MCP integration

```
// Mastra
import { MCPClient } from '@mastra/mcp';

const mcp = new MCPClient({ servers: { filesystem: { url: '...' } } });
const tools = await mcp.getTools();
```

```
// personaforge
import { createMCPClient } from 'personaforge/mcp';

const mcp = await createMCPClient({
  servers: { filesystem: { command: 'npx', args: ['-y', '@modelcontextprotocol/server-filesystem', '/tmp'] } },
});
const tools = await mcp.listTools();

const agent = createAgent({
  name: 'filesystem-agent',
  instructions: 'Use filesystem tools to answer questions.',
  model: 'gpt-4o',
  apiKey: process.env.OPENAI_API_KEY!,
  tools,
});
```

See [MCP Client & Server](./mcp) for full setup.

---

## Streaming

```
// Mastra
const stream = await agent.stream('Explain async/await');
for await (const chunk of stream.textStream) process.stdout.write(chunk);

// personaforge
for await (const chunk of agent.stream('Explain async/await')) {
  process.stdout.write(chunk);
}

// Event-level streaming (tool calls, steps)
for await (const event of agent.streamEvents('Plan my vacation')) {
  if (event.type === 'text-delta') process.stdout.write(event.delta ?? '');
  if (event.type === 'tool-call')  console.log('Calling:', event.tool?.name);
}
```

---

## Memory / threads

```
// Mastra — thread-based memory
const result = await agent.generate('Hello', { threadId: 'user-123' });
const followUp = await agent.generate('What did I just say?', { threadId: 'user-123' });

// personaforge — explicit sessions
const session = agent.createSession({ sessionId: 'user-123' });
await session.run('Hello');
const result = await session.run('What did I just say?');
```

---

## What you gain by switching

| Mastra gap | personaforge answer |
|---|---|
| No durable DAG engine | [Graph Engine](./graph) — conditional edges, fan-out, event sourcing |
| No circuit breakers | [Resilience & Circuit Breakers](./production) |
| No USD budget caps | [Budget Enforcement](./production#budget-enforcement) |
| Limited multi-tenancy | [Multi-Tenancy](./multi-tenancy) |
| Partial enterprise audit | SOC2/HIPAA audit logging + [Control Plane](./control-plane) |
| 100+ built-in tools | [Built-in Tools](./tools) — web search, databases, code execution, and more |

---

## Where to go next

- [Framework Comparisons](./comparisons) — full capability matrix vs all frameworks.
- [Agents](./agents) — `createAgent` in full.
- [Execution Workflows](./workflows) — typed DAG workflows.
- [Graph Engine](./graph) — conditional edges, parallel fan-out, durable execution.
- [MCP Client & Server](./mcp) — MCP tools and servers.


# Guide: migration-vercel

# Migrate From Vercel AI SDK

Vercel AI SDK focuses on streaming primitives and UI integration. `personaforge` provides the same streaming surface with persistence, multi-step reasoning, tools, sessions, and production middleware built in.

---

## Quick comparison

| Vercel AI SDK | personaforge equivalent |
|---|---|
| `generateText({ model, prompt })` | `agent.run(prompt)` → `result.text` |
| `streamText({ model, prompt })` | `agent.stream(prompt)` → `AsyncIterable<string>` |
| `generateObject({ model, schema })` | `agent.run(prompt)` + Zod schema in instructions |
| `streamObject(...)` | `agent.streamEvents(prompt)` |
| `tool({ description, parameters, execute })` | `tool({ name, description, schema, execute })` |
| `useChat()` hook | `agent.createSession()` + direct SDK calls |
| `CoreMessage[]` history | `agent.createSession({ sessionId })` |
| `maxSteps` | `createAgent({ maxSteps })` |
| `onChunk` callback | `agent.stream()` — iterate chunks |
| `experimental_telemetry` | [Observability](./observability) — OpenTelemetry-native |

---

## `generateText` → `agent.run`

```
// Vercel AI SDK
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';

const { text } = await generateText({
  model: openai('gpt-4o'),
  prompt: 'Write a haiku about recursion.',
});

// personaforge
import { createAgent } from 'personaforge';

const agent = createAgent({
  name: 'writer',
  instructions: 'You are a creative writing assistant.',
  model: 'gpt-4o',
  apiKey: process.env.OPENAI_API_KEY!,
});

const result = await agent.run('Write a haiku about recursion.');
// result.text
```

---

## `streamText` → `agent.stream`

```
// Vercel AI SDK
import { streamText } from 'ai';
const { textStream } = await streamText({ model: openai('gpt-4o'), prompt });
for await (const chunk of textStream) process.stdout.write(chunk);

// personaforge
for await (const chunk of agent.stream('Explain async/await in simple terms')) {
  process.stdout.write(chunk);
}
```

---

## Tools

Tools are intentionally similar — add `name` and change `parameters` to `schema`:

```
// Vercel AI SDK
import { tool } from 'ai';
import { z } from 'zod';

const getWeather = tool({
  description: 'Get weather for a city.',
  parameters: z.object({ city: z.string() }),
  execute: async ({ city }) => fetchWeather(city),
});

// personaforge (only difference: add `name`, rename `parameters` → `schema`)
import { tool } from 'personaforge';
import { z } from 'zod';

const getWeather = tool({
  name: 'get_weather',
  description: 'Get weather for a city.',
  schema: z.object({ city: z.string().describe('City name') }),
  execute: async ({ city }) => fetchWeather(city),
});
```

---

## Multi-step tool calls

```
// Vercel AI SDK
const { text } = await generateText({
  model: openai('gpt-4o'),
  tools: { getWeather },
  maxSteps: 5,
  prompt: 'What is the weather in Paris and London?',
});

// personaforge
const agent = createAgent({
  name: 'weather-agent',
  instructions: 'Answer weather questions using the available tools.',
  model: 'gpt-4o',
  apiKey: process.env.OPENAI_API_KEY!,
  tools: [getWeather],
  maxSteps: 5,
});
const result = await agent.run('What is the weather in Paris and London?');
```

---

## `useChat` hook → session

```
// Vercel AI SDK (Next.js)
import { useChat } from 'ai/react';
const { messages, input, handleSubmit } = useChat({ api: '/api/chat' });

// personaforge (route handler)
// app/api/chat/route.ts
export async function POST(req: Request) {
  const { message, sessionId } = await req.json();
  const session = agent.createSession({ sessionId });

  const encoder = new TextEncoder();
  const stream = new ReadableStream({
    async start(controller) {
      for await (const chunk of session.stream(message)) {
        controller.enqueue(encoder.encode(`data: ${JSON.stringify({ text: chunk })}\n\n`));
      }
      controller.close();
    },
  });
  return new Response(stream, { headers: { 'Content-Type': 'text/event-stream' } });
}
```

---

## Streaming events

```
// Vercel AI SDK
const { fullStream } = streamText({ model: openai('gpt-4o'), tools: { ... }, prompt });
for await (const part of fullStream) {
  if (part.type === 'text-delta') process.stdout.write(part.textDelta);
}

// personaforge
for await (const event of agent.streamEvents('Plan my vacation to Japan')) {
  if (event.type === 'text-delta') process.stdout.write(event.delta ?? '');
  if (event.type === 'tool-call')  console.log('Calling tool:', event.tool?.name);
}
// event types: text-delta | tool-call | tool-result | step-finish | run-finish | error
```

---

## Structured output

```
// Vercel AI SDK
const { object } = await generateObject({
  model: openai('gpt-4o'),
  schema: z.object({ title: z.string(), tags: z.array(z.string()) }),
  prompt: 'Classify this article',
});

// personaforge — instruct the agent to return JSON, then parse
const agent = createAgent({
  name: 'classifier',
  instructions: 'Classify the article. Return ONLY a JSON object: { "title": string, "tags": string[] }',
  model: 'gpt-4o',
  apiKey: process.env.OPENAI_API_KEY!,
});
const result = await agent.run(articleText);
const parsed = JSON.parse(result.text);
```

---

## Where to go next

- [Framework Comparisons](./comparisons) — full capability matrix vs all frameworks.
- [Agents](./agents) — full `createAgent` API.
- [Tools](./tools) — `tool()` authoring.
- [Websocket & streaming](./websocket) — SSE and resumable stream endpoints.
- [Session](./session) — persistent conversation sessions.


# Guide: model-fallbacks

# Model Fallbacks & Retry

`withFallbacks` and `withRetry` are one-liner resilience wrappers around any `LLMProvider`. They compose, so you can retry the primary and then fall back to a different vendor.

```
import { withFallbacks, withRetry } from 'personaforge/models';
```

---

## `withFallbacks`

Try the primary provider first; on any error, walk the fallback list until one succeeds. Throws only when all providers fail.

```
const resilient = withFallbacks(
  openai('gpt-4o'),
  [anthropic('claude-3-5-sonnet'), ollama('llama3.1')],
);

await resilient.generateText(messages);
```

Streaming is proxied automatically if any provider in the list supports `streamText`.

---

## `withRetry`

Retry with exponential backoff. Backoff schedule: `baseDelayMs * 2^attempt`.

```
const retried = withRetry(openai('gpt-4o'), {
  maxRetries: 3,
  baseDelayMs: 200,
  retryOn: (err) => (err as Error).message.includes('429'),
});
```

If `retryOn` is omitted, every error triggers a retry.

---

## Composing

`withRetry` and `withFallbacks` compose in either order. A typical stack is retry the primary, then fall back on final failure:

```
const provider = withFallbacks(
  withRetry(openai('gpt-4o'), { maxRetries: 3 }),
  [anthropic('claude-3-5-sonnet'), ollama('llama3.1')],
);
```

Or fall back first (fast), then retry the whole chain:

```
const provider = withRetry(
  withFallbacks(openai('gpt-4o'), [anthropic('claude-3-5-sonnet')]),
  { maxRetries: 2 },
);
```

---

## Behaviour details

- `withFallbacks` never retries a single provider; use `withRetry` for that.
- `withRetry` retries on any thrown error unless `retryOn` returns `false`.
- Both preserve the underlying provider's `streamText` when present.

---

## Related pages

- [Providers](/guide/providers) — provider adapters.
- [LLM Router](/guide/llm-router) — cost/capability-aware routing (higher-level than fallbacks).
- [Resilience & Circuit Breakers](/guide/production) — process-wide guards.


# Guide: monorepo-migration

# Monorepo Migration Plan

This repo is midway through a package-first migration. The root `personaforge` package still ships the legacy `src/` implementation for compatibility, while `packages/*` contains the clean workspace packages that should become the source of truth.

The goal is not to move folders one-for-one. The goal is to create stable package boundaries, keep the quickstart working, and make every optional capability opt-in.

## Current State

- Root `src/` has 36 top-level domains and 351 TypeScript files.
- `packages/*` currently has 15 workspace packages and 440 TypeScript files.
- Full tests pass, but the root build needs a larger Node heap during declaration generation.
- Turbo was present in config but could not run until the root package declared `packageManager`.
- The root entry point intentionally re-exports `src/` modules for backward compatibility.

## Target Shape

The root package should become a compatibility layer. New implementation code should live in workspace packages. Root subpaths should re-export from packages only after each package has full tests, public exports, and compatibility shims.

Package layering:

1. Foundation: `contracts`, `shared`
2. Core runtime: `core`, `models`, `tools`, `session`
3. Safety and telemetry: `guard`, `observe`, `guardrails`, `production`
4. Agent capabilities: `agentic`, `memory`, `knowledge`, `planner`, `reasoning`, `compression`, `context`
5. Orchestration: `workflow`, `graph`, `execution`, `orchestration`, `scheduler`, `background`
6. Delivery and DX: `serve`, `runtime`, `cli`, `dx`, `sdk`, `testing`, `test-utils`
7. Media and extensions: `artifacts`, `voice`, `video`, `plugins`, `extensions`, `adapters`, provider-specific adapter packages

## Source Domain Mapping

| Legacy `src/` domain | Target package | Notes |
| --- | --- | --- |
| `src/contracts` | `@personaforge/contracts` | Move interfaces and error contracts first. No runtime deps. |
| `src/shared` | `@personaforge/shared` | Shared version, telemetry flags, debug helpers. No domain imports. |
| `src/core` | `@personaforge/core` | Agent contracts, registry, low-level runner primitives. |
| `src/providers` | `@personaforge/models` | Keep SDKs as optional peers and dynamically imported. |
| `src/tools` | `@personaforge/tools` plus domain tool packages | Split heavy tool families later into dedicated packages. |
| `src/session` | `@personaforge/session` | In-memory, SQLite, Redis shims. Redis implementation can also live in `adapter-redis`. |
| `src/guard` facade | `@personaforge/guard` | Root facade should re-export package implementation. |
| `src/observability` and `src/observe.ts` | `@personaforge/observe` and future `@personaforge/observability` | Keep basic tracing/logger in `observe`; advanced eval stores can be separate. |
| `src/create-agent` and `src/create-agent.ts` | `@personaforge/core` or `@personaforge/agentic` facade | Keep public `createAgent` API stable. Move implementation after `agentic`, `models`, `tools`, and `session` are ready. |
| `src/agentic` | `@personaforge/agentic` | ReAct loop package. Depends on core contracts, models, tools, guard, observe. |
| `src/guardrails` | `@personaforge/guardrails` | Content safety and prompt injection rules. Depends on contracts/core only. |
| `src/production` | `@personaforge/production` | Budget, approval, audit, checkpoint, tenant, health. Depends on contracts, guard, observe, session. |
| `src/memory` | `@personaforge/memory` | Long-term memory and vector stores. Optional vector SDKs stay peer deps. |
| `src/knowledge` | `@personaforge/knowledge` | RAG orchestration. Should consume `@personaforge/memory`, not duplicate vector logic. |
| `src/planner` | `@personaforge/planner` | Planning algorithms and task decomposition. Keep zero provider imports. |
| `src/reasoning` | `@personaforge/reasoning` | Reasoning manager and event stream types. Depends on models/core contracts. |
| `src/compression` | `@personaforge/compression` | Message and context compression. Should be provider-agnostic. |
| `src/context` | `@personaforge/context` | Context providers and backends. Keep storage adapters injected. |
| `src/execution` | `@personaforge/workflow` or `@personaforge/execution` | If it is generic workflow primitives, keep separate. If agent workflow only, merge into workflow. |
| `src/workflow.ts` | `@personaforge/workflow` | Root facade only after package exports match current API. |
| `src/graph` | `@personaforge/graph` | DAG engine, event store, scheduler helpers, durable executor. |
| `src/orchestration` | `@personaforge/orchestration` | A2A, consensus, handoff, routers. Depends on graph/workflow/core. |
| `src/scheduler` | `@personaforge/scheduler` | Cron parser and schedule manager. No provider imports. |
| `src/background` | `@personaforge/background` | Queue abstraction plus optional BullMQ/Kafka/SQS/RabbitMQ peers. |
| `src/runtime` and `src/serve.ts` | `@personaforge/runtime` and `@personaforge/serve` | Keep HTTP primitives in serve; agent lifecycle service in runtime. |
| `src/cli` | `@personaforge/cli` | CLI should depend on public package APIs only. |
| `src/dx` | `@personaforge/dx` | Friendly builders and dev logger. Depends on public APIs, not internals. |
| `src/sdk` | `@personaforge/sdk` | Typed builder layer. Depends on package APIs only. |
| `src/testing` and `src/test.ts` | `@personaforge/test-utils` plus `@personaforge/testing` | Keep unit helpers separate from integration harnesses. |
| `src/adapters` | `@personaforge/adapters` | Registry and in-memory adapters. Concrete external adapters get own packages. |
| `src/plugins` | `@personaforge/plugins` | Plugin contracts and built-ins. Avoid importing providers. |
| `src/storage` | `@personaforge/storage` | Generic key-value and file storage. Optional persistence adapters injected. |
| `src/artifacts` | `@personaforge/artifacts` | Artifact types and stores. |
| `src/voice` | `@personaforge/voice` | Voice provider abstraction and optional providers. |
| `src/video` | `@personaforge/video` | Video workflows and media adapters. |
| `src/extensions` | `@personaforge/extensions` | Integration adapters. Should depend on public packages only. |
| `src/config` | `@personaforge/config` | Config loading and secret manager adapters. Optional cloud SDKs as peers. |

## Migration Order

1. Finish existing foundation packages: `contracts`, `shared`, `core`, `guard`, `observe`, `session`.
2. Move provider and tool primitives: `models`, `tools`, then split heavy domain tools.
3. Extract `agentic` and switch `createAgent` to consume the package implementation.
4. Extract production safety: `guardrails`, `production`, `runtime`, `serve` integration.
5. Extract state and intelligence: `memory`, `knowledge`, `planner`, `reasoning`, `compression`, `context`.
6. Extract orchestration: `graph`, `execution`, `workflow`, `orchestration`, `scheduler`, `background`.
7. Move outer layers last: `dx`, `sdk`, `cli`, `testing`, `plugins`, `extensions`, media packages.
8. Convert root `src/` files to compatibility barrels and delete moved implementation files only after tests prove parity.

## Migration Rules

- Every package must build, typecheck, lint, and test independently before root imports it.
- Package implementation must not import from root `src/`.
- Root `src/` may import from packages only after the package is fully extracted and the root file is reduced to a facade.
- `contracts` and `shared` must remain dependency-light and must never import provider, adapter, or runtime code.
- Heavy SDKs stay as optional peer dependencies and are loaded only inside the adapter that uses them.
- Keep existing `personaforge/*` subpaths during v1. New package imports are additive until the next major version.
- Add parity tests before changing any public export path.

## Immediate Fixes

- Add `packageManager` so Turbo can resolve workspace behavior consistently.
- Keep root build as `build:root` with a larger heap for declaration generation.
- Add `build:packages` and `build:all` so CI can validate packages first and root compatibility second.
- Fix package manifests that export files that do not exist, such as `packages/tools` exporting `./search` before `src/search.ts` exists and `packages/knowledge` exporting `./loaders` before `src/loaders.ts` exists.
- Sync package versions with the root package before publishing.
- Decide whether package manifests should point directly at `dist` or keep source-first development fields with a publish tool that rewrites them. Do not publish mixed source/dist metadata.

## Definition Of Done

A module is considered migrated only when all of these are true:

1. The package has a clear public API and `exports` map.
2. It has package-local tests for the moved behavior.
3. It has no imports from root `src/`.
4. Root `src/` only re-exports from the package or contains compatibility glue.
5. Existing root imports and documented examples still pass.
6. `bun run build:all`, `bun run typecheck`, and `bun run test` pass.

---

## Migration Risk Analysis (2025-07)

### Risk overview by domain

| Domain | LOC | Cross-domain src imports | Test files | Risk level | Notes |
|---|---|---|---|---|---|
| `src/providers` | 5 492 | 5 (memory, observability, tools, shared) | 5 | **HIGH** | Most-imported file in codebase (`providers/types.ts` — 26 consumers). Duplicate types in `@personaforge/core`. |
| `src/orchestration` | 4 800 | 2 | **0** | **HIGH** | Zero test coverage. A2A + multi-agent consensus logic. Cannot migrate safely without first writing tests. |
| `src/graph` | 4 707 | 3 (memory, providers, tools) | 1 | **HIGH** | CLI imports `personaforge/graph` which resolves to `src/graph` via root src. Blocking clean package boundary for `@personaforge/cli`. |
| `src/execution` | 3 573 | 3 (contracts, core, planner) | **0** | **HIGH** | Zero test coverage. Contains two parallel engine implementations (`engine.ts` and `engine-v2.ts`). |
| `src/production` | 3 500 | 8 | 5 | MEDIUM | Partial duplicate with `@personaforge/guard` (`circuit-breaker.ts`, `budget.ts`, `rate-limiter.ts`). Must deduplicate before extracting. |
| `src/create-agent` | 832 | **14** | 1 | **HIGH** | Hub of the framework. 14 unique cross-domain imports. Must be last to migrate (after all deps are extracted). |
| `src/agentic` | 1 027 | 6 | **0** | HIGH | ReAct runner. Zero test coverage despite being the core execution loop. |
| `src/observability` | ~450 | 4 | 0 | MEDIUM | Partially duplicated by `@personaforge/observe`. Needs type reconciliation before extraction. |
| `src/session` | ~350 | 2 | 0 | LOW | Clean interface. `@personaforge/session` already exists. Root `src/session` can become a re-export immediately. |
| `src/memory` | ~280 | 0 | 0 | LOW | No cross-domain imports. Straightforward extraction. |
| `src/graph/event-store.ts` | ~200 | 0 | 1 | MEDIUM | SQLite event store used by CLI commands. Breaks build if not packaged before CLI is published standalone. |

### Structural risks

**1. `src/providers/types.ts` — the type hub (CRITICAL)**

26 src files import from `src/providers/types.ts`. The interfaces `LLMProvider`, `Message`, `GenerateOptions`,
`GenerateResult`, `StreamChunk` are defined twice — once here and once in `@personaforge/core`.
Both definitions are slightly different (field names, optionality).

Risk: any migration that moves either definition first will cause 20+ TypeScript errors across remaining domains.

Fix required before anything else: audit the two type sets, pick one canonical location (`@personaforge/core`
as the single source of truth), and alias the other.

**2. Circular-free but tightly coupled hub: `src/create-agent`**

`src/create-agent/factory.ts` imports from 14 distinct src domains. It is not circular, but it is the last
node to migrate because every one of its 14 dependencies must already be in packages first.
Migrating `create-agent` prematurely will cause an immediate DTS build failure because package implementations
must not import from root `src/`.

**3. Duplicate production-safety implementations**

`@personaforge/guard` contains `CircuitBreaker`, `withRetry`, `BudgetGuard`, `RateLimiter`.
`src/production/` has independent implementations of `circuit-breaker.ts`, `budget.ts`, `rate-limiter.ts`.
Until these are merged, any production fix must be applied in two places.

**4. CLI root-src leakage**

`packages/cli/src/commands/{export,replay,inspect,diff}-cmd.ts` import from `personaforge/graph`
and `personaforge/runtime`. These subpaths resolve to `src/graph/` and `src/runtime/` in the root package,
meaning the CLI package silently depends on root `src/` at runtime.

Breaking point: as soon as `@personaforge/graph` is added as a proper workspace package with its own
`package.json`, the root subpath will resolve to the package — which is the desired end state — but
will fail until the package exposes exactly the same named exports.

**5. Zero-test-coverage domains**

`src/agentic`, `src/execution`, and `src/orchestration` have 0 test files. These are among the most
complex parts of the framework. Migration is unsafe without tests because there is no regression signal.

---

## Concrete Migration Roadmap

### Wave 0 — Type layer consolidation (no new packages, immediate)

Prerequisite for everything. Estimated files changed: 30. No new packages needed.

| Step | Action | Files |
|---|---|---|
| 0a | Audit `@personaforge/core` exports vs `src/providers/types.ts`. Identify field-level diffs. | `packages/core/src/index.ts`, `src/providers/types.ts` |
| 0b | Move canonical LLM types (`LLMProvider`, `Message`, `GenerateOptions`, `GenerateResult`, `StreamChunk`, etc.) to `@personaforge/core` if not already there. | `packages/core/src/` |
| 0c | Replace `src/providers/types.ts` content with re-exports from `@personaforge/core`. This converts 26 consumers without touching them. | `src/providers/types.ts` |
| 0d | Run `bun run build:all && bun run typecheck && bun run test` — all must stay green. | — |

**Gate**: `providers/types.ts` is a thin re-export barrel. No domain logic changed.

---

### Wave 1 — Already-patterned re-exports (zero-risk, immediate)

These domains already have matching packages. Convert root `src/` files to re-exports.

| Step | Domain | Action |
|---|---|---|
| 1a | `src/session/` | Replace implementation with `export * from '@personaforge/session'`. Redis store already in `@personaforge/adapter-redis`. |
| 1b | `src/knowledge/` | Replace with `export * from '@personaforge/knowledge'`. |
| 1c | `src/shared/errors.ts` | Replace with `export * from '@personaforge/shared'` where shared already has the error classes. |
| 1d | `src/guardrails/` | Replace with `export * from '@personaforge/guard'` guardrails sub-exports. |

**Gate**: `bun run build:all && bun run test` — 515 tests still pass.

---

### Wave 2 — Provider implementations → `@personaforge/models` (medium effort)

Prerequisite: Wave 0 complete (canonical types in `@personaforge/core`).

| Step | Action |
|---|---|
| 2a | Move provider implementations from `src/providers/` (all `*-provider.ts`, `cost-tracker.ts`, `structured-output.ts`, etc.) into `packages/models/src/`. |
| 2b | Remove the moved files from `src/providers/`, replace `src/providers/index.ts` with `export * from '@personaforge/models'`. |
| 2c | Update `src/model.ts` root facade: it already imports from `src/providers/` — point it at `@personaforge/models` instead. |
| 2d | Add package-level tests to `packages/models/` that cover the moved provider classes (unit test with mock HTTP). |

**Gate**: `bun run --cwd packages/models build`, `bun run typecheck`, full test suite green.

---

### Wave 3 — Graph engine → new `@personaforge/graph` package (fixes CLI dep)

Prerequisite: Wave 0.

| Step | Action |
|---|---|
| 3a | Create `packages/graph/` with `package.json`, `tsconfig.json`, `tsup.config.ts`. |
| 3b | Move `src/graph/engine.ts`, `event-store.ts`, `builder.ts`, `types.ts`, `memory.ts`, `scheduler.ts`, `orchestrator.ts`, `plugins.ts` into `packages/graph/src/`. |
| 3c | Export `SqliteEventStore`, `GraphEventType`, `ExecutionId`, `GraphEvent` from `packages/graph/src/index.ts`. |
| 3d | Replace `src/graph/` with re-export barrel `export * from '@personaforge/graph'`. |
| 3e | Update `packages/cli/package.json` to add `"@personaforge/graph": "workspace:*"` as a dependency. |
| 3f | The 5 CLI command files that import from `personaforge/graph` now resolve to the real package — no source change needed. |

**Gate**: `bun run --cwd packages/graph build`, CLI typecheck, test for graph engine passes.

---

### Wave 4 — Memory, planner, reasoning, config (isolated leaf nodes)

These domains have 0 or 1 cross-domain imports and are entirely self-contained.
Create one new package per domain.

| Domain | New package | Dependencies |
|---|---|---|
| `src/memory/` | `@personaforge/memory` | `@personaforge/core` only |
| `src/planner/` | `@personaforge/planner` | `@personaforge/core` contracts only |
| `src/reasoning/` | `@personaforge/reasoning` | `@personaforge/core`, `@personaforge/models` |
| `src/config/` | `@personaforge/config` | `@personaforge/shared` only |
| `src/scheduler/` | `@personaforge/scheduler` | zero src deps |
| `src/compression/` | `@personaforge/compression` | `@personaforge/core` |
| `src/context/` | `@personaforge/context` | `@personaforge/core` |
| `src/storage/` | `@personaforge/storage` | `@personaforge/shared` |
| `src/artifacts/` | `@personaforge/artifacts` | `@personaforge/core` |

**Gate per package**: independent build, lint, typecheck. No inter-package circular deps.

---

### Wave 5 — Production safety deduplication → `@personaforge/guard` + new `@personaforge/production`

Prerequisite: Wave 3 (session already a package for redis-rate-limiter, wave 0 for types).

| Step | Action |
|---|---|
| 5a | Diff `src/production/circuit-breaker.ts` vs `packages/guard/src/circuit-breaker.ts`. Pick the more complete implementation (keep package). Delete the duplicate from `src/`. |
| 5b | Same for `budget.ts` / `rate-limiter.ts`. Keep the `@personaforge/guard` version. |
| 5c | Move `src/production/{approval-store, audit-store, checkpoint, idempotency, health, tenant, graceful-shutdown, resilient-agent, resumable-stream, latency-eval}.ts` into a new `packages/production/src/`. |
| 5d | Replace `src/production/index.ts` with `export * from '@personaforge/production'`. |

**Gate**: `bun run --cwd packages/guard build`, `bun run --cwd packages/production build`, test suite green.

---

### Wave 6 — Agentic runner (TESTS FIRST)

Prerequisite: Wave 2 (models), Wave 5 (guard), `@personaforge/core` canonical types.

> **Blocker**: `src/agentic` has zero test coverage. Do NOT migrate until unit tests are written.

| Step | Action |
|---|---|
| 6a | Write unit tests for `src/agentic/runner.ts` in `tests/` covering the ReAct loop, tool dispatch, streaming response assembly. Aim for ≥ 80% coverage. |
| 6b | Move `src/agentic/runner.ts` and `types.ts` into `packages/core/src/runner/` (or a new `packages/agentic/`). |
| 6c | Replace `src/agentic/index.ts` with re-export barrel. |

---

### Wave 7 — Execution and orchestration (TESTS FIRST, high risk)

Prerequisite: Wave 3 (graph), Wave 6 (agentic).

> **Blocker**: `src/execution` and `src/orchestration` have zero test coverage. Same rule applies.

| Domain | Target | Notes |
|---|---|---|
| `src/execution/` | `@personaforge/execution` (new) or merged into `@personaforge/workflow` | Decide if `engine-v2.ts` supersedes `engine.ts` — delete the older one first. |
| `src/orchestration/` | `@personaforge/orchestration` (new) | 4 800 LOC. A2A, multi-agent, consensus. Highest-risk migration. Write integration tests first. |
| `src/background/` | `@personaforge/background` (new) | Queue abstraction. Optional peer deps (BullMQ, Kafka, etc.). |

---

### Wave 8 — create-agent factory (last)

Prerequisite: **All of waves 0–7 complete.**

`src/create-agent/factory.ts` has 14 cross-domain imports. Once all 14 target domains are packages,
the factory can be refactored to import from those packages and moved into `packages/core/src/create-agent/`
or a new `packages/agentic/` package.

Replace `src/create-agent/index.ts` with a re-export shim.

---

### Wave 9 — DX, SDK, adapters, plugins, extensions, media (thin wrappers)

These are thin facades and adapters. Migrate last because they depend on the full package layer.

`src/dx/`, `src/sdk/`, `src/adapters/`, `src/plugins/`, `src/extensions/`, `src/voice/`, `src/video/`

---

### Final — Root `src/` becomes a compatibility facade

Once all waves are complete, every file under `src/` is either:
- A pure re-export barrel pointing at a workspace package, **or**
- Deleted (if the package covers all cases)

Root `src/index.ts` becomes:

```
// personaforge — backward-compatible umbrella re-export
export * from '@personaforge/core';
export * from '@personaforge/models';
export * from '@personaforge/tools';
// … etc
```

---

## Immediate Next Actions (Start Monday)

These are the highest-ROI, lowest-risk steps to do first:

1. **Wave 0a–0d** — Type reconciliation (`providers/types.ts` → `@personaforge/core`). Unblocks everything.
2. **Wave 3** — Extract `@personaforge/graph` package. Fixes the CLI root-src leakage immediately. Medium effort, clear boundary.
3. **Write tests for `src/agentic` and `src/execution`** (zero coverage is the single biggest risk in the codebase).
4. **Wave 5a–5b** — Deduplicate `circuit-breaker` / `budget` / `rate-limiter`. Stops dual-maintenance today.

---

## Known Blockers Summary

| Blocker | Impact | Resolution |
|---|---|---|
| `providers/types.ts` duplicate in `@personaforge/core` | Blocks all provider/agentic migration | Wave 0 |
| `src/agentic` — 0 test files | Cannot migrate safely | Write tests first |
| `src/execution` — 0 test files | Cannot migrate safely | Write tests first |
| `src/orchestration` — 0 test files | Cannot migrate safely | Write tests first |
| CLI imports `personaforge/graph` (root src) | CLI package not self-contained | Wave 3 |
| `production` duplicates `guard` implementations | Dual maintenance, inconsistent behaviour | Wave 5 dedup |
| `engine.ts` vs `engine-v2.ts` in `src/execution` | Dead code or unclear which is active | Audit and delete older one |


# Guide: multi-tenancy

# Multi-Tenancy

Use `createTenantContext()` to isolate sessions, rate limits, and run context per tenant — without separate databases. All stores are wrapped and all keys are automatically prefixed.

```
import { createTenantContext, TenantRegistry } from 'personaforge/production';
```

---

## Quick start

```
import { createAgent } from 'personaforge';
import { createTenantContext } from 'personaforge/production';
import { createSqliteStore } from 'personaforge/session';

// Single shared session store
const sessionStore = createSqliteStore({ path: './agent.db' });

// In your request handler, scope to the authenticated tenant:
async function handleRequest(req: Request) {
  const tenantId = req.headers.get('x-tenant-id')!;

  const ctx = createTenantContext(tenantId, { sessionStore });

  const agent = createAgent({
    name: 'support',
    instructions: 'Help users with support requests.',
    model: 'gpt-4o-mini',
    apiKey: process.env.OPENAI_API_KEY!,
    sessionStore: ctx.sessionStore,  // all keys are prefixed with 'tenantId:'
  });

  return agent.run(req.body.message, ctx.runContext);
}
```

---

## `createTenantContext`

```
const ctx = createTenantContext('tenant-acme', {
  sessionStore: baseSessionStore,         // wrapped with 'tenant-acme:' prefix
  rateLimitConfig: { maxRequests: 100, intervalMs: 60_000 },  // per-tenant limiter
});

// ctx fields:
// ctx.tenantId        — 'tenant-acme'
// ctx.sessionStore    — TenantScopedSessionStore (auto-prefixes all keys)
// ctx.rateLimiter     — RateLimiter scoped to this tenant
// ctx.runContext       — { tenantId: 'tenant-acme' }  (pass to agent.run())
```

---

## Key isolation in practice

```
// Tenant A and Tenant B share the same Postgres session store,
// but their sessions never overlap:
const ctxA = createTenantContext('tenant-a', { sessionStore });
const ctxB = createTenantContext('tenant-b', { sessionStore });

// Session IDs stored as 'tenant-a:sess-123' vs 'tenant-b:sess-123'
const sessionId = await ctxA.sessionStore.create({ agentId: 'support' });
// → stored as 'tenant-a:<generated-id>'
```

---

## `TenantRegistry` — per-tenant configuration

Use `TenantRegistry` to define configuration for each tenant (rate limits, allowed models):

```
import { TenantRegistry } from 'personaforge/production';

const registry = new TenantRegistry();

registry.register({
  tenantId: 'tenant-acme',
  maxRpm: 100,                // max 100 requests per minute
  maxUsdPerDay: 5.00,         // max $5/day spend
  allowedModels: ['gpt-4o-mini', 'gpt-4o'],
});

registry.register({
  tenantId: 'tenant-enterprise',
  maxRpm: 1000,
  maxUsdPerDay: 50.00,
  allowedModels: ['gpt-4o', 'claude-3-5-sonnet'],
});

// Lookup in request handler
const config = registry.get(tenantId);
if (config?.allowedModels && !config.allowedModels.includes(requestedModel)) {
  return Response.json({ error: 'Model not available on your plan.' }, { status: 403 });
}
```

---

## Namespace each layer explicitly

For full tenant isolation, scope every stateful layer:

```
const ctx = createTenantContext(tenantId, { sessionStore: baseSessionStore });

const agent = createAgent({
  name: 'support',
  instructions: '...',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,

  // Session: auto-namespaced by createTenantContext
  sessionStore: ctx.sessionStore,

  // Memory: namespace manually
  memoryStore: createDbMemoryStore({ db, namespace: tenantId }),

  // Storage: prefix keys manually
  storage: createStorage({ driver: 'file', basePath: `./data/${tenantId}` }),
});
```

---

## `TenantContext` interface

```
interface TenantContext {
  readonly tenantId: string;
  readonly sessionStore: SessionStore;    // TenantScopedSessionStore
  readonly rateLimiter: RateLimiter;
  readonly runContext: { tenantId: string; userId?: string };
}
```

---

## Where to go next

- [Session](./session) — underlying session stores.
- [Production](./production) — `BudgetEnforcer` and `RateLimiter`.
- [Secret manager](./secret-manager) — per-tenant credential isolation.


# Guide: observability

# Observability

The framework ships a full observability stack: structured logging, distributed tracing (OTLP), Prometheus metrics, W3C trace context propagation, and native integrations with Langfuse and LangSmith. Import from `personaforge` or `personaforge/observe`.

## Logging

### `ConsoleLogger`

```
import { ConsoleLogger } from 'personaforge';

const logger = new ConsoleLogger({ level: 'info' });
// levels: 'debug' | 'info' | 'warn' | 'error'

logger.info('Agent started', { agentName: 'assistant', runId: 'run-123' });
logger.warn('Guardrail violation', { rule: 'pii-detection', score: 0.9 });
logger.error('Tool call failed', { tool: 'search', error: err.message });
```

### Attach logger to an agent

```
import { createAgent } from 'personaforge';

const agent = createAgent({
  name: 'production-agent',
  instructions: '...',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
  logger: new ConsoleLogger({ level: 'info' }),
});
```

---

## Tracing

### `InMemoryTracer` (development)

```
import { InMemoryTracer } from 'personaforge';

const tracer = new InMemoryTracer();

// Manually start/end spans
const span = tracer.startSpan('agent-run', { runId: 'run-123', agentName: 'assistant' });
// ... work ...
span.end({ status: 'ok', tokens: 420 });

// Inspect all spans
console.log(tracer.getSpans());
```

### OTLP (production)

Export traces to any OpenTelemetry-compatible backend (Grafana Tempo, Jaeger, Honeycomb, Datadog, etc.):

```
import { OTLPTraceExporter } from 'personaforge';

const exporter = new OTLPTraceExporter({
  endpoint: process.env.OTLP_ENDPOINT!,  // e.g. http://otel-collector:4318/v1/traces
  headers: {
    'x-honeycomb-team': process.env.HONEYCOMB_API_KEY!,
  },
  serviceName: 'my-agent-service',
});

// Plug into the framework's tracer
const tracer = exporter.createTracer();
```

---

## Metrics

### `MetricsCollectorImpl`

```
import { MetricsCollectorImpl } from 'personaforge';

const metrics = new MetricsCollectorImpl();

// Record a counter (e.g. requests, tool calls)
metrics.increment('agent.runs.total', 1, { agentName: 'assistant' });

// Record a gauge (e.g. active sessions)
metrics.gauge('agent.active_sessions', 42);

// Record a histogram (e.g. latency)
metrics.histogram('agent.run.duration_ms', 345, { agentName: 'assistant', status: 'ok' });

// Flush to Prometheus / OTLP
const snapshot = metrics.snapshot();
```

### OTLP Metrics export

```
import { OTLPMetricsExporter } from 'personaforge';

const metricsExporter = new OTLPMetricsExporter({
  endpoint: process.env.OTLP_METRICS_ENDPOINT!,
  serviceName: 'my-agent-service',
  exportIntervalMs: 15_000,  // push every 15s
});
```

---

## W3C trace context propagation

Propagate trace context across HTTP boundaries (microservices, A2A calls):

```
import {
  generateTraceparent,
  parseTraceparent,
  injectTraceHeaders,
  extractTraceContext,
  childSpan,
} from 'personaforge';

// Generate a new root trace
const traceparent = generateTraceparent();
// '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01'

// Parse an incoming header
const ctx = parseTraceparent(req.headers['traceparent']);

// Create a child span context
const child = childSpan(ctx);

// Inject into outbound fetch headers
const headers = injectTraceHeaders({}, ctx);

// Extract from incoming request headers
const incoming = extractTraceContext(req.headers);
```

---

## Langfuse integration

Send traces and evals to [Langfuse](https://langfuse.com):

```
import { sendLangfuseBatch } from 'personaforge';

await sendLangfuseBatch({
  publicKey: process.env.LANGFUSE_PUBLIC_KEY!,
  secretKey: process.env.LANGFUSE_SECRET_KEY!,
  baseUrl: 'https://cloud.langfuse.com',
  batch: [
    {
      id: 'trace-123',
      type: 'trace',
      name: 'agent-run',
      input: { prompt: 'Hello' },
      output: { text: 'Hi there!' },
      usage: { promptTokens: 5, completionTokens: 4 },
    },
  ],
});
```

---

## LangSmith integration

```
import { sendLangSmithRunBatch } from 'personaforge';

await sendLangSmithRunBatch({
  apiKey: process.env.LANGSMITH_API_KEY!,
  projectName: 'my-agent-project',
  runs: [
    {
      id: 'run-456',
      name: 'agent-run',
      runType: 'chain',
      inputs: { prompt: 'Explain quantum computing.' },
      outputs: { text: agentResult.text },
      startTime: Date.now() - 1200,
      endTime: Date.now(),
    },
  ],
});
```

---

## Complete observability setup

```
import { createAgent, ConsoleLogger, OTLPTraceExporter, OTLPMetricsExporter, MetricsCollectorImpl } from 'personaforge';

// Tracing
const traceExporter = new OTLPTraceExporter({
  endpoint: process.env.OTLP_ENDPOINT!,
  serviceName: 'customer-service-agent',
});

// Metrics
const metricsExporter = new OTLPMetricsExporter({
  endpoint: process.env.OTLP_METRICS_ENDPOINT!,
  serviceName: 'customer-service-agent',
  exportIntervalMs: 15_000,
});

// Structured logging
const logger = new ConsoleLogger({ level: 'info' });

const agent = createAgent({
  name: 'customer-service',
  instructions: 'You are a customer service agent.',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
  logger,
});

// Wrap runs with tracing
const tracer = traceExporter.createTracer();
const metrics = new MetricsCollectorImpl();

const run = async (prompt: string, userId: string) => {
  const traceparent = generateTraceparent();
  const span = tracer.startSpan('agent-run', { userId });

  try {
    const result = await agent.run(prompt, { userId, traceId: traceparent });
    span.end({ status: 'ok', tokens: result.usage?.totalTokens });
    metrics.increment('agent.runs.success');
    metrics.histogram('agent.run.duration_ms', result.durationMs ?? 0);
    return result;
  } catch (err) {
    span.end({ status: 'error', error: (err as Error).message });
    metrics.increment('agent.runs.error');
    throw err;
  }
};
```

---

## Where to go next

- [Eval](./eval) — score agent quality with LLM-as-judge and text metrics.
- [Production](./production) — circuit breakers, budget enforcement, rate limiting.


# Guide: orchestration

# Orchestration

The framework ships a full orchestration layer for coordinating multiple agents. Import from `personaforge/workflow` or `personaforge`.

```
import {
  Team, SwarmOrchestrator, createSupervisor, createHandoff,
  createAgentRouter, createConsensus, createPipeline,
  compose, pipe,
} from 'personaforge/workflow';
```

---

## `compose` — sequential pipeline

Chain agents sequentially. Each agent's output becomes the next agent's input.

```
import { compose, createAgent } from 'personaforge';

const researcher = createAgent({ name: 'researcher', instructions: 'Research the topic.', model: 'gpt-4o', apiKey: '...' });
const writer     = createAgent({ name: 'writer',     instructions: 'Write a clear report from the research.', model: 'gpt-4o-mini', apiKey: '...' });
const editor     = createAgent({ name: 'editor',     instructions: 'Edit and polish the report.', model: 'gpt-4o-mini', apiKey: '...' });

const pipeline = compose(researcher, writer, editor);

const result = await pipeline.run('Write a report on the state of quantum computing in 2026.');
console.log(result.text);
```

### `pipe` — functional style

```
import { pipe } from 'personaforge/workflow';

const process = pipe(
  (input: string) => researcher.run(input),
  (r) => writer.run(r.text),
  (r) => editor.run(r.text),
);

const result = await process('Quantum computing 2026');
```

---

## `Team` — role-based coordination

Coordinate a team of specialist agents under a named team identity:

```
import { Team, createAgent } from 'personaforge';

const codeAgent   = createAgent({ name: 'coder',    instructions: 'Write production-quality TypeScript.', model: 'gpt-4o', apiKey: '...' });
const reviewAgent = createAgent({ name: 'reviewer', instructions: 'Review code for bugs and style.', model: 'gpt-4o-mini', apiKey: '...' });
const docsAgent   = createAgent({ name: 'docs',     instructions: 'Write API documentation.', model: 'gpt-4o-mini', apiKey: '...' });

const engineeringTeam = new Team({
  name: 'engineering',
  agents: [codeAgent, reviewAgent, docsAgent],
  strategy: 'parallel',  // 'parallel' | 'sequential' | 'hierarchical'
});

const result = await engineeringTeam.run('Implement a rate-limiter class with tests and docs.');
console.log(result.synthesis);
```

---

## `createSupervisor` — delegating coordinator

A supervisor agent decides which specialist to delegate each task to:

```
import { createSupervisor, createRole } from 'personaforge/orchestration';
import { createAgent } from 'personaforge';

const supervisor = createSupervisor({
  name: 'triage',
  description: 'Coordinates specialist agents to resolve each request.',
  // Each sub-agent is paired with a role describing its responsibilities.
  subAgents: [
    { agent: createAgent({ name: 'billing', instructions: 'Handle billing and payment questions.', model: 'gpt-4o-mini', apiKey: '...' }), role: createRole('billing', ['Handle billing and payment questions']) },
    { agent: createAgent({ name: 'tech',    instructions: 'Solve technical product issues.',        model: 'gpt-4o',      apiKey: '...' }), role: createRole('tech',    ['Solve technical product issues']) },
    { agent: createAgent({ name: 'general', instructions: 'Answer general questions.',               model: 'gpt-4o-mini', apiKey: '...' }), role: createRole('general', ['Answer general questions']) },
  ],
  guidelines: ['Assign each request to the most relevant specialist.'],
  // coordinationType?: 'sequential' (default) | 'parallel'
});

const result = await supervisor.run('My invoice shows the wrong amount.');
console.log(result);
```

---

## `createHandoff` — explicit handoff protocol

Define explicit handoff conditions so agents can transfer control at runtime:

```
import { createHandoff, createAgent } from 'personaforge';

const triageAgent = createAgent({
  name: 'triage',
  instructions: 'Triage the request. Hand off to specialists when needed.',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
});

const specialistAgent = createAgent({
  name: 'specialist',
  instructions: 'Handle complex technical escalations.',
  model: 'gpt-4o',
  apiKey: process.env.OPENAI_API_KEY!,
});

const handoff = createHandoff({
  from: triageAgent,
  to: { specialist: specialistAgent },
  // The router receives a HandoffContext ({ prompt, fromAgent, ... }) and
  // returns the key of the target agent to hand off to.
  router: () => 'specialist',
  maxDepth: 5,
});

const result = await handoff.execute('My database is returning corrupted data after the migration.');
console.log(result.finalOutput.result);  // answered by the specialist
```

---

## `createAgentRouter` — capability-based routing

Route requests to agents based on declared capabilities:

```
import { createAgentRouter, createAgent } from 'personaforge';

const router = createAgentRouter({
  strategy: 'capability-match',  // 'capability-match' | 'round-robin' | 'least-loaded'
  agents: {
    code:  { agent: createAgent({ name: 'code-agent',  instructions: '...', model: 'gpt-4o',      apiKey: '...' }), capabilities: ['coding', 'debugging'] },
    data:  { agent: createAgent({ name: 'data-agent',  instructions: '...', model: 'gpt-4o',      apiKey: '...' }), capabilities: ['data-analysis', 'sql'] },
    write: { agent: createAgent({ name: 'write-agent', instructions: '...', model: 'gpt-4o-mini', apiKey: '...' }), capabilities: ['writing', 'editing'] },
  },
});

const result = await router.route('Fix the SQL query performance issue.');
```

---

## `createConsensus` — multi-agent voting

Run multiple agents on the same prompt and pick the best answer by consensus:

```
import { createConsensus, createAgent } from 'personaforge';

const consensus = createConsensus({
  agents: {
    a: createAgent({ name: 'agent-a', instructions: 'Answer carefully.', model: 'gpt-4o', apiKey: '...' }),
    b: createAgent({ name: 'agent-b', instructions: 'Answer carefully.', model: 'claude-sonnet-4-20250514', apiKey: '...' }),
    c: createAgent({ name: 'agent-c', instructions: 'Answer carefully.', model: 'gpt-4o-mini', apiKey: '...' }),
  },
  strategy: 'majority-vote',  // 'majority-vote' | 'unanimous' | 'weighted' | 'best-of-n'
  quorum: 2,                  // minimum agents that must agree (default: ceil(n/2))
});

const result = await consensus.decide('What is the most efficient sorting algorithm for nearly-sorted data?');
console.log(result.decision);    // winning answer
console.log(result.confidence);  // 0-1 agreement score
```

---

## `SwarmOrchestrator` — dynamic agent swarm

A self-organising swarm where agents spawn sub-agents and hand off dynamically:

The swarm decomposes the task into parallelizable subtasks and dynamically
instantiates specialist subagents (driven by the configured LLM) — you configure
limits and the model, not a fixed agent list. Use the `createSwarm` factory or the
`SwarmOrchestrator` class directly:

```
import { createSwarm } from 'personaforge';

const swarm = createSwarm({
  maxSubagents: 12,
  concurrency: 8,
  subtaskTimeoutMs: 30_000,
  llm: {
    // A model string, a provider instance, or explicit fields — see SwarmLLMConfig.
    provider: 'openrouter:meta-llama/llama-3.3-70b-instruct',
    openRouterApiKey: process.env.OPENROUTER_API_KEY,
  },
});

const result = await swarm.execute({
  prompt: 'Produce a detailed market analysis report for the EV charging industry.',
});
console.log(result.status);            // 'success' | 'partial' | 'failed'
console.log(result.aggregatedOutput);  // collated subtask results
```

---

## `createPipeline` — typed data pipeline

Chain agents with typed input/output contracts:

```
import { createPipeline } from 'personaforge';

// Agents run in order; each agent receives the previous agent's output as its input.
const pipeline = createPipeline({
  name: 'etl',
  agents: [extractAgent, enrichAgent, reportAgent],
});

const report = await pipeline.run('Extract, enrich, and report on the sales data.');
```

---

## A2A (agent-to-agent) HTTP communication

Expose an agent as an HTTP service and connect to it from another process:

```
import { A2AServer, createHttpA2AClient } from 'personaforge/workflow';

// Server side
const server = new A2AServer({ agent: myAgent, port: 3100 });
await server.start();

// Client side (different process / container)
const client = createHttpA2AClient({ url: 'http://agent-service:3100' });
const result = await client.run({ prompt: 'Analyse the data.' });
```

---

## Load balancers

```
import {
  RoundRobinLoadBalancer,
  LeastConnectionsLoadBalancer,
  WeightedResponseTimeLoadBalancer,
} from 'personaforge/workflow';

const balancer = new LeastConnectionsLoadBalancer([agentA, agentB, agentC]);
const agent = balancer.pick();
```

---

## `createGSDCoordinator` — spec-driven execution (GSD)

The **GSD (Get Shit Done) Protocol** is a spec-driven multi-agent pattern that separates execution into three distinct phases to prevent context pollution:
1. **Plan**: An agent analyzes the goal, creates a structured roadmap, and writes requirements.
2. **Execute**: An execution agent completes the roadmap steps sequentially, with each task running in a clean, isolated agent session.
3. **Verify**: A validation agent reviews the roadmap and output to verify all requirements are met.

State is kept aligned by writing to a `.planning` workspace folder containing `REQUIREMENTS.md`, `ROADMAP.md`, and `STATE.md`.

```
import { createGSDCoordinator, FilesystemGSDStorage } from 'personaforge/workflow';

const gsd = createGSDCoordinator({
  projectDir: './my-project',
  plannerAgent,
  executorAgent,
  verifierAgent,
  // Write planning files to disk (defaults to InMemoryGSDStorage if omitted)
  storage: new FilesystemGSDStorage('./my-project/.planning'),
});

// Phase 1: Create the roadmap and requirements
await gsd.plan('Implement a rate limiter class');

// Phase 2: Execute the next incomplete task (run until completed is true)
let step = await gsd.executeStep();
console.log(`Executed: ${step.taskName}`);

// Phase 3: Verify requirements are satisfied
const verification = await gsd.verify();
if (verification.success) {
  console.log('Project verified successfully!');
}
```

---

## `createRalphLoop` — context-isolated cycles (RALF)

The **Ralph / RALF Loop Protocol** (Read-Act-Loop-Finish) runs a single agent in an iterative loop to solve complex tasks. To avoid context bloat and performance degradation, it creates a fresh session instance for each cycle while propagating concise summaries of preceding cycles in the prompt.

```
import { createRalphLoop } from 'personaforge/workflow';

const loop = createRalphLoop({
  agent: codingAgent,
  maxCycles: 5,
  checkComplete: async (ctx) => {
    // Return true once the task is verified (e.g. running tests or validation check)
    return ctx.lastResult.includes('Tests passed');
  },
});

const result = await loop.run('Fix the failing test in src/index.ts');
console.log(result.success);      // true
console.log(result.cyclesRun);   // e.g. 3
```

---

## Extended Multi-Agent Orchestration Patterns

The framework supports 9 advanced agentic interaction patterns under `personaforge` to orchestrate agents for specific reasoning, code execution, or tutoring tasks.

### 1. Mixture-of-Agents (MoA)
Combines multiple proposer agents to generate candidate responses in parallel, then refines them across rounds before a single aggregator agent synthesizes the final result.

```
import { createMixtureOfAgents } from 'personaforge';

const moa = createMixtureOfAgents({
  name: 'MoA-Synthesizer',
  proposers: [coderA, coderB, coderC],
  aggregator: leadCritic,
  rounds: 2,
});

const outcome = await moa.run({ prompt: 'Write an optimized matrix multiplication in TS.' });
```

### 2. Actor-Critic
An Actor agent generates an answer, which a Critic agent reviews. The Actor refines the answer based on the Critic's feedback, looping until satisfying a validator or reaching `maxRefinements`.

```
import { createActorCritic } from 'personaforge';

const actorCritic = createActorCritic({
  name: 'Code-Review-Loop',
  actor: codeAgent,
  critic: reviewerAgent,
  maxRefinements: 3,
  isSatisfactory: (critique) => critique.toLowerCase().includes('looks good'),
});
```

### 3. Socratic Tutor
Wraps an agent to guide users conceptually without giving direct answers, forcing reflection by asking clarifying questions or pointing out contradictions.

```
import { createSocraticAgent } from 'personaforge';

const tutor = createSocraticAgent({
  name: 'Math-Tutor',
  agent: generalAgent,
  topic: 'linear algebra',
  instructions: 'Guide the user to solve systems of equations.',
});
```

### 4. Prompt Chaining
Sequentially pipes a series of structured tasks where each agent's execution depends on the outputs of preceding agents.

```
import { createPromptChain } from 'personaforge';

const chain = createPromptChain({
  name: 'Content-Pipeline',
  steps: [
    { name: 'outline', agent: outlineAgent },
    { 
      name: 'draft', 
      agent: writerAgent,
      template: (input, prev) => `Outline:\n${prev.outline}\n\nWrite a post on: ${input}` 
    },
    { name: 'seo', agent: seoAgent },
  ],
});
```

### 5. Program-of-Thought (PoT)
Delegates mathematical or algorithmic tasks to an agent by prompting it to write executable code (e.g. JavaScript), executes that code in a sandbox runtime, and feeds the results back to the agent to synthesize the final answer.

```
import { createProgramOfThought } from 'personaforge';

const pot = createProgramOfThought({
  name: 'Math-PoT',
  agent: codingAgent,
  // Custom sandbox executor (defaults to a safe Function evaluation)
  executor: async (code) => {
    return { stdout: 'Result: 42', stderr: '' };
  },
});
```

### 6. Skeleton-of-Thought (SoT)
Speeds up long generation tasks by first generating a structured outline (skeleton), then invoking worker agents in parallel to write details for each section, finally joining the details together.

```
import { createSkeletonOfThought } from 'personaforge';

const sot = createSkeletonOfThought({
  name: 'Article-Generator',
  planner: layoutAgent,
  worker: sectionWriterAgent,
  parallel: true, // Generate sections concurrently
});
```

### 7. Step-Back Abstraction
Prompts an agent to "step back" and analyze the underlying conceptual principle or broader context of a task first, then feeds that abstraction as context to a solver agent to resolve the original question.

```
import { createStepBackAgent } from 'personaforge';

const stepBack = createStepBackAgent({
  name: 'Physics-Solver',
  stepBackAgent: conceptualAgent,
  solverAgent: mathAgent,
});
```

### 8. Rejection Sampling (Best-of-N)
Generates `N` candidate answers in parallel and evaluates each candidate using a scoring function or a Judge agent, returning the highest-scoring candidate.

```
import { createRejectionSampling } from 'personaforge';

const bestOfN = createRejectionSampling({
  name: 'Creative-Writer',
  agent: writerAgent,
  n: 3,
  judge: async (candidate) => {
    return candidate.includes('metaphor') ? 10 : 5; // custom score logic
  },
});
```

### 9. Self-Correction / Self-Debugging
Runs an agentic loop that tests the agent's output against a validator function. If the output fails validation, the agent is prompted with the errors to self-correct its answer, up to `maxRetries`.

```
import { createSelfCorrection } from 'personaforge';

const selfDebugger = createSelfCorrection({
  name: 'JSON-Validator',
  agent: jsonAgent,
  validator: (output) => {
    try {
      JSON.parse(output);
      return { valid: true };
    } catch (e: any) {
      return { valid: false, errors: [e.message] };
    }
  },
  maxRetries: 3,
});
```

---

## Where to go next

- [Workflows](./workflows) — DAG-based graph workflows with branching and retries.
- [Reasoning](./reasoning) — step-by-step reasoning loops inside an agent.
- [Production](./production) — circuit breakers and health checks for distributed agent systems.


# Guide: output-parsers

# Output Parsers

Parsers turn raw LLM text into typed, validated data. Every parser extends `Runnable<string, T>`, so they compose with `.pipe()` after an LLM call.

```
import {
  StringOutputParser, JsonOutputParser, CsvListParser, RegexParser,
  OutputFixingParser, RetryWithErrorParser, ParseError,
} from 'personaforge/parsers';
```

---

## `JsonOutputParser`

Extracts JSON from raw text or from a fenced ```` ```json ```` block, and optionally validates against a Zod schema.

```
import { z } from 'zod';

const schema = z.object({ name: z.string(), age: z.number() });
const parser = new JsonOutputParser({ schema });

const chain = llm.pipe(parser);
const person = await chain.invoke('Give me a person as JSON');
// Validated { name, age }; throws ParseError on malformed output
```

Without a schema it returns the parsed value untyped-checked (cast to `T`):

```
const parser = new JsonOutputParser<{ answer: string }>();
```

---

## `StringOutputParser`

Identity parser that trims whitespace. Useful as the terminal step of a chain that just needs clean text.

```
const chain = llm.pipe(new StringOutputParser());
```

---

## `CsvListParser`

Splits a comma-separated response into `string[]`.

```
await new CsvListParser().invoke('apples, bananas, cherries');
// ['apples', 'bananas', 'cherries']
```

`getFormatInstructions()` returns a hint you can inject into your prompt.

---

## `RegexParser`

Extracts named capture groups into a `Record<string, string>`.

```
const parser = new RegexParser(/(?<name>\w+):(?<age>\d+)/);
await parser.invoke('bob:42');
// { name: 'bob', age: '42' }
```

---

## Self-correcting parsers

### `OutputFixingParser`

On parse failure, sends the malformed output plus the error to a (cheap) fixer LLM and parses again.

```
const parser = new OutputFixingParser({
  parser: new JsonOutputParser({ schema }),
  fixer: (prompt) => cheapLlm.generate(prompt),
  maxRetries: 1,
});
```

### `RetryWithErrorParser`

On failure, re-runs the **original chain** with the error fed back into the prompt, letting the model self-correct with full context.

```
const parser = new RetryWithErrorParser({
  parser: new JsonOutputParser({ schema }),
  retryChain: llm,       // a Runnable<string, string>
  maxRetries: 2,
});
```

---

## Error handling

All parsers throw `ParseError` (a named subclass of `Error`) on failure so you can catch parsing problems specifically:

```
try {
  await parser.invoke(raw);
} catch (err) {
  if (err instanceof ParseError) { /* handle malformed output */ }
}
```

---

## Related pages

- [Runnable / LCEL](/guide/runnable) — chain composition.
- [Structured Output](/guide/structured-output) — native provider-level JSON schema.


# Guide: packages

# Packages & Imports

Install `personaforge` once. That is the public consumer package.

Use `personaforge` for the common agent APIs. Use `personaforge/<module>` when you want a more focused import path from the same installation.

```
npm install personaforge
```

## Root imports

Use the root package for the headline APIs that most apps start with.

```
import { agent, defineAgent, compose, tool } from 'personaforge';
```

## Module subpaths

Use subpaths when you want clearer intent or a narrower import surface.

```
import { TavilySearchTool } from 'personaforge/tools/search';
import { createSqliteStore } from 'personaforge/session';
import { GuardrailValidator, createPiiDetectionRule } from 'personaforge/guardrails';
import { withResilience } from 'personaforge/production';
import { CircuitBreaker } from 'personaforge/guard';
import { createHttpService, listenService } from 'personaforge/runtime';
import { ConsoleLogger } from 'personaforge/observability';
import { openai } from 'personaforge/model';
```

Common subpaths:

| Import path | Use for |
|---|---|
| `personaforge/tools` | Integrations and toolkits |
| `personaforge/session` | Session stores |
| `personaforge/guardrails` | Safety rules and validators |
| `personaforge/production` | `withResilience()` and production wrappers |
| `personaforge/guard` | Low-level circuit breaker, rate limiter, health helpers |
| `personaforge/runtime` | HTTP runtime, auth, WebSocket transport |
| `personaforge/orchestration` | Supervisor, routing, consensus, A2A |
| `personaforge/observability` | Logging, tracing, eval utilities |
| `personaforge/llm` | Provider classes and routing utilities |
| `personaforge/model` | `openai()`, `anthropic()`, `ollama()` shorthands |
| `personaforge/processors` | Mastra-style input/output/error processor pipeline |
| `personaforge/durable` | Long-running, resumable agent execution with replay |
| `personaforge/goals` | Durable, thread-scoped judge-scored objectives |
| `personaforge/code-mode` | Sandboxed multi-tool computation |
| `personaforge/approval` | Human-in-the-loop approval + suspended runs |
| `personaforge/events` | Typed event bus + core event vocabulary |
| `personaforge/registry` | Agent registration, discovery, delegation toolkit |
| `personaforge/harness` | `evaluate()` — A/B harness over agents/tasks/workflows |

## Contributor note

The repository is organized internally as a monorepo, so contributors will see `@personaforge/*` workspace package names in implementation code and build scripts.

That internal layout is not the public install story. Consumer docs, app code, and examples should use:

- `personaforge`
- `personaforge/<module>`

## Publish checks

The repository still validates every exported subpath before publishing:

```
npm run package:prepare
```

That command builds the single public package surface and verifies that every declared export target exists on disk.


# Guide: planner

# Planner

The planner module separates goal decomposition from execution. `LLMPlanner` uses a language model to break a goal into a structured `Plan` of `Task` objects. `ClassicalPlanner` uses deterministic rules. `PlanValidator` checks a plan before execution starts.

```
import {
  LLMPlanner,
  ClassicalPlanner,
  PlanValidator,
  TaskPriority,
  TaskStatus,
} from 'personaforge';
```

---

## `LLMPlanner` — LLM-driven decomposition

```
import { createAgent, OpenAIProvider } from 'personaforge';
import { LLMPlanner, TaskPriority } from 'personaforge';

const llm = new OpenAIProvider({ apiKey: process.env.OPENAI_API_KEY!, model: 'gpt-4o' });

const planner = new LLMPlanner(
  {
    maxIterations: 10,
    allowParallelExecution: true,
    model: 'gpt-4o',
    temperature: 0.3,
    maxTokens: 2_000,
  },
  {
    generateText: async (prompt) => {
      const result = await llm.generateText([{ role: 'user', content: prompt }]);
      return result.text;
    },
  },
);

// Generate a plan
const plan = await planner.plan('Launch a new product blog post', {
  availableTools: ['search_web', 'write_content', 'publish_post'],
  constraints: ['Must be done in 2 hours', 'Use SEO best practices'],
});

console.log(plan.tasks.map(t => ({
  id:       t.id,
  name:     t.name,
  priority: t.priority,
  deps:     t.dependencies,
})));
// [
//   { id: 'task-1', name: 'Research keywords', priority: TaskPriority.HIGH, deps: [] },
//   { id: 'task-2', name: 'Write draft',       priority: TaskPriority.MEDIUM, deps: ['task-1'] },
//   { id: 'task-3', name: 'SEO review',        priority: TaskPriority.MEDIUM, deps: ['task-2'] },
//   { id: 'task-4', name: 'Publish post',      priority: TaskPriority.LOW,    deps: ['task-3'] },
// ]
```

---

## `Task` shape

```
interface Task {
  readonly id:                  string;
  readonly name:                string;
  readonly description:         string;
  readonly dependencies:        string[];         // task IDs this depends on
  readonly priority:            TaskPriority;     // CRITICAL=0 HIGH=1 MEDIUM=2 LOW=3
  readonly estimatedDurationMs: number | undefined;
  readonly metadata: {
    toolIds?:         string[];   // which tools this task needs
    requiredMemory?:  string[];   // memory keys needed
    outputKey?:       string;     // key to store result under
    maxRetries?:      number;
    timeoutMs?:       number;
  };
}
```

---

## `PlanValidator`

Validate a plan before executing it:

```
import { PlanValidator } from 'personaforge';

const validator = new PlanValidator();

const validation = validator.validate(plan);

if (!validation.valid) {
  console.error('Plan is invalid:', validation.errors);
  // [{ taskId: 'task-3', message: 'Missing dependency: task-99 does not exist in the plan', severity: 'error' }]
} else {
  console.log('Plan is valid. Executing...');
}
```

---

## Execute a plan

Execute tasks in dependency order with agents:

```
import { createAgent } from 'personaforge';

// Map task names to agents
const executors: Record<string, ReturnType<typeof createAgent>> = {
  'Research keywords': createAgent({ name: 'researcher', instructions: 'Research SEO keywords.', model: 'gpt-4o-mini', apiKey: '...' }),
  'Write draft':       createAgent({ name: 'writer',     instructions: 'Write blog content.',     model: 'gpt-4o',     apiKey: '...' }),
  'SEO review':        createAgent({ name: 'seo',        instructions: 'Review for SEO.',          model: 'gpt-4o-mini', apiKey: '...' }),
  'Publish post':      createAgent({ name: 'publisher',  instructions: 'Publish the post.',        model: 'gpt-4o-mini', apiKey: '...' }),
};

const taskResults: Record<string, string> = {};

for (const task of plan.tasks) {
  const executor = executors[task.name];
  if (!executor) continue;

  // Build context from upstream results
  const context = task.dependencies.map(depId => {
    const depTask = plan.tasks.find(t => t.id === depId);
    return depTask ? `${depTask.name}: ${taskResults[depId]}` : '';
  }).join('\n');

  const result = await executor.run(`${task.description}\n\nContext:\n${context}`);
  taskResults[task.id] = result.text;
  console.log(`✓ ${task.name}`);
}

console.log('Done!', taskResults);
```

---

## `ClassicalPlanner` — deterministic rules

```
import { ClassicalPlanner, PlanningAlgorithm, TaskPriority } from 'personaforge';

// `algorithm` is required: A_STAR | BFS | DFS | GREEDY | HIERARCHICAL
const planner = new ClassicalPlanner({
  algorithm: PlanningAlgorithm.HIERARCHICAL,
});

// Register rule-based decompositions as task patterns
planner.registerPattern({
  name: 'report',
  matches: (goal) => goal.includes('report'),
  generateTasks: (goal) => [
    { id: 'gather',  name: 'Gather data',  description: 'Collect raw data.', dependencies: [], priority: TaskPriority.HIGH,   metadata: {} },
    { id: 'analyse', name: 'Analyse data', description: 'Run analysis.',      dependencies: [], priority: TaskPriority.MEDIUM, metadata: {} },
    { id: 'write',   name: 'Write report', description: 'Write the report.',  dependencies: [], priority: TaskPriority.LOW,    metadata: {} },
  ],
});

const plan = await planner.plan('Generate quarterly sales report');
```

---

## Where to go next

- [Workflows](./workflows) — execute a plan as a structured DAG.
- [Reasoning](./reasoning) — step-by-step chain-of-thought before planning.
- [Orchestration](./orchestration) — multi-agent teams to execute each plan task.


# Guide: plugins

# Plugins

Plugins are cross-cutting extensions that apply to all agents and tools registered in the same `PluginRegistry`. Unlike hooks (which are per-agent), plugins are global: register once, run everywhere.

```
import {
  createPluginRegistry,
  createLoggingPlugin,
  createRateLimitPlugin,
  createTelemetryPlugin,
} from 'personaforge/plugins';
```

---

## Quick start

```
import { createAgent } from 'personaforge';
import {
  createPluginRegistry,
  createLoggingPlugin,
  createRateLimitPlugin,
} from 'personaforge/plugins';

const plugins = createPluginRegistry();

plugins.register(createLoggingPlugin());
plugins.register(createRateLimitPlugin({ maxRpm: 60 }));

const agent = createAgent({
  name: 'my-agent',
  instructions: '...',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
});

// There is no `plugins` option on createAgent — a registry is applied
// manually around each run. `runBeforeHooks` folds every plugin's beforeRun
// over the input (in registration order) and may transform it:
const context = { agentId: 'my-agent', logger: console, metadata: {} };
const input = await plugins.runBeforeHooks({ prompt: 'Summarize the latest report.' }, context);

const result = await agent.run(input.prompt);

// Collect the combined tool middleware from every plugin. Run the after /
// error hooks with `plugins.runAfterHooks(output, context)` and
// `plugins.runErrorHooks(error, context)`.
const toolMiddleware = plugins.getToolMiddleware();
```

---

## Built-in plugins

### `createLoggingPlugin`

Logs every agent invocation, tool call, and error:

```
import { createLoggingPlugin } from 'personaforge/plugins';

plugins.register(createLoggingPlugin(myLogger));  // optional custom logger
```

### `createRateLimitPlugin`

Rejects or queues requests that exceed a per-minute request rate:

```
import { createRateLimitPlugin } from 'personaforge/plugins';

plugins.register(createRateLimitPlugin({
  maxRpm:    60,    // max requests per minute (default: 60)
  maxTokens: 100_000,  // optional token budget per minute
}));
```

### `createTelemetryPlugin`

Emits metrics counters and histograms to any `MetricsCollector`:

```
import { createTelemetryPlugin } from 'personaforge/plugins';

plugins.register(createTelemetryPlugin(metricsCollector));
```

---

## `PluginRegistry` interface

```
interface PluginRegistry {
  register(plugin: Plugin): void;
  unregister(pluginId: string): boolean;
  get(pluginId: string): Plugin | undefined;
  list(): Plugin[];

  /** Run every plugin's beforeRun in order — may transform the input. */
  runBeforeHooks(input: AgentInput, context: PluginContext): Promise<AgentInput>;
  /** Run every plugin's afterRun in order — may transform the output. */
  runAfterHooks(output: AgentOutput, context: PluginContext): Promise<AgentOutput>;
  /** Combined tool middleware contributed by all plugins. */
  getToolMiddleware(): (ToolMiddleware | ToolMiddlewareObject)[];
  /** Fan an error out to every plugin's onError. */
  runErrorHooks(error: Error, context: PluginContext): Promise<void>;
}
```

---

## Author a custom plugin

```
import type { Plugin } from 'personaforge/plugins';

const auditPlugin: Plugin = {
  id: 'audit-logger',
  name: 'Audit Logger',

  async beforeRun(input, ctx) {
    await auditLog.write({ event: 'run.start', runId: ctx.runId, userId: ctx.userId });
    return input;  // must return (possibly modified) input
  },

  async afterRun(output, ctx) {
    await auditLog.write({ event: 'run.end', runId: ctx.runId, tokens: output.usage?.totalTokens });
    return output;  // must return (possibly modified) output
  },

  async toolMiddleware(name, args, next) {
    const start = Date.now();
    try {
      const result = await next(name, args);
      metrics.counter('tool.success', 1, { tool: name });
      return result;
    } catch (err) {
      metrics.counter('tool.error', 1, { tool: name });
      throw err;
    }
  },

  async onError(error, ctx) {
    await alerting.notify(`Agent error in run ${ctx.runId}: ${error.message}`);
  },
};

plugins.register(auditPlugin);
```

### `Plugin` interface

```
interface Plugin {
  /** Unique identifier */
  readonly id: string;
  /** Human-readable name */
  readonly name: string;

  /** Runs before every agent.run() — can modify input */
  beforeRun?(input: AgentInput, ctx: PluginContext): Promise<AgentInput>;

  /** Runs after every agent.run() — can modify output */
  afterRun?(output: AgentOutput, ctx: PluginContext): Promise<AgentOutput>;

  /** Tool middleware — wraps every tool call */
  toolMiddleware?(name: string, args: unknown, next: (name: string, args: unknown) => Promise<unknown>): Promise<unknown>;

  /** Called when an unhandled error occurs */
  onError?(error: Error, ctx: PluginContext): Promise<void>;
}
```

---

## Convert hooks to a plugin

If you already have `AgentLifecycleHooks`, use `hooksToPlugin` to register them as a plugin:

```
import { hooksToPlugin } from 'personaforge/plugins';

const myPlugin = hooksToPlugin('my-hooks', {
  beforeRun: async (input) => { console.log('run started'); return input; },
  afterRun:  async (output) => { console.log('run finished'); return output; },
});

plugins.register(myPlugin);
```

---

## Where to go next

- [Hooks](./hooks) — per-agent lifecycle hooks.
- [Observability](./observability) — OpenTelemetry spans and metrics.
- [Production](./production) — circuit breakers and rate limiters at the agent level.


# Guide: processors

# Processors

`personaforge/processors` is a Mastra-style inspired processor pipeline. Input/output/error processors transform, validate, and control messages as they flow through an agent. Combined with the built-in guardrail processors, they form the **security + quality layer** of the runtime.

```
import { ModerationProcessor, TokenLimiter, PIIDetector } from 'personaforge/processors';
```

---

## Quick start

Attach processors to an agent at creation time:

```
import { agent } from 'personaforge';
import {
  TokenLimiter,
  PIIDetector,
  ModerationProcessor,
  PromptInjectionDetector,
} from 'personaforge/processors';

const bot = agent({
  instructions: 'You are a helpful assistant.',
  inputProcessors: [
    new TokenLimiter(64_000),                       // cap input size
    new PIIDetector({ strategy: 'redact' }),        // redact PII
    new PromptInjectionDetector({ strategy: 'block' }),
    new ModerationProcessor({ strategy: 'block' }), // content moderation
  ],
});
```

You can also override processors **per run** — per-call arrays replace the agent-level arrays for that run only:

```
await bot.run('Tell me a story', {
  processors: {
    input: [new TokenLimiter(10_000)],
    output: [new EnsureFinalResponse()],
  },
});
```

---

## Processor stages

A `ProcessorSet` has three phases:

| Phase | Runs when | Typical use |
|---|---|---|
| `input` | Before messages reach the LLM | Token caps, PII redaction, moderation, injection defense |
| `output` | After the LLM responds | Validate answer shape, final-response enforcement, cache writes |
| `error` | Provider rejects a request | Retry with recovery messages |

Within a processor, several hooks fire at specific points (input, input-step, LLM-request, LLM-response, output-step, output-result, output-stream, API-error). A processor coordinates between its own hooks via a per-request `state` scratchpad.

Processors can `abort()` (throw a `TripWireError`) to block a request, `sendSignal()` to inject a `<system-reminder>` user message, and reuse per-request state across hooks.

---

## Built-in processors

| Processor | What it does |
|---|---|
| `TokenLimiter` | Caps input tokens (block/warn) |
| `UnicodeNormalizer` | Normalizes unicode in messages |
| `ToolCallFilter` | Allows/blocks tool calls by name |
| `PIIDetector` | Detects / redacts / blocks PII |
| `PromptInjectionDetector` | Detects / blocks prompt-injection patterns |
| `ModerationProcessor` | Content moderation (block/warn/detect) |
| `CostGuardProcessor` | Budgets request cost |
| `LanguageDetector` | Detects message language |
| `BatchPartsProcessor` | Batches multimodal parts |
| `SystemPromptScrubber` | Strips secrets from system prompts |
| `ResponseCache` | Caches LLM responses by prompt |
| `EnsureFinalResponse` | Forces a final answer after max steps |
| `ContextLengthHandler` | Handles context overflow |

LLM-backed processors accept an optional `classify` function so you can plug in any model judge; deterministic heuristic implementations are used by default (zero extra calls).

---

## Writing a custom processor

A processor is any object implementing the `Processor` interface — implement one or more hooks; `id` must be unique (it scopes the per-request `state`):

```
import type { Processor, ProcessInputArgs } from 'personaforge/processors';

const myChecker: Processor = {
  id: 'my-checker',

  async processInput({ messages, abort }: ProcessInputArgs) {
    for (const m of messages) {
      if (typeof m.content === 'string' && m.content.includes('secret:')) {
        abort('Contains forbidden content', { metadata: { match: 'secret:' } });
      }
    }
    return messages;
  },
};
```

Other hooks: `processInputStep`, `processLLMRequest`, `processLLMResponse`, `processOutputStep`, `processOutputStream`, `processOutputResult`, and `processAPIError`.

---

## Related pages

- [Guardrails](./guardrails) — the guardrail module (rules + validators).
- [Memory](./memory) — memory processors (`MessageHistoryProcessor`, …).
- [Production](./production) — resilience and safety in production.


# Guide: production

# Production

The production package wraps the agent runtime with resilience, observability, and control-plane primitives. Everything is pluggable and composable — add only what you need.

```
import {
  CircuitBreaker, createLLMCircuitBreaker,
  RateLimiter, createOpenAIRateLimiter,
  BudgetEnforcer, InMemoryBudgetStore,
  HealthCheckManager, createLLMHealthCheck,
  GracefulShutdown, createGracefulShutdown,
  ResilientAgent, withResilience,
  InMemoryAuditStore, SqliteAuditStore, createSqliteAuditStore,
  InMemoryIdempotencyStore, createSqliteIdempotencyStore,
  createSqliteCheckpointStore,
} from 'personaforge/production';
```

---

## `ResilientAgent` — all-in-one wrapper

The fastest way to get production resilience — wraps a `createAgent()` agent with circuit breaker, rate limiter, budget enforcement, checkpointing, and idempotency:

```
import { createAgent } from 'personaforge';
import { withResilience } from 'personaforge/production';

const agent = createAgent({
  name: 'production-agent',
  instructions: 'You are a customer service assistant.',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
});

const resilientAgent = withResilience(agent, {
  circuitBreaker: {
    failureThreshold: 5,      // open after 5 failures
    resetTimeoutMs: 30_000,   // retry after 30s
  },
  rateLimit: { maxRpm: 60 },  // max requests per minute
  healthCheck: true,
  gracefulShutdown: true,
  retry: { maxRetries: 2, backoffMs: 500 },
});

// Use exactly like a regular agent
const result = await resilientAgent.run('Help me with my order.', {
  sessionId: 'session-1',
  userId: 'user-42',
  runId: 'run-abc',       // used for idempotency
});
```

---

## Circuit breaker

Prevent cascading failures by temporarily stopping calls to a failing dependency:

```
import { CircuitBreaker, CircuitState, createLLMCircuitBreaker } from 'personaforge/production';

// Factory for LLM circuit breakers (pre-configured sensible defaults)
const cb = createLLMCircuitBreaker('openai', {
  failureThreshold: 5,
  resetTimeoutMs: 30_000,
  onStateChange: (from, to) => {
    console.log(`Circuit: ${from} → ${to}`);
    if (to === CircuitState.OPEN) alert('OpenAI circuit opened!');
  },
});

// Wrap any async operation
const result = await cb.execute(async () => {
  return await openai.chat(messages);
});

console.log(cb.getState());  // CLOSED | OPEN | HALF_OPEN
console.log(cb.getMetrics()); // { totalCalls, failures, successes, lastFailure }
```

---

## Rate limiter

Token-bucket rate limiting for external APIs:

```
import { RateLimiter, createOpenAIRateLimiter, RateLimitError } from 'personaforge/production';

// Factory for OpenAI (Tier 1 defaults: 60 RPM + 10 burst)
const limiter = createOpenAIRateLimiter();

// Custom
const limiter2 = new RateLimiter({
  name: 'anthropic',
  maxRequests: 20,
  intervalMs: 60_000,
  burstCapacity: 5,
  overflowMode: 'queue',     // 'reject' (default) | 'queue'
  maxQueueSize: 100,
  maxQueueWaitMs: 30_000,
});

try {
  await limiter.acquire();
  const result = await callOpenAI();
  limiter.release();
} catch (err) {
  if (err instanceof RateLimitError) {
    console.log(`Rate limited. Retry after ${err.retryAfterMs}ms`);
  }
}
```

## Redis rate limiter (distributed)

```
import { RedisRateLimiter } from 'personaforge/production';

const limiter = new RedisRateLimiter({
  redis: process.env.REDIS_URL!,
  name: 'openai',
  maxRequests: 60,
  intervalMs: 60_000,
});
```

---

## Budget enforcement

Hard stop on LLM spend per run, per user, and per month:

```
import { BudgetEnforcer, InMemoryBudgetStore, BudgetExceededError, estimateCostUsd } from 'personaforge/production';

const budget = new BudgetEnforcer({
  maxUsdPerRun: 0.50,
  maxUsdPerUser: 10.00,
  maxUsdPerMonth: 500.00,
  onExceeded: 'throw',   // 'throw' | 'warn' | 'truncate'
  store: new InMemoryBudgetStore(),
});

// Estimate before running
const estimatedCost = estimateCostUsd('gpt-4o-mini', { promptTokens: 1_000, completionTokens: 500 });

try {
  await budget.checkAndReserve({ userId: 'user-42', estimatedUsd: estimatedCost });
  const result = await agent.run(prompt);
  await budget.commit({ userId: 'user-42', actualUsd: result.usage?.totalCost ?? 0 });
} catch (err) {
  if (err instanceof BudgetExceededError) {
    return { error: 'Monthly budget exceeded.' };
  }
}
```

---

## Health checks

```
import {
  HealthCheckManager, HealthStatus,
  createLLMHealthCheck,
  createSessionStoreHealthCheck,
  createHttpHealthCheck,
  createCustomHealthCheck,
} from 'personaforge/production';

const health = new HealthCheckManager({
  checks: [
    createLLMHealthCheck('openai', openaiProvider),
    createSessionStoreHealthCheck('redis', redisSessionStore),
    createHttpHealthCheck('db-api', 'https://api.internal/health'),
    createCustomHealthCheck('queue', async () => {
      const lag = await queue.getLag();
      return lag < 1000 ? { status: HealthStatus.HEALTHY } : { status: HealthStatus.DEGRADED };
    }),
  ],
  intervalMs: 30_000,
});

const report = await health.check();
console.log(report);
// { status: 'healthy', components: { openai: 'healthy', redis: 'healthy', ... } }

// Expose as HTTP endpoint
app.get('/health', async (req, res) => {
  const report = await health.check();
  res.status(report.status === 'healthy' ? 200 : 503).json(report);
});
```

---

## Graceful shutdown

```
import { createGracefulShutdown, withShutdownGuard } from 'personaforge/production';

const shutdown = createGracefulShutdown({
  timeoutMs: 30_000,
  onShutdown: (event) => logger.info('Shutting down', event),
});

// Register cleanup handlers
shutdown.register('session-store', () => sessionStore.flush());
shutdown.register('queue', () => queue.drain());
shutdown.register('http-server', () => server.close(30_000)); // drain up to 30 s

// Guard long-running operations against premature termination
const safeRun = withShutdownGuard(shutdown, async () => {
  return agent.run(prompt);
});
```

---

## Audit logs

```
import { SqliteAuditStore, createSqliteAuditStore } from 'personaforge/production';

const auditStore = createSqliteAuditStore('./agent.db');

// Log a run
await auditStore.append({
  runId: 'run-123',
  userId: 'user-42',
  agentName: 'billing-agent',
  prompt: userPrompt,
  response: result.text,
  toolCalls: result.toolCalls,
  durationMs: 420,
  tokens: result.usage?.totalTokens,
});

// Query audit trail
const entries = await auditStore.query({
  userId: 'user-42',
  from: new Date('2026-05-01'),
  to: new Date('2026-05-31'),
  limit: 100,
});
```

---

## Idempotency (exactly-once runs)

Prevent duplicate runs from retried HTTP requests:

```
import { createSqliteIdempotencyStore } from 'personaforge/production';

const idempotency = createSqliteIdempotencyStore('./agent.db');

// Pass as runId — the framework deduplicates automatically
const result = await agent.run(prompt, {
  runId: req.headers['idempotency-key'] as string,
  // If a run with this ID already completed, returns the cached result instantly
});
```

---

## Cascade delete

Clean up all data associated with a session:

```
import { deleteSession } from 'personaforge/production';

await deleteSession({
  sessionId: 'session-1',
  sessionStore,
  memoryStore,
  checkpointStore,
  auditStore,
});
```

---

## Where to go next

- [HITL](./hitl) — human approval gates.
- [Observability](./observability) — tracing, metrics, Langfuse.
- [Multi-tenancy](./multi-tenancy) — per-tenant isolation.
- [Example 13: Production](../examples/13-production) — full production setup example.


# Guide: providers

# Providers

Every provider implements the same `LLMProvider` interface. Swapping one for another is a single-line change in `createAgent()`.

## Quick start

```
import { createAgent } from 'personaforge';
import { OpenAIProvider } from 'personaforge';

const agent = createAgent({
  name: 'assistant',
  instructions: 'You are a helpful assistant.',
  llm: new OpenAIProvider({ apiKey: process.env.OPENAI_API_KEY! }),
});

const result = await agent.run('Explain transformers in two sentences.');
console.log(result.text);
```

Swap `OpenAIProvider` for any provider below — the rest of the agent code stays the same.

---

## Native SDK providers

These providers use their official SDKs at runtime (peer dependencies — install only what you use).

### OpenAI

```
import { OpenAIProvider } from 'personaforge';

const llm = new OpenAIProvider({
  apiKey: process.env.OPENAI_API_KEY!,
  model: 'gpt-4o',          // default: gpt-4o
  // baseURL: '...'         // override for custom endpoints
  // debug: true            // log raw API calls
});
```

**Install:** `npm install openai`

**Popular models:** `gpt-4o`, `gpt-4o-mini`, `gpt-4.1`, `gpt-4.1-nano`, `o3-mini`, `o4-mini`

### Anthropic

```
import { AnthropicProvider } from 'personaforge';

const llm = new AnthropicProvider({
  apiKey: process.env.ANTHROPIC_API_KEY!,
  model: 'claude-sonnet-4-20250514',  // default: claude-3-5-sonnet-20241022
});
```

**Install:** `npm install @anthropic-ai/sdk`

**Popular models:** `claude-opus-4-20250514`, `claude-sonnet-4-20250514`, `claude-haiku-4-20250514`

### Google Gemini

```
import { GoogleProvider } from 'personaforge';

const llm = new GoogleProvider({
  apiKey: process.env.GOOGLE_API_KEY!,
  model: 'gemini-2.5-pro-preview',  // default: gemini-2.0-flash
});
```

**Install:** `npm install @google/generative-ai`

**Popular models:** `gemini-2.5-pro-preview`, `gemini-2.0-flash`, `gemini-1.5-pro`, `gemini-1.5-flash`

### Amazon Bedrock

```
import { BedrockConverseProvider } from 'personaforge';

const llm = new BedrockConverseProvider({
  region: 'us-east-1',
  modelId: 'anthropic.claude-3-5-sonnet-20240620-v1:0',
  // client: myPrebuiltClient   // optional
});
```

**Install:** `npm install @aws-sdk/client-bedrock-runtime`

Uses the default AWS credential chain (env vars, instance profile, etc.).

---

## Multi-model gateway

### OpenRouter

Access every major model through one API key and one endpoint.

```
import { createOpenRouterProvider } from 'personaforge';

const llm = createOpenRouterProvider({
  apiKey: process.env.OPENROUTER_API_KEY!,
  model: 'anthropic/claude-sonnet-4',  // any OpenRouter model id
});
```

**Popular model ids:** `openai/gpt-4o`, `anthropic/claude-opus-4`, `google/gemini-2.5-pro-preview`, `meta-llama/llama-3.3-70b-instruct`

---

## Fast inference

### Groq (LPU)

```
import { createGroqProvider } from 'personaforge';

const llm = createGroqProvider({
  apiKey: process.env.GROQ_API_KEY,
  model: 'llama-3.3-70b-versatile',  // default
});
```

**Popular models:** `llama-3.3-70b-versatile`, `llama-3.1-8b-instant`, `gemma2-9b-it`, `mixtral-8x7b-32768`

### Cerebras

```
import { createCerebrasProvider } from 'personaforge';

const llm = createCerebrasProvider({
  apiKey: process.env.CEREBRAS_API_KEY,
  model: 'llama3.3-70b',
});
```

### Fireworks AI

```
import { createFireworksProvider } from 'personaforge';

const llm = createFireworksProvider({
  apiKey: process.env.FIREWORKS_API_KEY,
  model: 'accounts/fireworks/models/llama-v3p3-70b-instruct',  // default
});
```

### SambaNova

```
import { createSambaNovaProvider } from 'personaforge';

const llm = createSambaNovaProvider({
  apiKey: process.env.SAMBANOVA_API_KEY,
  model: 'Meta-Llama-3.3-70B-Instruct',
});
```

---

## Other cloud providers

### xAI (Grok)

```
import { createXAIProvider } from 'personaforge';

const llm = createXAIProvider({
  apiKey: process.env.XAI_API_KEY,
  model: 'grok-3',  // default. Also: grok-3-mini, grok-2
});
```

### Together AI

```
import { createTogetherProvider } from 'personaforge';

const llm = createTogetherProvider({
  apiKey: process.env.TOGETHER_API_KEY,
  model: 'meta-llama/Llama-3.3-70B-Instruct-Turbo',  // default
});
```

### DeepSeek

```
import { createDeepSeekProvider } from 'personaforge';

const llm = createDeepSeekProvider({
  apiKey: process.env.DEEPSEEK_API_KEY,
  model: 'deepseek-chat',      // DeepSeek-V3 (default)
  // model: 'deepseek-reasoner'  // DeepSeek-R1
});
```

### Mistral AI

```
import { createMistralProvider } from 'personaforge';

const llm = createMistralProvider({
  apiKey: process.env.MISTRAL_API_KEY,
  model: 'mistral-large-latest',  // default. Also: codestral-latest
});
```

### Perplexity (web-grounded)

```
import { createPerplexityProvider } from 'personaforge';

const llm = createPerplexityProvider({
  apiKey: process.env.PERPLEXITY_API_KEY,
  model: 'sonar-pro',  // default. Also: sonar-reasoning-pro
});
```

### Cohere (Command R)

```
import { createCohereProvider } from 'personaforge';

const llm = createCohereProvider({
  apiKey: process.env.COHERE_API_KEY,
  model: 'command-r-plus-08-2024',  // default
});
```

### NVIDIA NIM

```
import { createNvidiaProvider } from 'personaforge';

const llm = createNvidiaProvider({
  apiKey: process.env.NVIDIA_API_KEY,
  model: 'meta/llama-3.3-70b-instruct',
});
```

### Hyperbolic

```
import { createHyperbolicProvider } from 'personaforge';

const llm = createHyperbolicProvider({
  apiKey: process.env.HYPERBOLIC_API_KEY,
  model: 'meta-llama/Llama-3.3-70B-Instruct',
});
```

### Deep Infra

```
import { createDeepInfraProvider } from 'personaforge';

const llm = createDeepInfraProvider({
  apiKey: process.env.DEEPINFRA_API_KEY,
  model: 'meta-llama/Meta-Llama-3.1-70B-Instruct',
});
```

### Hugging Face Inference API

```
import { createHuggingFaceProvider } from 'personaforge';

const llm = createHuggingFaceProvider({
  apiKey: process.env.HF_API_KEY,
  model: 'meta-llama/Llama-3.3-70B-Instruct',
});
```

### Replicate

```
import { createReplicateProvider } from 'personaforge';

const llm = createReplicateProvider({
  apiKey: process.env.REPLICATE_API_KEY,
  model: 'meta/meta-llama-3-70b-instruct',
});
```

---

## Enterprise

### Azure OpenAI

```
import { createAzureOpenAIProvider } from 'personaforge';

const llm = createAzureOpenAIProvider({
  apiKey: process.env.AZURE_OPENAI_API_KEY,
  resource: process.env.AZURE_OPENAI_RESOURCE,      // Azure resource name
  deployment: process.env.AZURE_OPENAI_DEPLOYMENT,  // deployment name
  apiVersion: '2025-01-01-preview',                 // default
});
```

### IBM watsonx.ai

```
import { createWatsonxProvider } from 'personaforge';

const llm = createWatsonxProvider({
  apiKey: process.env.WATSONX_API_KEY,
  model: 'ibm/granite-13b-chat-v2',
});
```

### Snowflake Cortex

```
import { createSnowflakeProvider } from 'personaforge';

const llm = createSnowflakeProvider({
  apiKey: process.env.SNOWFLAKE_API_KEY,
  model: 'snowflake-arctic-instruct',
});
```

### Cloudflare AI

```
import { createCloudflareProvider } from 'personaforge';

const llm = createCloudflareProvider({
  apiKey: process.env.CLOUDFLARE_API_KEY,
  model: '@cf/meta/llama-3.3-70b-instruct-fp8-fast',
});
```

---

## Self-hosted and local

### Ollama

```
import { OpenAIProvider } from 'personaforge';

// Ollama exposes an OpenAI-compatible API on port 11434
const llm = new OpenAIProvider({
  baseURL: 'http://localhost:11434/v1',
  apiKey: 'ollama',   // required by the SDK, ignored by Ollama
  model: 'llama3.2',  // run: ollama pull llama3.2
});
```

### vLLM

```
import { createVllmProvider } from 'personaforge';

const llm = createVllmProvider({
  baseURL: 'http://localhost:8000/v1',  // default (VLLM_BASE_URL env)
  model: 'meta-llama/Llama-3.3-70B-Instruct',
});
// Start server: vllm serve meta-llama/Llama-3.3-70B-Instruct --port 8000
```

### LM Studio

```
import { createLmStudioProvider } from 'personaforge';

const llm = createLmStudioProvider({
  baseURL: 'http://localhost:1234/v1',  // default
  model: 'local-model',
});
```

### Generic OpenAI-compatible endpoint

```
import { createOpenAICompatibleProvider } from 'personaforge';

const llm = createOpenAICompatibleProvider({
  baseURL: 'https://my-gateway.internal/v1',
  apiKey: process.env.MY_API_KEY!,
  model: 'my-fine-tuned-model',
});
```

---

## Chinese frontier providers

```
import {
  createHunyuanProvider,     // Tencent Hunyuan
  createVolcengineProvider,  // ByteDance Volcengine (Doubao)
  createMinimaxProvider,     // MiniMax
  createBaichuanProvider,    // Baichuan
  createStepfunProvider,     // Stepfun
  createInternLMProvider,    // InternLM (Shanghai AI Lab)
  createMoonshotProvider,    // Moonshot (Kimi)
  createDashScopeProvider,   // Alibaba DashScope (Qwen)
  createZhipuProvider,       // Zhipu AI (GLM)
  createYiProvider,          // 01.AI (Yi)
} from 'personaforge';

const llm = createDashScopeProvider({
  apiKey: process.env.DASHSCOPE_API_KEY,
  model: 'qwen-max',
});
```

---

## Fallback chain

Chain multiple providers so failures cascade automatically.

```
import { FallbackChainProvider, FallbackStrategy } from 'personaforge';

const llm = new FallbackChainProvider({
  providers: [
    createGroqProvider({ model: 'llama-3.1-8b-instant' }),  // fast, cheap
    new OpenAIProvider({ apiKey: process.env.OPENAI_API_KEY!, model: 'gpt-4o-mini' }),
    new OpenAIProvider({ apiKey: process.env.OPENAI_API_KEY!, model: 'gpt-4o' }),
  ],
  strategy: FallbackStrategy.ANY_ERROR,
  // strategy: FallbackStrategy.RATE_LIMIT  // cascade only on 429s
  debug: true,
});
```

**Strategies:** `ANY_ERROR` · `RATE_LIMIT` · `TIMEOUT` · `API_ERROR`

---

## Bring your own provider

Any object that satisfies this interface works:

```
interface LLMProvider {
  generateText(messages: Message[], options?: GenerateOptions): Promise<GenerateResult>;
  streamText?(messages: Message[], options?: StreamOptions): Promise<GenerateResult>;
}
```

---

## Where to go next

- [LLM Router](./llm-router) — route each request to the best model per task, cost, and speed.
- [Stream utilities](./stream-utils) — consume and transform provider streams.
- [Production](./production) — circuit breakers, budget limits, and observability for provider calls.


# Guide: rag

# Retrieval Augmented Generation

The knowledge layer lets you ingest documents, embed them into a vector store, and attach them to an agent so answers are grounded in your content rather than model guesswork.

```
import {
  KnowledgeEngine,
  createKnowledgeEngine,
  InMemoryVectorStore,   // built-in, good for <10 000 docs
  loadPdf, loadCsv, loadUrl,
} from 'personaforge';
```

---

## Quick start

```
import { createAgent } from 'personaforge';
import { createKnowledgeEngine, loadUrl } from 'personaforge';
import { OpenAIEmbeddingProvider } from 'personaforge';

// 1. Build the engine — `embed` is an EmbeddingFn: (text) => Promise<number[]>
const embedder = new OpenAIEmbeddingProvider({ apiKey: process.env.OPENAI_API_KEY! });
const kb = createKnowledgeEngine({
  embed: (text) => embedder.embed(text),
  // default: InMemoryVectorStore (cosine similarity)
});

// 2. Ingest documents
const docs = await loadUrl('https://docs.example.com/api-reference', { recursive: true, maxPages: 20 });
await kb.addDocuments(docs);

// 3. Attach to agent
const agent = createAgent({
  name: 'docs-assistant',
  instructions: 'Answer questions about our product using the provided documentation.',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
  knowledgebase: kb,
  addKnowledgeToContext: true,   // automatically prepends retrieved chunks to system prompt (default: true when a knowledgebase is set)
});

const result = await agent.run('How do I authenticate API requests?');
console.log(result.text);
```

---

## Document loaders

### Load from URL

```
import { loadUrl } from 'personaforge';

const docs = await loadUrl('https://example.com/docs', {
  recursive: true,
  maxPages: 50,
  selector: 'main',  // CSS selector to extract content from
});
```

### Load PDF

```
import { loadPdf } from 'personaforge';

const docs = await loadPdf('./data/handbook.pdf', {
  splitByPage: true,  // one Document per page
  metadata: { source: 'handbook', version: '2.1' },
});
```

### Load CSV

```
import { loadCsv } from 'personaforge';

const docs = await loadCsv('./data/products.csv', {
  contentColumn: 'description',   // column to use as document content
  metadataColumns: ['sku', 'category', 'price'],
});
```

### Manual documents

```
import type { Document } from 'personaforge';

const docs: Document[] = [
  {
    id: crypto.randomUUID(),
    content: 'The refund policy allows returns within 30 days of purchase.',
    metadata: { source: 'policy', section: 'refunds' },
  },
];
await kb.addDocuments(docs);
```

---

## Vector store backends

### InMemoryVectorStore (default)

Good for development and up to ~10 000 documents. Data is lost on process restart.

```
const embedder = new OpenAIEmbeddingProvider({ apiKey: '...' });
const kb = createKnowledgeEngine({
  embed: (text) => embedder.embed(text),
  // InMemoryVectorStore is the default; no extra config needed
});
```

### PgvectorKnowledgeAdapter

Production-ready vector search backed by PostgreSQL + pgvector:

```
import { PgvectorKnowledgeAdapter, createKnowledgeEngine } from 'personaforge';

const adapter = new PgvectorKnowledgeAdapter({
  connectionString: process.env.DATABASE_URL!,
  tableName: 'knowledge_embeddings',
  dimensions: 1536,  // match your embedding model
});

const kb = createKnowledgeEngine({ embed: myEmbed, store: adapter });
```

### ChromaKnowledgeAdapter

```
import { ChromaKnowledgeAdapter } from 'personaforge';

const adapter = new ChromaKnowledgeAdapter({
  url: 'http://localhost:8000',
  collectionName: 'my-docs',
  embed: myEmbed,   // EmbeddingFn used to embed docs and queries
});
```

### Neo4jKnowledgeAdapter — graph RAG

```
import { Neo4jKnowledgeAdapter } from 'personaforge';

const adapter = new Neo4jKnowledgeAdapter({
  uri: process.env.NEO4J_URI!,
  username: process.env.NEO4J_USER!,
  password: process.env.NEO4J_PASSWORD!,
  database: 'docs',
});
```

### DbKnowledgeEngine — zero infra (SQLite-backed)

```
import { createDbKnowledgeEngine } from 'personaforge';
import { SqliteAgentDb } from 'personaforge/db';

const db = new SqliteAgentDb({ path: './agent.db' });
const kb = createDbKnowledgeEngine({ db, embed: myEmbed });
```

---

## Retrieval options

```
// When the engine is attached to an agent via `knowledgebase`, retrieval runs
// automatically before each run. To build the retrieved context manually,
// call buildContext(query, topK?) — it returns the top-k chunks joined into a
// single string, ready to inject into a prompt.
const context = await kb.buildContext('How do I reset my password?', 5);
console.log(context);
```

---

## Embedding providers

```
import { OpenAIEmbeddingProvider } from 'personaforge';

const openaiEmbed = new OpenAIEmbeddingProvider({ apiKey: '...', model: 'text-embedding-3-small' });
```

### Custom embedding function

Any `async (text: string) => number[]` works:

```
import type { EmbeddingFn } from 'personaforge';

const myEmbed: EmbeddingFn = async (text) => {
  const res = await fetch('https://my-embed-service/embed', {
    method: 'POST', body: JSON.stringify({ text }),
    headers: { 'Content-Type': 'application/json' },
  });
  const { embedding } = await res.json();
  return embedding;
};
```

---

## Embedding cache

Avoid re-embedding identical text within a process. `withEmbeddingCache` wraps an
`EmbeddingFn` with an in-process LRU cache — it keeps up to `maxSize` most-recent
embeddings in memory (no external store, no TTL). The cache is cleared on restart.

```
import { withEmbeddingCache } from 'personaforge';

// Second arg is the max number of cached entries (default: 500).
const cachedEmbed = withEmbeddingCache(myEmbeddingFn, 500);
```

---

## Where to go next

- [Memory](./memory) — retain facts across conversations.
- [Eval](./eval) — measure RAG quality with `RAG_CRITERIA`.
- [Example 05: RAG](../examples/05-rag) — full ingestion-to-answer example.


# Guide: reasoning

# Reasoning

The reasoning module gives agents explicit, inspectable multi-step thinking. Use `ReasoningManager` for Chain-of-Thought (CoT) and `TreeOfThoughtEngine` for Tree-of-Thought (ToT) reasoning.

> **Experimental.** This subsystem is newer and not yet semver-stable — its CoT/ToT engines and config shapes may change in a minor release.

```
import {
  ReasoningManager,
  TreeOfThoughtEngine,
  ReasoningEventType,
  NextAction,
} from 'personaforge';
```

---

## Chain-of-Thought with `ReasoningManager`

```
import { createAgent, OpenAIProvider } from 'personaforge';
import { ReasoningManager, ReasoningEventType } from 'personaforge';

const llm = new OpenAIProvider({ apiKey: process.env.OPENAI_API_KEY!, model: 'gpt-4o' });

const manager = new ReasoningManager({
  generate: async (messages) => llm.generate(messages),
  minSteps: 2,       // minimum reasoning steps before final answer
  maxSteps: 10,      // maximum steps before forced termination
  // systemPrompt: '...',  // override the built-in CoT prompt
});

const messages = [
  { role: 'user' as const, content: 'A farmer has 17 sheep. All but 9 die. How many are left?' },
];

for await (const event of manager.reason(messages)) {
  switch (event.eventType) {
    case ReasoningEventType.STARTED:
      console.log('Reasoning started');
      break;
    case ReasoningEventType.STEP:
      console.log(`Step: ${event.step?.title}`);
      console.log(`  Action:     ${event.step?.action}`);
      console.log(`  Result:     ${event.step?.result}`);
      console.log(`  Confidence: ${event.step?.confidence}`);
      console.log(`  Next:       ${event.step?.nextAction}`);
      break;
    case ReasoningEventType.DELTA:
      process.stdout.write(event.contentDelta ?? '');
      break;
    case ReasoningEventType.COMPLETED:
      console.log('\nFinal answer:', event.steps?.at(-1)?.result);
      console.log('Total steps:', event.steps?.length);
      break;
    case ReasoningEventType.ERROR:
      console.error('Reasoning error:', event.error);
      break;
  }
}
```

Prefer a single result over the event stream? `await manager.run(messages)` collects every step and returns a `ReasoningResult` (`{ steps, success, error? }`). The default CoT system prompt is exported as `REASONING_SYSTEM_PROMPT` if you want to extend rather than replace it.

---

## `ReasoningStep` fields

Each step emitted by `ReasoningManager` contains:

| Field | Type | Description |
|---|---|---|
| `title` | `string` | Short title summarising this step |
| `action` | `string` | What the agent plans to do ("I will...") |
| `result` | `string` | What happened after executing the action |
| `reasoning` | `string` | Rationale and assumptions |
| `nextAction` | `NextAction` | `continue` \| `validate` \| `final_answer` \| `reset` |
| `confidence` | `number` | 0.0–1.0 confidence score |

---

## Attach reasoning to an agent

Pass a `ReasoningManager` to `createAgent()` for automatic CoT on every run:

```
import { createAgent } from 'personaforge';
import { ReasoningManager } from 'personaforge';

const agent = createAgent({
  name: 'reasoning-agent',
  instructions: 'Solve problems step by step.',
  model: 'gpt-4o',
  apiKey: process.env.OPENAI_API_KEY!,
  reasoning: new ReasoningManager({
    generate: async (msgs) => llm.generate(msgs),
    maxSteps: 8,
  }),
  // Stream reasoning steps in the result
  streamReasoningSteps: true,
});

const result = await agent.run('What is the optimal strategy for the knapsack problem?');
console.log(result.reasoningSteps);  // full array of ReasoningStep
console.log(result.text);            // final answer
```

---

## Tree-of-Thought

`TreeOfThoughtEngine` explores multiple reasoning branches and picks the best path:

```
import { TreeOfThoughtEngine } from 'personaforge';

const tot = new TreeOfThoughtEngine({
  generate: async (messages) => llm.generate(messages),
  beamWidth: 3,          // branches to expand and keep per BFS level
  maxDepth: 4,           // max tree depth
  // Optional separate evaluator. Receives a messages array and returns a score
  // as a string — either a plain float ('0.0'–'1.0') or JSON `{ "score": 0.8 }`.
  // Defaults to `generate` when omitted.
  evaluate: async (messages) => llm.generate(messages),
});

// solve(goal, context?) runs beam search and returns the best branch.
const result = await tot.solve(
  'What is the optimal strategy for the knapsack problem?',
);

console.log(result.bestThought); // best final thought text
console.log(result.score);       // cumulative score of the winning branch (0–1)
console.log(result.nodes);       // full beam tree (TotNode[], for inspection)
console.log(result.depth);       // number of BFS levels traversed
```

---

## Where to go next

- [Planner](./planner) — decompose a goal into an explicit execution plan.
- [Workflows](./workflows) — graph-based execution with explicit branching.
- [Example 19: Reasoning agent](../examples/19-reasoning) — full CoT example.


# Guide: reasoning-tools

# Reasoning Tools

Reasoning-as-tools makes step-by-step thinking an explicit tool call rather than free-form chain-of-thought. This lifts non-reasoning models by giving them a structured scratchpad they can write to and query.

```
import {
  ReasoningScratchpad, createReasoningTools,
} from 'personaforge/reasoning';
```

---

## Quick start

```
import { agent } from 'personaforge';

const scratchpad = new ReasoningScratchpad();
const { think, analyze } = createReasoningTools(scratchpad);

const researcher = agent({
  name: 'researcher',
  model: 'gpt-4o-mini',
  instructions: 'Use the think tool to plan before acting, and analyze to review your reasoning.',
  tools: [think, analyze /* , ...domainTools */],
});

await researcher.run('Compare three database options for our workload.');

// Inspect what the agent reasoned about
console.log(scratchpad.render());
```

---

## The tools

### `think(title, thought)`

Records a reasoning step. Returns the step ID and running total.

```
await think.execute({
  title: 'Plan',
  thought: 'Break the comparison into cost, latency, and operational overhead.',
});
// { stepId: 1, totalSteps: 1 }
```

### `analyze(query?)`

Reviews recorded steps. With no query it returns everything; with a query it substring-filters titles and thoughts.

```
await analyze.execute({ query: 'cost' });
// { steps: [ ...matching steps ] }
```

---

## The scratchpad

`ReasoningScratchpad` is a per-run store you own and inspect:

```
const pad = new ReasoningScratchpad();

pad.add('Plan', 'decompose the task');
pad.count();          // 2
pad.search('plan');   // matching steps
pad.render();         // prompt-injectable text summary
pad.clear();          // reset between runs
```

Create a fresh scratchpad per run to avoid cross-run leakage.

---

## Prompt injection pattern

Render the scratchpad into a later prompt to give the model its own prior reasoning as context:

```
const priorReasoning = scratchpad.render();
const followup = await agent.run(
  `Previous reasoning:\n${priorReasoning}\n\nNow produce the final recommendation.`,
);
```

---

## Related pages

- [Reasoning (CoT / ToT)](/guide/reasoning) — the ReasoningManager and Tree-of-Thought.
- [Planner](/guide/planner) — task decomposition.


# Guide: registry

# Agent Registry

`personaforge/registry` provides a first-class agent registry for registration, discovery, and delegation. Register runnable agents once, resolve them by name, search by description/tags, and expose any (or all) registered agents to a parent orchestrator as tools in a single call.

```
import { createAgentRegistry } from 'personaforge/registry';
```

---

## Quick start

```
import { createAgentRegistry } from 'personaforge/registry';
import { agent } from 'personaforge';

const registry = createAgentRegistry();

registry.register({
  name: 'translator',
  description: 'Translate text into another language',
  tags: ['language', 'nlp'],
  agent: agent('You translate text.'),
});

registry.register({
  name: 'summarizer',
  description: 'Summarize long documents',
  tags: ['nlp', 'summarization'],
  agent: agent('You summarize documents.'),
});

// O(1) lookup by name
const t = registry.get('translator');              // AgentRecord

// Case-insensitive discovery across name/description/tags
const matches = registry.search('translate');      // → [translator record]
const scoped = registry.search('nlp');             // → [translator, summarizer]

// Delegate to any agent as an LLM tool
const translateTool = registry.asTool('translator');

// Or expose every registered agent as a delegation toolkit:
const tools = registry.toTools();                  // one tool per agent
```

---

## Registration metadata

`AgentRecord` carries discovery + marketplace metadata:

```
registry.register({
  name: 'finance-report',
  description: 'Generate a weekly finance report',
  tags: ['finance', 'reporting'],
  version: '1.2.0',
  author: 'data-team',
  metadata: { owner: 'finance@example.com', sla: 'p1' },
  agent: financeAgent,
});
```

---

## Managing agents

```
registry.size        // number of agents
registry.names()     // registered names, in order
registry.list()      // Array<{ name, registration }>
registry.has('x')    // boolean
registry.resolve('x') // the raw runnable agent
registry.remove('x') // boolean (true if removed)
registry.clear()     // remove all

// Batch registration (throws on duplicates):
registry.registerMany([
  { name: 'a', agent: agentA },
  { name: 'b', agent: agentB },
]);
```

`register()` throws if the name is empty or already registered.

---

## Delegation — agents as tools

Each registered agent can be exposed to a parent LLM as a function-calling tool. This is the "agent registry → orchestrator" pattern:

```
// Single agent as a tool with overrides:
const tool = registry.asTool('translator', {
  category: 'language',
  tags: ['delegate'],
});

// All agents at once:
const allTools = registry.toTools();
// Consistent category across all exports:
const categorized = registry.toToolsWithCategory('language');
```

Pass `allTools` directly to an orchestrator agent's `tools` and the parent LLM can delegate work to any registered specialist.

---

## Related pages

- [Orchestration](./orchestration) — supervisors and multi-agent systems that consume registry tools.
- [Agents](./agents) — `agent()` / `createAgent()` reference.
- [Tools](./tools) — `agentAsTool` / tool helpers.


# Guide: retrieval-advanced

# Advanced Retrieval

The `personaforge/knowledge` module ships everything needed to close the retrieval quality gap that separates a demo RAG pipeline from a production one: real chunking, dense-plus-sparse hybrid search, reranking, and composable retriever primitives.

```
import {
  // Chunking
  RecursiveCharacterSplitter, MarkdownSplitter, SemanticSplitter,
  // Keyword + hybrid
  BM25Index, HybridRetriever, rrfFuse,
  // Rerankers
  CohereReranker, JinaReranker, LLMReranker,
  // Retriever primitives
  MultiQueryRetriever, ContextualCompressionRetriever, LLMCompressor,
  ParentDocumentRetriever, SelfQueryRetriever, TimeWeightedRetriever,
} from 'personaforge/knowledge';
```

Everything below is zero-dependency by default. External services (Cohere, Jina) are opt-in.

---

## Text splitters

Splitters turn a raw document into overlapping, size-bounded `Chunk`s before embedding. Chunking quality is one of the biggest levers on final answer quality.

### `RecursiveCharacterSplitter`

The default. Tries a priority list of separators (paragraph → line → sentence → word → char), recursing into any fragment still larger than `chunkSize`. Semantic boundaries are preferred.

```
const splitter = new RecursiveCharacterSplitter({
  chunkSize: 800,     // characters (or tokens if lengthFn supplied)
  chunkOverlap: 100,  // characters kept between adjacent chunks for context
});

const chunks = splitter.splitText(longDocument);
// [{ content: '...', metadata: {}, chunkIndex: 0 }, ...]
```

For token-accurate chunking pass a counter:

```
new RecursiveCharacterSplitter({
  chunkSize: 1000,
  lengthFn: (t) => Math.ceil(t.length / 4),  // rough tokens-per-char heuristic
});
```

### `MarkdownSplitter`

Cuts on Markdown headings first (each chunk keeps its heading as context), then falls back to recursive splitting inside oversized sections.

```
const chunks = new MarkdownSplitter({ chunkSize: 1200 }).splitText(readme);
// Each chunk's metadata.heading is the enclosing section heading.
```

### `SemanticSplitter`

Groups adjacent sentences while their embedding stays similar, and cuts a new chunk when the cosine similarity drops below `breakThreshold`. Requires an embedding function; use it when your source is prose without heading structure.

```
const splitter = new SemanticSplitter({
  embed: (text) => embedder.embed(text),
  breakThreshold: 0.5,  // lower = more permissive grouping
  maxChars: 2000,       // hard cap so a semantic run cannot overflow the context window
});
const chunks = await splitter.splitTextAsync(article);
```

---

## Keyword retrieval: `BM25Index`

Vector cosine similarity does not always find exact-term matches (order IDs, error codes, product names). `BM25Index` is a zero-dependency Okapi BM25 keyword index that complements dense retrieval.

```
const bm25 = new BM25Index();
bm25.add(documents);

const hits = bm25.search('reset password link', 10);
// SearchResult[] normalised to 0..1 for comparability with cosine scores
```

Deliberately in-memory. For >100k documents pair with Elasticsearch or Meilisearch through the same `SearchResult` interface.

---

## Hybrid retrieval (dense + sparse) with RRF

Dense scores and BM25 scores live on different scales, so score-level averaging is unreliable. `HybridRetriever` fuses **rankings**, not scores, using Reciprocal-Rank Fusion:

$$
\text{score}(d) = \sum_{i} \frac{1}{k + \text{rank}_i(d)}
$$

with `k = 60` (Cormack et al. 2009) by default.

```
const hybrid = new HybridRetriever({
  dense: vectorStore,   // your existing VectorStore
  sparse: bm25,
  k: 60,                // RRF constant; higher = tail contributions matter more
  candidateK: 20,       // pull this many from each list before fusion
});

const results = await hybrid.search('reset password link', 10);
```

Need to fuse more than two lists (e.g. dense + BM25 + a third source)? Use `rrfFuse` directly:

```
const fused = rrfFuse([denseHits, bm25Hits, externalHits], 60).slice(0, 10);
```

---

## Rerankers

A reranker takes the top-N candidates from a cheap retriever and re-scores them with a cross-encoder that sees the `(query, document)` pair together. It is the single largest quality lever above vanilla cosine similarity.

### Cohere

```
const reranker = new CohereReranker({
  apiKey: process.env.COHERE_API_KEY,
  model: 'rerank-english-v3.0',
});
const reranked = await reranker.rerank(query, candidates, 5);
```

### Jina

```
const reranker = new JinaReranker({
  apiKey: process.env.JINA_API_KEY,
  model: 'jina-reranker-v2-base-multilingual',
});
```

### LLM-as-reranker

Portable to any chat model (Ollama, Anthropic, Google, OpenAI) without a dedicated rerank endpoint. Runs candidate pairs in parallel with a bounded worker pool.

```
const reranker = new LLMReranker({
  generate: (prompt) => llm.generate(prompt),
  concurrency: 4,
});
```

**Suggested pipeline**: retrieve 20 candidates with hybrid → rerank to top 5 → pass to the LLM.

```
const candidates = await hybrid.search(query, 20);
const top5 = await reranker.rerank(query, candidates, 5);
```

---

## Retriever primitives

Each primitive implements the same `Retriever` interface (`search(query, topK)`), so they compose. Wrap a base retriever in one primitive to add a capability; wrap again to stack.

### `MultiQueryRetriever`

Asks an LLM to generate `queryCount` variants of the user query, runs each through the base retriever, unions and dedupes results. Fixes recall on ambiguous or poorly-phrased queries.

```
const retriever = new MultiQueryRetriever({
  base: hybrid,
  generate: (prompt) => llm.generate(prompt),
  queryCount: 3,
});
```

### `ContextualCompressionRetriever`

Passes each retrieved chunk through a compressor that extracts only the sentences relevant to the query. Chunks that compress to empty are filtered out.

```
const retriever = new ContextualCompressionRetriever({
  base: hybrid,
  compressor: new LLMCompressor((prompt) => llm.generate(prompt)),
});
```

### `ParentDocumentRetriever`

Stores small child chunks for **precise retrieval** but returns their **full parent document** for richer LLM context. Fixes the common tradeoff between chunk granularity and answer completeness.

```
const retriever = new ParentDocumentRetriever({ childStore: vectorStore });

await retriever.addDocuments(parents, (parent) => {
  return splitter.splitText(parent.content).map((chunk) => ({
    id: crypto.randomUUID(),
    content: chunk.content,
    metadata: { _parentId: parent.id },
  }));
});
```

### `SelfQueryRetriever`

Uses an LLM to extract metadata filters from the user query, then applies them to the base retriever's candidates. Handles queries like *"papers by Karpathy about optimizers"*.

```
const retriever = new SelfQueryRetriever({
  base: hybrid,
  generate: (prompt) => llm.generate(prompt),
  fieldDescriptions: {
    author: 'Name of the paper author',
    year: 'Publication year',
    topic: 'Research topic',
  },
});
```

### `TimeWeightedRetriever`

Decays relevance of older documents. Score = base_similarity × `decayFactor^(ageInHours)`. Documents must include `metadata.createdAt` as a millisecond timestamp.

```
const retriever = new TimeWeightedRetriever({
  base: hybrid,
  decayFactor: 0.99,   // per-hour multiplier; closer to 1 = slower decay
});
```

---

## Composing the full pipeline

The primitives are designed to stack. A production pipeline typically looks like:

```
const retriever = new ContextualCompressionRetriever({
  base: new MultiQueryRetriever({
    base: new HybridRetriever({ dense: vectorStore, sparse: bm25 }),
    generate: (p) => llm.generate(p),
  }),
  compressor: new LLMCompressor((p) => llm.generate(p)),
});

// Optional rerank pass on top:
const raw = await retriever.search(query, 20);
const top5 = await reranker.rerank(query, raw, 5);
```

Each layer is optional. Start with `HybridRetriever` alone and add wrappers only when eval shows they help.

---

## Testing

Every splitter, retriever, and reranker has unit test coverage in `tests/retrieval.test.ts` (13 tests, all green). The `LLMReranker` and LLM-based primitives use injectable `generate` functions, so tests never call a real API.

---

## Related pages

- [RAG / Knowledge](/guide/rag) — the base `KnowledgeEngine` and vector stores.
- [Loaders Reference](/guide/loaders) — markdown, HTML, JSON, DOCX, sitemap, GitHub, S3.
- [Evaluation](/guide/eval) — measure retrieval quality with hit rate and MRR.


# Guide: runnable

# Runnable / LCEL

`Runnable<I, O>` is the universal unit of composition in personaforge. Everything that takes input and produces output — a prompt template, an LLM call, a parser, a retriever — can be a `Runnable` and composed with `.pipe()`.

```
import {
  Runnable, RunnableLambda, RunnableSequence,
  RunnableParallel, RunnablePassthrough,
} from 'personaforge/runnable';
```

---

## Quick start

```
const upper = new RunnableLambda<string, string>((s) => s.toUpperCase());
const exclaim = new RunnableLambda<string, string>((s) => s + '!');

const chain = upper.pipe(exclaim);
await chain.invoke('hello'); // 'HELLO!'
```

Chains flatten automatically. `a.pipe(b).pipe(c)` creates a three-step `RunnableSequence`, not a nested structure.

---

## Core methods

| Method | What it does |
|---|---|
| `.invoke(input)` | Execute the chain, return a single output |
| `.batch(inputs, { concurrency })` | Run N inputs in parallel |
| `.stream(input)` | Return an `AsyncGenerator` of incremental outputs |
| `.pipe(next)` | Chain two Runnables |
| `.map(fn)` | Transform the output |
| `.bind(kwargs)` | Partial-apply input fields |
| `.withRetry({ maxRetries, delayMs })` | Retry on error with exponential backoff |
| `.withFallbacks([alt1, alt2])` | Try alternatives on failure |
| `.withConfig({ tags, metadata })` | Inject config defaults |
| `.assign({ key: Runnable })` | Fan-out, merge results |

---

## Batching

```
const results = await upper.batch(['a', 'b', 'c'], { concurrency: 2 });
// ['A', 'B', 'C']
```

Concurrency is bounded by a worker pool; no Promise.all explosion.

---

## Streaming

The default `.stream()` yields a single result. Subclasses override for true token-level streaming:

```
for await (const chunk of chain.stream('input')) {
  process.stdout.write(chunk);
}
```

A `RunnableSequence` streams: it eagerly invokes all steps except the last, then yields tokens from the final step.

---

## Retry and fallback

```
const safe = llm
  .withRetry({ maxRetries: 3, delayMs: 200 })
  .withFallbacks([backupLlm]);
```

- `withRetry` uses exponential backoff: `delayMs * 2^attempt`.
- `withFallbacks` tries alternatives in order; throws only when all are exhausted.
- Both compose: retry wraps the primary, fallback wraps the retried primary.

---

## Assign (parallel fan-out)

Run named branches in parallel, merge results into the input object:

```
const base = new RunnablePassthrough<{ q: string }>();
const chain = base.assign({
  len: new RunnableLambda((x: { q: string }) => x.q.length),
  up: new RunnableLambda((x: { q: string }) => x.q.toUpperCase()),
});

await chain.invoke({ q: 'hi' });
// { q: 'hi', len: 2, up: 'HI' }
```

---

## `RunnableParallel`

Run named branches without a base passthrough:

```
const par = new RunnableParallel({
  a: new RunnableLambda<number, number>((n) => n + 1),
  b: new RunnableLambda<number, number>((n) => n * 2),
});

await par.invoke(10); // { a: 11, b: 20 }
```

---

## Building chains with parsers

Every parser in `personaforge/parsers` extends `Runnable`, so they compose:

```
import { JsonOutputParser } from 'personaforge/parsers';

const chain = llm.pipe(new JsonOutputParser<{ answer: string }>());
const result = await chain.invoke('What is 2+2?');
// { answer: '4' }
```

---

## Related pages

- [Output Parsers](/guide/output-parsers) — String, JSON, CSV, fixing, retry parsers.
- [Model Fallbacks](/guide/model-fallbacks) — provider-level resilience.


# Guide: scheduler

# Scheduler

The scheduler module lets you register cron-based jobs that invoke agents or custom handlers automatically. Use `ScheduleManager` for in-process scheduling and `DbScheduleStore` for durable schedule persistence.

```
import {
  ScheduleManager,
  InMemoryScheduleStore,
  InMemoryScheduleRunStore,
  DbScheduleStore,
  validateCronExpr,
  computeNextRun,
} from 'personaforge/scheduler';
```

---

## Quick start

```
import { createAgent } from 'personaforge';
import { ScheduleManager } from 'personaforge/scheduler';

const agent = createAgent({
  name: 'daily-reporter',
  instructions: 'Generate a concise daily business summary.',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
});

const scheduler = new ScheduleManager();

// Register a handler function by key
scheduler.register('daily-report', async () => {
  const result = await agent.run('Generate the daily business summary for today.');
  await saveReport(result.text);
  console.log('Daily report saved.');
});

// Create a schedule — create() returns the new schedule's id (a string).
const id = await scheduler.create({
  name: 'Daily Business Report',
  cronExpr: '0 8 * * *',        // 08:00 every day (evaluated in UTC)
  endpoint: 'daily-report',     // matches the registered handler key
  enabled: true,
  maxRetries: 3,
  retryDelaySeconds: 300,
});

// Start the schedule runner (poll-based)
scheduler.start();

// Later, stop cleanly
process.on('SIGTERM', () => scheduler.stop());
```

---

## Cron expression examples

```
import { validateCronExpr, computeNextRun } from 'personaforge';

// Standard 5-field cron (min hour dom mon dow)
validateCronExpr('*/5 * * * *');    // every 5 minutes — valid
validateCronExpr('0 8 * * 1-5');    // weekdays at 08:00 — valid

// Compute next run time. Day-of-week is numeric (0–6, 0 = Sunday); named days
// like MON are not supported. Returns a Date, or null if no match is found.
const next = computeNextRun('0 9 * * 1');   // next Monday 09:00 UTC
console.log(next?.toISOString());
```

> **Scheduling is UTC-only.** All cron fields are evaluated in UTC. `computeNextRun` ignores its `timezone` argument (and the `timezone` field on a schedule), so IANA zones like `America/New_York` do not shift the run time — convert to UTC yourself when building the expression.

Common patterns:

| Expression | Description |
|---|---|
| `* * * * *` | Every minute |
| `*/5 * * * *` | Every 5 minutes |
| `0 * * * *` | Every hour |
| `0 8 * * *` | Daily at 08:00 UTC |
| `0 8 * * 1-5` | Weekdays at 08:00 |
| `0 0 1 * *` | First day of every month |
| `0 0 * * 0` | Every Sunday at midnight |

---

## Schedule management

```
const scheduler = new ScheduleManager();

// Create
const id = await scheduler.create({
  name: 'Health check',
  cronExpr: '*/15 * * * *',
  endpoint: 'health-check',
  enabled: true,
});

// List all schedules
const all = await scheduler.list();

// List only enabled — pass the boolean positionally
const enabled = await scheduler.list(true);

// Get one
const schedule = await scheduler.get(id);

// Update
await scheduler.update(id, { cronExpr: '*/30 * * * *', enabled: false });

// Enable / disable
await scheduler.enable(id);
await scheduler.disable(id);

// Delete
await scheduler.delete(id);
```

---

## Run history

```
// Get last 20 runs for a schedule
const runs = await scheduler.getRuns(id, 20);

for (const run of runs) {
  console.log(run.status, run.triggeredAt, run.completedAt, run.error);
}
// { status: 'success', triggeredAt: '2026-05-11T08:00:00Z', completedAt: '...', error: null }
```

---

## Durable schedule store (survives restarts)

```
import { DbScheduleStore } from 'personaforge/scheduler';
import { SqliteAgentDb } from 'personaforge/db';

const db = new SqliteAgentDb({ path: './agent.db' });
const scheduleStore = new DbScheduleStore(db);

const scheduler = new ScheduleManager({ store: scheduleStore });
```

---

## Manual trigger (testing and backfill)

```
// Fire a schedule immediately without waiting for the cron
await scheduler.trigger(id);
```

---

## Let an agent manage schedules

`SchedulerTools` wraps a `ScheduleManager` as agent-callable tools, so an agent can create, list, update, and delete its own schedules from chat. Register the array returned by `getTools()`:

```
import { createAgent } from 'personaforge';
import { SchedulerTools, ScheduleManager } from 'personaforge/scheduler';

const manager = new ScheduleManager();

const agent = createAgent({
  name: 'SchedulerAgent',
  instructions: 'Create and manage the user\'s reminders and reports.',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
  tools: new SchedulerTools({
    manager,
    defaultEndpoint: '/agents/assistant/run',
  }).getTools(),
});
```

---

## Where to go next

- [Background Queues](./background-queues) — process jobs from a queue rather than a cron.
- [Production](./production) — graceful shutdown and health checks for scheduled services.
- [Example 20: Scheduled agents](../examples/20-scheduled-agents) — full scheduled agent example.


# Guide: secret-manager

# Secret Manager

`createSecretManager()` gives you a unified interface to fetch secrets from any cloud provider. Swap backends at deploy time without changing application code.

```
import { createSecretManager } from 'personaforge/config';
```

---

## Quick start

```
import { createSecretManager } from 'personaforge/config';

// Reads from AWS Secrets Manager
const secrets = createSecretManager({ provider: 'aws', region: 'us-east-1' });

const apiKey = await secrets.getSecret('openai-api-key');
const dbUrl  = await secrets.getSecret('database-url');

// Use with agent
const agent = createAgent({
  name: 'my-agent',
  instructions: '...',
  model: 'gpt-4o-mini',
  apiKey,
});
```

---

## Backends

### AWS Secrets Manager

```
const secrets = createSecretManager({
  provider: 'aws',
  region: 'us-east-1',
  // credentials optional — defaults to IAM role / env vars / ~/.aws
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
  },
});
```

### Azure Key Vault

```
const secrets = createSecretManager({
  provider: 'azure',
  vaultUrl: 'https://myvault.vault.azure.net',
  // credentials optional — defaults to DefaultAzureCredential (managed identity, CLI, env vars)
  credentials: {
    tenantId: process.env.AZURE_TENANT_ID!,
    clientId: process.env.AZURE_CLIENT_ID!,
    clientSecret: process.env.AZURE_CLIENT_SECRET!,
  },
});
```

### HashiCorp Vault

```
const secrets = createSecretManager({
  provider: 'vault',
  endpoint: process.env.VAULT_ADDR,   // default: http://127.0.0.1:8200
  token: process.env.VAULT_TOKEN!,
  mount: 'secret',                    // KV mount path (default: 'secret')
});
```

### GCP Secret Manager

```
const secrets = createSecretManager({
  provider: 'gcp',
  projectId: process.env.GOOGLE_CLOUD_PROJECT,  // or GCLOUD_PROJECT
});
```

### Environment (development / CI)

Reads from `process.env` — useful in development when you don't have a cloud secret store:

```
const secrets = createSecretManager({ provider: 'env' });

// Reads process.env.OPENAI_API_KEY
const apiKey = await secrets.getSecret('OPENAI_API_KEY');
```

---

## `getSecret(name, version?)`

```
// Latest version (default)
const value = await secrets.getSecret('my-api-key');

// Specific version (AWS ARN, Azure version ID, Vault version, GCP version)
const value = await secrets.getSecret('my-api-key', '2');
```

Throws if the secret doesn't exist or access is denied.

---

## Live secret watching

Poll for changes and react without restarting:

```
const watcher = secrets.watch(
  'openai-api-key',
  async (newValue) => {
    console.log('API key rotated — updating client');
    openaiClient.apiKey = newValue;
  },
  300_000,  // poll interval ms (default: 5 minutes)
);

// Stop watching when done
watcher.stop();
```

---

## `SecretManagerAdapter` interface

Implement this to add any backend:

```
interface SecretManagerAdapter {
  getSecret(name: string, version?: string): Promise<string>;
  watch(
    name: string,
    callback: (newValue: string) => void | Promise<void>,
    intervalMs?: number,
  ): { stop(): void };
}
```

---

## Where to go next

- [Production](./production) — circuit breakers, rate limiters, audit stores.
- [Custom adapter](./custom-adapter) — plug in your own infrastructure bindings.


# Guide: session

# Sessions

Sessions let an agent remember the thread of a conversation across multiple `run()` calls. Pass a `sessionId` and a `sessionStore` and the framework automatically loads and appends message history.

```
import {
  createInMemoryStore,
  createSqliteStore,
  createRedisStore,
  DbSessionStore,
  FallbackSessionStore,
  createFallbackSessionStore,
} from 'personaforge';
```

---

## Quick start

```
import { createAgent, createInMemoryStore } from 'personaforge';

const sessionStore = createInMemoryStore();

const agent = createAgent({
  name: 'chat',
  instructions: 'You are a helpful assistant.',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
  sessionStore,
});

// Turn 1
await agent.run('My name is Alice.', { sessionId: 'session-1' });

// Turn 2 — agent remembers the name
const result = await agent.run('What is my name?', { sessionId: 'session-1' });
console.log(result.text); // "Your name is Alice."
```

---

## Session stores

### InMemorySessionStore (development)

Fast, zero-config. Sessions are lost on restart.

```
import { createInMemoryStore } from 'personaforge';

const store = createInMemoryStore({
  retentionDays: 7,  // evict sessions older than 7 days
});

// Direct store access
const session = await store.create({ agentId: 'chat', userId: 'user-1' });
await store.appendMessage(session.id, { role: 'user', content: 'Hello' });
const messages = await store.getMessages(session.id);
await store.delete(session.id);

// Prune expired sessions manually
const deleted = store.pruneExpired();
console.log(`Pruned ${deleted} sessions`);
```

### SQLite (single-node production)

Durable sessions on disk — no external server needed:

```
import { createSqliteStore } from 'personaforge';

const store = createSqliteStore({
  path: './data/sessions.db',  // defaults to ':memory:' if omitted
});
```

### Redis (distributed, horizontally scalable)

```
import { createRedisStore } from 'personaforge';

const store = createRedisStore({
  redis: process.env.REDIS_URL!,   // 'redis://localhost:6379' or ioredis options
  keyPrefix: 'myapp:session:',     // namespacing
  ttlSeconds: 86_400,              // 24-hour TTL
});
```

### DbSessionStore (any AgentDb backend)

```
import { DbSessionStore } from 'personaforge';
import { SqliteAgentDb, PostgresAgentDb } from 'personaforge';

// SQLite
const db = new SqliteAgentDb({ path: './agent.db' });
const store = new DbSessionStore(db);

// Postgres
const pgDb = new PostgresAgentDb({ connectionString: process.env.DATABASE_URL! });
const pgStore = new DbSessionStore(pgDb);
```

### FallbackSessionStore (resilient)

Use a primary store (Redis/Postgres) with automatic in-memory fallback if it goes down:

```
import { createFallbackSessionStore, createRedisStore } from 'personaforge';

const store = createFallbackSessionStore(
  createRedisStore({ redis: process.env.REDIS_URL! }),
  {
    fallback: 'in-memory',
    onFallback: (err) => logger.warn('Session store degraded, using fallback', err),
    onRecover: () => logger.info('Session store recovered'),
  },
);

// Check degraded state
if (store.isDegraded()) {
  metrics.increment('session.store.degraded');
}

// Force re-check primary (call after your infra team fixes the issue)
store.recover();
```

---

## SessionStore interface

```
interface SessionStore {
  get(id: string): Promise<SessionData | undefined>;
  create(data: { agentId: string; userId?: string; messages?: SessionMessage[] } | string): Promise<SessionData>;
  update(id: string, data: { messages: SessionMessage[] }): Promise<void>;
  getMessages(id: string): Promise<SessionMessage[]>;
  appendMessage(id: string, message: SessionMessage): Promise<void>;
  delete(id: string): Promise<void>;
}
```

---

## Session IDs from your own system

You control the session ID — use any string that makes sense in your application:

```
// From an HTTP request
const sessionId = req.headers['x-session-id'] ?? crypto.randomUUID();

// From a database row
const sessionId = `order-${orderId}`;

// From a user ID for a single long-running conversation
const sessionId = `user-${userId}-chat`;

const result = await agent.run(prompt, { sessionId, userId });
```

---

## Read conversation history

```
const messages = await sessionStore.getMessages('session-1');
// [{ role: 'user', content: '...' }, { role: 'assistant', content: '...' }, ...]

// Get the full session object (metadata + messages)
const session = await sessionStore.get('session-1');
console.log(session?.createdAt, session?.updatedAt);
```

---

## Where to go next

- [Memory](./memory) — retain facts beyond ordinary conversation flow.
- [Storage](./storage) — durable application state around the agent.
- [HITL](./hitl) — pause a session to wait for human approval.


# Guide: skills

# Skills

Skills are capability bundles — a named set of instructions and tools that can be applied to any agent. They let you package reusable behaviours once and share them across agents without copying prompt logic or tool wiring.

```
import {
  webResearchSkill,
  codeReviewerSkill,
  pdfSummarizerSkill,
} from 'personaforge/skills';
```

---

## Attach built-in skills

```
import { defineAgent } from 'personaforge';
import { webResearchSkill, codeReviewerSkill } from 'personaforge/skills';

const agent = defineAgent('research-reviewer')
  .instructions('Help users research topics and review code.')
  .model('openai:gpt-4o-mini')
  .skills([webResearchSkill, codeReviewerSkill])
  .build();
```

---

## Built-in skills

### `webResearchSkill`

Gives the agent a `fetch_page` tool that retrieves the visible text from any HTTPS URL:

```
import { webResearchSkill } from 'personaforge/skills';

const agent = defineAgent('researcher')
  .instructions('Research questions using the web.')
  .model('openai:gpt-4o-mini')
  .skills([webResearchSkill])
  .build();

const result = await agent.run('What is the latest version of Node.js?');
// Agent will call fetch_page('https://nodejs.org/en/download/releases') internally
```

### `codeReviewerSkill`

Gives the agent a `read_source_file` tool that loads source files from disk:

```
import { codeReviewerSkill } from 'personaforge/skills';

const agent = defineAgent('code-reviewer')
  .instructions('Review source code files for bugs and security issues.')
  .model('openai:gpt-4o-mini')
  .skills([codeReviewerSkill])
  .build();

const result = await agent.run('Review src/runtime/jwt-rbac.ts for security vulnerabilities.');
// Supported extensions: .ts, .js, .py, .go, .rs, .java, .sql, .yaml, .md and more
```

### `pdfSummarizerSkill`

Gives the agent the ability to load and summarise PDF documents:

```
import { pdfSummarizerSkill } from 'personaforge/skills';

const agent = defineAgent('doc-summarizer')
  .instructions('Summarise documents and answer questions about their content.')
  .model('openai:gpt-4o')
  .skills([pdfSummarizerSkill])
  .build();

const result = await agent.run('Summarise the document at ./reports/Q4-2024.pdf');
```

---

## Author a custom skill

A `Skill` is a plain object with `id`, `name`, optional `instructions`, and an array of `tools`:

```
import type { Skill } from 'personaforge/contracts';
import { defineAgent, tool } from 'personaforge';
import { z } from 'zod';

const getWeather = tool({
  name: 'get_weather',
  description: 'Get the current weather for a city.',
  parameters: z.object({ city: z.string() }),
  execute: async ({ city }) => fetchWeather(city),
});

export const weatherSkill: Skill = {
  id: 'weather',
  name: 'weather',
  instructions: 'You can look up current weather for any city using get_weather.',
  tools: [getWeather],
};

// Use it
const agent = defineAgent('travel-agent')
  .instructions('Help users plan trips.')
  .model('openai:gpt-4o-mini')
  .skills([weatherSkill])
  .build();
```

---

## `Skill` interface

```
interface Skill {
  /** Unique identifier — kebab-case recommended (e.g. "web-research") */
  id: string;
  /** Human-readable display name */
  name: string;
  /** Short description of what this skill does */
  description?: string;
  /** Additional instructions appended to the agent system prompt */
  instructions?: string;
  /** Tools this skill provides */
  tools?: Tool[];
  /** Optional category tags for discovery and filtering */
  tags?: string[];
  /** Arbitrary metadata: version, author, homepage, etc. */
  metadata?: Record<string, unknown>;
}
```

---

## Where to go next

- [Custom tools](./custom-tools) — build the individual tools that skills expose.
- [Tool composition](./tool-composition) — wrap and extend skill tools.
- [Plugins](./plugins) — framework-level extension points.


# Guide: storage

# Storage

`createStorage()` gives you a typed, JSON-serializing key-value store on top of a pluggable `StorageAdapter`. Use it for durable run state, cached results, generated outputs, or any application data that should survive beyond a single agent run.

```
import { createStorage } from 'personaforge';
```

---

## Quick start

```
import { createStorage } from 'personaforge';

// In-memory (dev / testing)
const store = createStorage();

await store.set('user:alice', { name: 'Alice', plan: 'pro' });
const user = await store.get<{ name: string; plan: string }>('user:alice');
console.log(user?.plan); // 'pro'

await store.has('user:alice');      // true
await store.list('user:');          // ['user:alice']
await store.delete('user:alice');
```

---

## Drivers

### In-memory (default)

Data lives only for the process lifetime. No config needed — ideal for development and testing.

```
const store = createStorage();
// equivalent to:
const store2 = createStorage({ driver: 'memory' });
```

### File system

Persists JSON files under a base directory. Suitable for local single-node deployments.

```
const store = createStorage({
  driver: 'file',
  basePath: './data',     // directory created automatically
});

await store.set('config', { theme: 'dark' });
// writes to ./data/config.json
```

### Custom adapter (S3, Redis, R2, etc.)

Implement the `StorageAdapter` interface to use any backend:

```
import type { StorageAdapter } from 'personaforge';
import { S3Client, GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3';

class S3StorageAdapter implements StorageAdapter {
  private s3 = new S3Client({});
  private bucket = process.env.S3_BUCKET!;

  async get(key: string): Promise<string | undefined> {
    try {
      const res = await this.s3.send(new GetObjectCommand({ Bucket: this.bucket, Key: key }));
      return res.Body?.transformToString();
    } catch { return undefined; }
  }

  async set(key: string, value: string, ttl?: number): Promise<void> {
    await this.s3.send(new PutObjectCommand({ Bucket: this.bucket, Key: key, Body: value }));
  }

  async delete(key: string): Promise<void> { /* ... */ }
  async list(prefix?: string): Promise<string[]> { /* ... */ return []; }
  async has(key: string): Promise<boolean> { /* ... */ return false; }
}

const store = createStorage({ adapter: new S3StorageAdapter() });
```

---

## Storage interface

```
interface Storage {
  get<T>(key: string): Promise<T | undefined>;
  set<T>(key: string, value: T, ttl?: number): Promise<void>;  // ttl in seconds
  delete(key: string): Promise<void>;
  list(prefix?: string): Promise<string[]>;
  has(key: string): Promise<boolean>;
  clear(): Promise<void>;
  readonly adapter: StorageAdapter;
}
```

---

## Attach to agent

An agent can write its generated outputs to storage automatically:

```
const agent = createAgent({
  name: 'report-writer',
  instructions: '...',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
  storage: createStorage({ driver: 'file', basePath: './outputs' }),
});

const result = await agent.run('Write a quarterly summary.', { runId: 'q2-2026' });
console.log(result.storageKey); // key where the output was saved
```

---

## TTL (time-to-live)

```
// Expire after 1 hour
await store.set('cache:result-123', expensiveResult, 3_600);
```

---

## Namespacing pattern

Use key prefixes to keep different concerns separate:

```
// Separate namespaces using prefixes
await store.set('run:abc123:result', runResult);
await store.set('user:alice:prefs', prefs);
await store.set('cache:llm:sha256abc', cachedResponse);

const runKeys  = await store.list('run:');     // all run keys
const userKeys = await store.list('user:');    // all user keys
```

---

## Where to go next

- [Session](./session) — conversation continuity across turns.
- [Memory](./memory) — retain and recall facts inside agents.
- [Database](./database) — relational storage via AgentDb.


# Guide: stream-utils

# Stream Utilities

`personaforge/models` exports a set of helpers for `AsyncIterable<StreamDelta>` streams produced by model providers. These utilities are low-level building blocks for buffering, filtering, merging, and piping provider output to HTTP responses.

```
import {
  streamToText,
  streamToChunks,
  streamToSSE,
  streamWithBudget,
  streamTee,
  streamMap,
  streamFilter,
  streamMerge,
  streamToNodeCallback,
} from 'personaforge/models';
```

---

## `StreamDelta`

All stream utilities operate on `AsyncIterable<StreamDelta>`:

```
type StreamDelta =
  | { type: 'text'; text: string }
  | { type: 'tool_call'; id: string; name: string; argsDelta: string };
```

---

## `streamToText()`

Buffer an entire stream into one string. Tool-call deltas are ignored.

```
import { streamToText } from 'personaforge/models';

async function* demoStream() {
  yield { type: 'text', text: 'Petals fall softly ' } as const;
  yield { type: 'text', text: 'through the morning light.' } as const;
}

const text = await streamToText(demoStream());
console.log(text); // "Petals fall softly through the morning light."
```

---

## `streamToChunks()`

Collect just the text segments from a stream while preserving chunk boundaries.

```
import { streamToChunks } from 'personaforge/models';

async function* demoStream() {
  yield { type: 'text', text: 'alpha ' } as const;
  yield { type: 'tool_call', id: 'tool-1', name: 'search', argsDelta: '{"q":"beta"}' } as const;
  yield { type: 'text', text: 'gamma' } as const;
}

const chunks = await streamToChunks(demoStream());
console.log(chunks); // ['alpha ', 'gamma']
```

---

## `streamToSSE()`

Pipe a provider stream directly to a Node HTTP response as Server-Sent Events. The third argument is typed `StreamToSSEOptions` (also exported from `personaforge/models`).

```
import { createServer } from 'node:http';
import { streamToSSE } from 'personaforge/models';

async function* demoStream() {
  yield { type: 'text', text: 'Hello' } as const;
  yield { type: 'text', text: ' world' } as const;
}

const server = createServer(async (_req, res) => {
  await streamToSSE(demoStream(), res, { keepAliveMs: 0 });
});

server.listen(3000);
```

### SSE format

`streamToSSE()` writes events like:

```
event: text
data: {"text":"Hello"}

event: done
data: {}
```

---

## `streamWithBudget()`

Stop yielding deltas after an approximate token budget is reached. The options object is typed `StreamBudgetOptions` (also exported from `personaforge/models`).

```
import { streamWithBudget } from 'personaforge/models';

async function* demoStream() {
  yield { type: 'text', text: 'A short sentence.' } as const;
  yield { type: 'text', text: ' Another sentence that may exceed the budget.' } as const;
}

const budgetedStream = streamWithBudget(demoStream(), {
  maxTokens: 5,
  onBudgetExceeded: (used) => console.warn(`Budget exceeded at ~${used} tokens`),
});

for await (const chunk of budgetedStream) {
  if (chunk.type === 'text') process.stdout.write(chunk.text);
}
```

---

## `streamTee()`

Duplicate one stream into two independent consumers.

```
import { streamTee, streamToText } from 'personaforge/models';

async function* demoStream() {
  yield { type: 'text', text: 'Explain ' } as const;
  yield { type: 'text', text: 'quantum entanglement.' } as const;
}

const [displayStream, saveStream] = streamTee(demoStream());

void (async () => {
  for await (const chunk of displayStream) {
    if (chunk.type === 'text') process.stdout.write(chunk.text);
  }
})();

void (async () => {
  const fullText = await streamToText(saveStream);
  console.log(fullText);
})();
```

::: warning Backpressure
`streamTee` buffers chunks internally when one consumer is slower than the other. For very long streams, prefer `streamToText` on one branch and process the other in real-time.
:::

---

## `streamMap()`

Transform each delta without buffering the entire stream.

```
import { streamMap, streamToText } from 'personaforge/models';

async function* demoStream() {
  yield { type: 'text', text: 'hello ' } as const;
  yield { type: 'text', text: 'world' } as const;
}

const uppercaseStream = streamMap(demoStream(), async (delta) => {
  if (delta.type !== 'text') return delta;
  return { ...delta, text: delta.text.toUpperCase() };
});

console.log(await streamToText(uppercaseStream)); // "HELLO WORLD"
```

---

## `streamFilter()`

Drop chunks that don't satisfy a predicate:

```
import { streamFilter, streamToText } from 'personaforge/models';

async function* demoStream() {
  yield { type: 'text', text: 'visible ' } as const;
  yield { type: 'tool_call', id: 'tool-1', name: 'search', argsDelta: '{"q":"hidden"}' } as const;
  yield { type: 'text', text: 'text' } as const;
}

const textOnly = streamFilter(demoStream(), (delta) => delta.type === 'text');
console.log(await streamToText(textOnly)); // "visible text"
```

---

## `streamMerge()`

Merge multiple concurrent streams into one stream. Deltas are yielded as they arrive.

```
import { streamMerge } from 'personaforge/models';

async function* streamA() {
  yield { type: 'text', text: 'A1 ' } as const;
  yield { type: 'text', text: 'A2 ' } as const;
}

async function* streamB() {
  yield { type: 'text', text: 'B1 ' } as const;
}

for await (const chunk of streamMerge([streamA(), streamB()])) {
  if (chunk.type === 'text') process.stdout.write(chunk.text);
}
```

---

## `streamToNodeCallback()`

Adapt an `AsyncIterable<StreamDelta>` to a Node-style callback.

```
import { streamToNodeCallback } from 'personaforge/models';

async function* demoStream() {
  yield { type: 'text', text: 'Hello' } as const;
}

streamToNodeCallback(
  demoStream(),
  (err, chunk) => {
    if (err) {
      console.error('Stream error:', err);
      return;
    }
    if (chunk?.type === 'text') process.stdout.write(chunk.text);
    if (chunk === null) console.log('\nStream complete');
  },
);
```

---

## Composing utilities

Most utilities accept and return `AsyncIterable<StreamDelta>`, so they compose naturally:

```
import {
  streamFilter,
  streamTee,
  streamToSSE,
  streamToText,
  streamWithBudget,
} from 'personaforge/models';
import { createServer } from 'node:http';

async function* rawStream() {
  yield { type: 'text', text: 'hello ' } as const;
  yield { type: 'tool_call', id: 'tool-1', name: 'search', argsDelta: '{"q":"world"}' } as const;
  yield { type: 'text', text: 'world' } as const;
}

// Build a processing pipeline:
const [logStream, responseStream] = streamTee(
  streamWithBudget(
    streamFilter(rawStream(), (delta) => delta.type === 'text'),
    { maxTokens: 1000 },
  ),
);

const server = createServer(async (_req, res) => {
  await streamToSSE(responseStream, res, { keepAliveMs: 0 });
});

server.listen(3000);

console.log(await streamToText(logStream));
```

---

## Quick reference

| Function | Input | Output | Description |
|----------|-------|--------|-------------|
| `streamToText` | `AsyncIterable<StreamDelta>` | `Promise<string>` | Collect all text |
| `streamToChunks` | `AsyncIterable<StreamDelta>` | `Promise<string[]>` | Collect text chunks |
| `streamToSSE` | `AsyncIterable<StreamDelta>` | `Promise<void>` | Write SSE to a `ServerResponse` |
| `streamWithBudget` | `AsyncIterable<StreamDelta>` | `AsyncIterable<StreamDelta>` | Token-budget gate |
| `streamTee` | `AsyncIterable<StreamDelta>` | `[AsyncIterable, AsyncIterable]` | Duplicate stream |
| `streamMap` | `AsyncIterable<StreamDelta>` | `AsyncIterable<StreamDelta>` | Transform each delta |
| `streamFilter` | `AsyncIterable<StreamDelta>` | `AsyncIterable<StreamDelta>` | Drop deltas |
| `streamMerge` | `AsyncIterable<StreamDelta>[]` | `AsyncIterable<StreamDelta>` | Merge multiple streams |
| `streamToNodeCallback` | `AsyncIterable<StreamDelta>` | `void` | Node.js callback adapter |

---

## See also

- [Creating Agents](/guide/agents) — `agent.stream()` and `agent.streamEvents()`
- [WebSocket Transport](/guide/websocket) — stream over WebSocket
- [Observability & OTLP](/guide/observability) — trace every stream chunk
- [Background Queues](/guide/background-queues) — process streams asynchronously


# Guide: structured-output

# Structured Output

`generateStructured()` gives you validated, typed output from any LLM provider using each provider's native structured-output API when available, and a prompt-plus-retry fallback when it is not.

```
import { generateStructured, detectProviderKind } from 'personaforge/structured';
```

Native paths:

| Provider | Native mechanism |
|---|---|
| OpenAI, OpenRouter | `response_format` with JSON schema |
| Anthropic | Forced tool call whose parameters are the schema |
| Gemini | `responseSchema` |
| Everything else | Prompt injection + parse retry |

Selection is automatic based on the provider class name.

---

## Quick start

```
import { z } from 'zod';

const Person = z.object({
  name: z.string(),
  age: z.number(),
  interests: z.array(z.string()),
});

const result = await generateStructured(
  llm,
  [{ role: 'user', content: 'Invent a person profile.' }],
  { parse: (data) => Person.parse(data), name: 'person' },
);

// result.data is Person
console.log(result.data.name);
console.log(result.attempts);   // 1 on success, up to maxRetries+1 on fallback
```

---

## Options

```
generateStructured(provider, messages, schema, {
  maxRetries: 3,   // fallback path only
  temperature: 0,  // + any GenerateOptions field
});
```

Return shape:

```
interface StructuredOutputResult<T> {
  data: T;                       // validated
  raw: string;                   // raw model text
  attempts: number;
  usage?: { promptTokens; completionTokens; totalTokens };
}
```

---

## Providing a schema

Two shapes are supported. Pick whichever you already have:

### Zod

Any object with a `.parse()` method (Zod v3 or v4) is accepted directly:

```
const Person = z.object({ name: z.string(), age: z.number() });
const result = await generateStructured(llm, msgs, {
  parse: (d) => Person.parse(d),
  name: 'person',
});
```

If Zod exposes `.toJSONSchema()`, it is called automatically to produce the JSON Schema that the provider needs.

### Raw JSON Schema

Pass a JSON Schema directly and let the caller do validation:

```
const result = await generateStructured(llm, msgs, {
  name: 'invoice',
  jsonSchema: {
    type: 'object',
    properties: {
      id: { type: 'string' },
      lineItems: { type: 'array', items: { type: 'object' } },
    },
    required: ['id', 'lineItems'],
  },
  parse: (d) => d, // caller-defined validation
});
```

---

## Detecting provider capability

```
import { detectProviderKind } from 'personaforge/structured';

const kind = detectProviderKind(llm);
// 'openai' | 'anthropic' | 'gemini' | 'bedrock' | 'unknown'
```

Used internally to route to the native path. Useful if you want to branch behaviour based on provider support.

---

## Fallback behaviour

Providers we do not recognise get the prompt-injection path:

1. The schema is appended as a system message.
2. If parse fails, the model is re-prompted with the error and asked to correct.
3. Retries up to `maxRetries`. On exhaustion the last error is thrown.

This makes structured output work on Ollama, DeepSeek, or any custom provider.

---

## Related pages

- [Output Parsers](/guide/output-parsers) — post-hoc parsing for text output.
- [Providers](/guide/providers) — provider registration.


# Guide: team-modes

# Team Modes

`createModeTeam` is ergonomic sugar over the orchestration layer for three common multi-agent patterns. Each mode has different routing and aggregation semantics.

```
import { createModeTeam } from 'personaforge/orchestration';
```

| Mode | Behaviour |
|---|---|
| `route` | Leader picks **one** specialist per query |
| `coordinate` | All specialists respond; leader **synthesizes** one answer |
| `collaborate` | All specialists respond; outputs are **merged** verbatim |

---

## `route`

The leader agent selects the single best specialist for each query. Requires a `leader`.

```
const team = createModeTeam({
  mode: 'route',
  leader: routerAgent,
  agents: [mathAgent, proseAgent, codeAgent],
});

const result = await team.run('What is the derivative of x^2?');
// result.text            — the selected agent's answer
// result.contributions   — [{ agent: 'math', text: '...' }]
```

The leader is prompted with each agent's `instructions` as a manifest and asked to return `{"agent": "<name>"}`. Falls back to the first agent if selection is ambiguous.

---

## `coordinate`

Every specialist answers in parallel, then the leader synthesizes a single coherent response. Requires a `leader`.

```
const team = createModeTeam({
  mode: 'coordinate',
  leader: editorAgent,
  agents: [researchAgent, factCheckAgent],
  maxRounds: 1,
});

const result = await team.run('Explain quantum computing.');
// result.text           — the synthesized answer
// result.contributions  — every specialist's raw response
```

---

## `collaborate`

Every specialist answers; outputs are merged verbatim with agent labels. No leader needed.

```
const team = createModeTeam({
  mode: 'collaborate',
  agents: [optimistAgent, skepticAgent],
});

const result = await team.run('Should we adopt this technology?');
// result.text: "[optimist]: ...\n\n[skeptic]: ..."
```

---

## Team result shape

```
interface TeamResult {
  text: string;                                        // aggregated answer
  contributions: Array<{ agent: string; text: string }>;
}
```

---

## Choosing a mode

- **route** — mutually exclusive specialties (a math agent vs a writing agent). Cheapest: only one specialist runs.
- **coordinate** — overlapping perspectives that should merge into one authoritative answer.
- **collaborate** — you want to preserve each voice (e.g. a debate, or multiple independent drafts).

---

## Related pages

- [Orchestration](/guide/orchestration) — full multi-agent primitives (supervisor, swarm, pipeline).
- [Reasoning Tools](/guide/reasoning-tools) — structured thinking for individual agents.


# Guide: tool-composition

# Tool Composition

Tool composition lets you add caching, auth, logging, retries, and other cross-cutting concerns to any tool without touching its core logic.

```
import { extendTool, wrapTool, pipeTools, versionTool } from 'personaforge';
```

---

## `extendTool` — lifecycle hooks + transforms

Add `beforeExecute`, `afterExecute`, `transformInput`, `transformOutput`, and `onError` to any existing tool:

```
import { extendTool } from 'personaforge';
import { webSearchTool } from 'personaforge';  // or any built-in / custom tool

// Add result trimming + console logging to the built-in web search
const limitedSearch = extendTool(webSearchTool, {
  name: 'limited_web_search',
  description: 'Web search — returns top 3 results only.',

  beforeExecute: async (params, ctx) => {
    console.log(`[${ctx.runId}] Searching: ${params.query}`);
    // Return false to cancel the call
  },

  transformInput: async (params) => ({
    ...params,
    query: params.query.trim().toLowerCase(),  // normalise
  }),

  transformOutput: (results) =>
    Array.isArray(results) ? results.slice(0, 3) : results,  // trim

  afterExecute: (results, params, ctx) => {
    console.log(`[${ctx.runId}] Got ${Array.isArray(results) ? results.length : 1} results`);
  },

  onError: (err, params) => {
    console.warn('Search failed, returning empty:', err.message);
    return [];  // graceful fallback
  },

  timeoutMs: 10_000,
});
```

### All `extendTool` options

| Option | Type | Description |
|---|---|---|
| `name` | `string` | Override the tool name |
| `description` | `string` | Override the description |
| `transformInput` | `(params, ctx) => params` | Rewrite inputs before execution |
| `transformOutput` | `(output, params, ctx) => output` | Rewrite outputs after execution |
| `beforeExecute` | `(params, ctx) => false \| undefined` | Pre-hook — return `false` to cancel |
| `afterExecute` | `(output, params, ctx) => void` | Post-hook for logging / analytics |
| `onError` | `(err, params, ctx) => output` | Error handler — return fallback or re-throw |
| `needsApproval` | `boolean \| fn` | Override approval requirement |
| `timeoutMs` | `number` | Override timeout |
| `tags` | `string[]` | Append tags |
| `category` | `ToolCategory` | Override category |

---

## `wrapTool` — middleware pipeline

Apply a stack of `(params, ctx, next) => result` middlewares (onion model):

```
import { wrapTool } from 'personaforge';

const safeTool = wrapTool(myTool, [
  // 1. Auth check (outermost — runs first)
  async (params, ctx, next) => {
    if (!ctx.userId) throw new Error('Unauthorized');
    return next(params, ctx);
  },
  // 2. Cache layer
  async (params, ctx, next) => {
    const key = `cache:${JSON.stringify(params)}`;
    const hit = await cache.get<string>(key);
    if (hit) return JSON.parse(hit);
    const result = await next(params, ctx);
    await cache.set(key, JSON.stringify(result), 300);
    return result;
  },
  // 3. Retry on transient failure
  async (params, ctx, next) => {
    for (let attempt = 0; attempt < 3; attempt++) {
      try { return await next(params, ctx); }
      catch (err) {
        if (attempt === 2) throw err;
        await new Promise(r => setTimeout(r, 200 * (attempt + 1)));
      }
    }
  },
]);
```

---

## `pipeTools` — chain two tools

Output of the first tool becomes input to the second:

```
import { pipeTools } from 'personaforge';
import { fetchUrlTool, myHtmlParserTool } from './tools.js';

const fetchAndParse = pipeTools(fetchUrlTool, myHtmlParserTool, {
  name: 'fetch_and_parse',
  description: 'Fetch a URL then parse the HTML content.',
  // Map the first tool's output to the second tool's input schema
  adapter: (fetchResult, originalParams) => ({
    html: fetchResult.body,
    url: originalParams.url,
  }),
});

// Use like any other tool
const result = await fetchAndParse.execute({ url: 'https://example.com' }, ctx);
```

---

## `versionTool` — versioned wrappers

Tag a tool with a version for deprecation management:

```
import { versionTool, extendTool } from 'personaforge';

// Tag as version 2.0 with a changelog
const searchV2 = versionTool(searchTool, '2.0', {
  changelog: 'Returns structured results with source URLs and snippets.',
});

// Mark old version as deprecated
const searchV1 = versionTool(oldSearchTool, '1.0', {
  deprecated: true,
  replacedBy: 'search_v2_0',
});
```

---

## Compose with `toolMiddleware` on the agent

Apply middleware to every tool in an agent at once:

```
import { createAgent } from 'personaforge';

const starts = new Map<string, number>();

const agent = createAgent({
  name: 'monitored-agent',
  instructions: '...',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
  tools: [searchTool, emailTool, dbTool],
  toolMiddleware: [
    {
      beforeExecute: (tool) => {
        starts.set(tool.name, Date.now());
      },
      afterExecute: (tool, result) => {
        const start = starts.get(tool.name) ?? Date.now();
        metrics.histogram('tool.duration_ms', Date.now() - start, {
          tool: tool.name,
          status: result.success ? 'ok' : 'error',
        });
      },
      onError: (tool) => {
        const start = starts.get(tool.name) ?? Date.now();
        metrics.histogram('tool.duration_ms', Date.now() - start, { tool: tool.name, status: 'error' });
      },
    },
  ],
});
```

---

## Where to go next

- [Custom tools](./custom-tools) — author tools from scratch with `tool()`.
- [Tools](./tools) — built-in tools and the `ToolRegistry`.
- [HITL](./hitl) — approval stores for `needsApproval` in production.


# Guide: toolkits

# Toolkits

A toolkit is a named set of tools **plus** a system-prompt fragment describing how the agent should use them. Ship a starter set of related capabilities to an agent in one line.

```
import { sqlToolkit, httpToolkit, fileToolkit, combineToolkits } from 'personaforge/toolkits';
```

---

## `sqlToolkit`

Read-only SQL toolkit. Blocks destructive DML/DDL at the tool boundary.

```
const kit = sqlToolkit({
  execute: async (q) => db.query(q),
  listTables: async () => ['users', 'orders'],
  describeTable: async (t) => db.getSchema(t),
});

const analyst = agent({
  name: 'analyst',
  instructions: [baseInstructions, kit.promptFragment].join('\n'),
  tools: kit.tools,   // sql_list_tables, sql_describe_table, sql_query
});
```

Tools:
- `sql_list_tables` — discover tables.
- `sql_describe_table` — get columns and types for a table.
- `sql_query` — run a SELECT (throws on DROP/DELETE/UPDATE/INSERT/ALTER/TRUNCATE).

---

## `httpToolkit`

HTTP GET and POST with optional URL allowlist.

```
const kit = httpToolkit({
  allowlist: ['https://api.example.com', 'https://api.stripe.com'],
  headers: { 'user-agent': 'my-agent/1.0' },
});
```

Requests outside the allowlist are rejected before the fetch runs.

Tools: `http_get`, `http_post`.

---

## `fileToolkit`

Read/write/list files rooted at a configurable directory. Path guards prevent escaping the workspace root.

```
const kit = fileToolkit({ root: '/workspace' });
// Tools: file_read, file_write, file_list
```

Any attempt like `file_read({ path: '../../etc/passwd' })` throws.

Inject a custom `fs` adapter for tests or virtual filesystems:

```
const kit = fileToolkit({
  root: '/vfs',
  fs: {
    readFile: async (p) => mem.get(p),
    writeFile: async (p, c) => { mem.set(p, c); },
    readdir: async (p) => Array.from(mem.keys()).filter((k) => k.startsWith(p)),
  },
});
```

---

## `combineToolkits`

Merge multiple toolkits into one bundle. Duplicate tool names throw.

```
const bundle = combineToolkits(
  sqlToolkit({ ...sqlOpts }),
  httpToolkit({ allowlist: ['https://api.internal'] }),
);

agent({
  tools: bundle.tools,
  instructions: bundle.promptFragment,  // both fragments joined and labelled
});
```

---

## Building your own toolkit

The shape is deliberately minimal:

```
interface PromptedToolkit {
  name: string;
  description: string;
  tools: Tool[];
  promptFragment: string;
}
```

Follow the pattern in `src/toolkits/index.ts`. Keep tools narrow, describe them precisely, and prefer strict `parameters` schemas so the model gets validation for free.

---

## Related pages

- [Built-in Tools](/guide/tools) — the 100+ pre-existing tool library.
- [Tool Composition](/guide/tool-composition) — piping and wrapping tools.
- [Custom Tools](/guide/custom-tools) — authoring your own.


# Guide: tools

# Tools

Tools are functions the agent can call during a run. They are defined with `tool()` or `defineTool()`, validated with Zod, and passed directly to `createAgent()`.

## Define a tool

```
import { tool } from 'personaforge/tool';
import { z } from 'zod';

const getWeather = tool({
  name: 'get_weather',
  description: 'Get the current weather for a city. Use this when the user asks about weather.',
  parameters: z.object({
    city: z.string().describe('City name, e.g. "Tokyo"'),
    unit: z.enum(['celsius', 'fahrenheit']).default('celsius'),
  }),
  execute: async ({ city, unit }) => {
    // real implementation calls your weather API
    return { city, temperature: 22, unit, condition: 'sunny' };
  },
});
```

Pass the tool to `createAgent`:

```
import { createAgent } from 'personaforge';

const agent = createAgent({
  name: 'weather-agent',
  instructions: 'Help with weather queries. Always call get_weather before answering.',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
  tools: [getWeather],
});
```

---

## `tool()` vs `defineTool()`

Both produce the same result. `defineTool` is the older API; `tool` is the preferred shorthand.

```
import { tool, defineTool, createTool } from 'personaforge/tool';

// All three are equivalent:
const t1 = tool({ name: 'add', description: '...', parameters: z.object({ a: z.number(), b: z.number() }), execute: async ({ a, b }) => a + b });
const t2 = defineTool({ name: 'add', description: '...', parameters: z.object({ a: z.number(), b: z.number() }), execute: async ({ a, b }) => a + b });
const t3 = createTool({ name: 'add', description: '...', parameters: z.object({ a: z.number(), b: z.number() }), execute: async ({ a, b }) => a + b });
```

---

## Multiple tools: `createTools`

```
import { createTools } from 'personaforge/tool';
import { z } from 'zod';

const tools = createTools({
  search_orders: {
    description: 'Find a customer order by id.',
    parameters: z.object({ orderId: z.string() }),
    execute: async ({ orderId }) => ({ orderId, status: 'shipped', eta: '2026-05-14' }),
  },
  cancel_order: {
    description: 'Cancel an order. Only use if the customer explicitly requests cancellation.',
    parameters: z.object({ orderId: z.string(), reason: z.string() }),
    execute: async ({ orderId, reason }) => ({ cancelled: true, orderId, reason }),
  },
});

const agent = createAgent({ name: 'support', instructions: '...', model: 'gpt-4o-mini', apiKey: process.env.OPENAI_API_KEY!, tools: Object.values(tools) });
```

---

## Tool context

Every tool receives a context object as the second argument:

```
const auditTool = tool({
  name: 'update_record',
  description: 'Update a database record.',
  parameters: z.object({ id: z.string(), data: z.record(z.string()) }),
  execute: async ({ id, data }, ctx) => {
    console.log('agent:', ctx.agentId);
    console.log('session:', ctx.sessionId);
    // ctx.abortSignal — AbortSignal for cancellation
    return { updated: true };
  },
});
```

---

## Tool middleware

Apply cross-cutting behaviour (logging, caching, auth) across all tools:

```
import { createAgent } from 'personaforge';

const agent = createAgent({
  name: 'agent',
  instructions: '...',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
  tools: [searchTool, dbTool],
  toolMiddleware: [
    // Logging middleware
    {
      beforeExecute: (tool, params) => {
        console.log(`[tool] ${tool.name} called`, params);
      },
      afterExecute: (tool, result) => {
        console.log(`[tool] ${tool.name} returned`, result);
      },
    },
  ],
});
```

---

## Extend and wrap tools

```
import { extendTool, wrapTool, pipeTools } from 'personaforge/tool';

// Normalise inputs and trim results around an existing tool
const reliableSearch = extendTool(searchTool, {
  name: 'reliable_search',
  transformInput: (params) => ({ ...params, query: params.query.trim() }),
  transformOutput: (results) => (Array.isArray(results) ? results.slice(0, 3) : results),
  timeoutMs: 10_000,
});

// Wrap with a middleware pipeline: (params, ctx, next)
const wrappedSearch = wrapTool(searchTool, [
  async (params, ctx, next) => {
    const sanitised = { ...params, query: params.query.trim() };
    const result = await next(sanitised, ctx);
    return { ...result, source: 'search' };
  },
]);

// Chain tools: output of tool1 becomes input of tool2
const pipeline = pipeTools(fetchPageTool, summariseTool, {
  name: 'fetch_and_summarise',
  description: 'Fetch a page then summarise it.',
  adapter: (page) => ({ text: page.body }),
});
```

---

## Built-in tools (100+)

Each provider-backed tool is imported from its category subpath (e.g. `personaforge/tools/search`).

### Search

```
import {
  TavilySearchTool,       // AI-optimised web search
  BraveSearchTool,        // privacy-first web search
  ExaSearchTool,          // neural search
  PerplexitySearchTool,   // web-grounded LLM search
  ArxivSearchTool,        // academic papers
  PubMedSearchTool,       // biomedical papers
  YouTubeSearchTool,
  RedditSearchTool,
  OpenWeatherToolkit,
  GoogleMapsToolkit,
} from 'personaforge/tools/search';

const agent = createAgent({
  name: 'researcher',
  instructions: 'Research the topic thoroughly.',
  model: 'gpt-4o',
  apiKey: process.env.OPENAI_API_KEY!,
  tools: [new TavilySearchTool({ apiKey: process.env.TAVILY_API_KEY! })],
});
```

### Communication

```
import {
  SlackToolkit,
  GmailToolkit,
  EmailToolkit,
  DiscordToolkit,
  TelegramTool,
  TwilioToolkit,
  ZoomToolkit,
  ResendToolkit,
} from 'personaforge/tools/communication';
```

### Productivity

```
import {
  JiraToolkit,
  NotionToolkit,
  ConfluenceToolkit,
  LinearToolkit,
  ClickUpToolkit,
  GoogleDriveToolkit,
  GoogleSheetsToolkit,
  GoogleCalendarToolkit,
} from 'personaforge/tools/productivity';
```

### Developer tools

```
import {
  GitHubToolkit,
  GitLabToolkit,
  DockerToolkit,
  E2BToolkit,        // sandboxed code execution
  CodeExecToolkit,   // local code execution
} from 'personaforge/tools/devtools';
```

### Data

```
import {
  BigQueryToolkit,
  CsvToolkit,
  DatabaseToolkit,
  Neo4jToolkit,
  RedisToolkit,
} from 'personaforge/tools/data';
```

### Finance

```
import {
  StripeToolkit,
  YFinanceTool,      // Yahoo Finance market data
} from 'personaforge/tools/finance';
```

### Utilities

```
import {
  httpClient,        // HTTP requests
  fileSystem,        // read/write local files
  browserTool,       // headless browser
  createShellTool,   // run shell commands
} from 'personaforge/tool';
```

### Web preset

Pass `tools: 'web'` to give the agent HTTP + browser tools with no imports:

```
const agent = createAgent({
  name: 'web-agent',
  instructions: 'Browse the web and answer questions.',
  model: 'gpt-4o',
  apiKey: process.env.OPENAI_API_KEY!,
  tools: 'web',
});
```

---

## Tool registry

Group tools into a typed registry for advanced use:

```
import { ToolRegistryImpl } from 'personaforge/tool';

const registry = new ToolRegistryImpl();
registry.register(searchTool);
registry.register(emailTool);
registry.register(dbTool);

const agent = createAgent({ name: 'agent', instructions: '...', model: 'gpt-4o-mini', apiKey: '...', tools: registry });
```

---

## Where to go next

- [Custom tools](./custom-tools) — advanced tool authoring patterns.
- [Tool composition](./tool-composition) — wrapping, caching, and pipelining tools.
- [MCP](./mcp) — expose or consume tools via the Model Context Protocol.
- [HITL](./hitl) — require human approval before a tool executes.


# Guide: trace-dataset

# Trace ↔ Dataset Loop

The `personaforge/eval` module closes the LangSmith-style feedback loop: capture a real production interaction, turn it into an eval sample, replay it against a new prompt or model, and diff the results to catch regressions before shipping.

```
import {
  spanToSample, replayDataset, diffResults, summarizeDiff,
} from 'personaforge/eval';
```

---

## The workflow

1. **Capture** a trace span from production.
2. **Convert** it to an `EvalSample` with `spanToSample`.
3. **Replay** the dataset against a candidate agent version with `replayDataset`.
4. **Diff** candidate vs baseline with `diffResults`, then summarise with `summarizeDiff`.

---

## 1. Capture and convert

```
const sample = spanToSample({
  id: span.id,
  name: 'agent.run',
  input: span.input,
  output: span.output,        // becomes the expected value
  startTime: span.startTime,
  endTime: span.endTime,
  metadata: { model: 'gpt-4o' },
});

dataset.push(sample);
```

`spanToSample` tags the sample with `metadata.source = 'trace'` so trace-derived samples are distinguishable from hand-authored ones.

---

## 2. Replay

Run every sample through a candidate run function, in parallel:

```
const results = await replayDataset(
  dataset,
  (input) => candidateAgent.run(input),   // returns string or { text }
  { concurrency: 4 },
);
// ReplayResult[] — { sample, output, durationMs }
```

---

## 3. Diff

Compare two replay runs (same order and length):

```
const baseline = await replayDataset(dataset, (i) => currentAgent.run(i));
const candidate = await replayDataset(dataset, (i) => newAgent.run(i));

const diffs = diffResults(baseline, candidate);
```

Each `DiffEntry` reports:

```
{
  sampleId, input, expected,
  baselineOutput, newOutput,
  unchanged,                    // outputs identical?
  baselineMatchesExpected,      // did the old version match ground truth?
  newMatchesExpected,           // did the new version?
}
```

---

## 4. Summarise for CI

```
const summary = summarizeDiff(diffs);
// { total, unchanged, changed, regressions, improvements }

if (summary.regressions > 0) {
  console.error(`❌ ${summary.regressions} regressions detected`);
  process.exit(1);
}
```

A **regression** is a sample the baseline got right and the candidate got wrong. An **improvement** is the reverse. Wire this into a CI gate to block PRs that regress golden traces.

---

## Example CI script

```
const dataset = productionSpans.map((s) => spanToSample(s));
const baseline = await replayDataset(dataset, (i) => mainBranchAgent.run(i));
const candidate = await replayDataset(dataset, (i) => prBranchAgent.run(i));
const summary = summarizeDiff(diffResults(baseline, candidate));

console.log(`Improvements: ${summary.improvements}, Regressions: ${summary.regressions}`);
process.exit(summary.regressions > 0 ? 1 : 0);
```

---

## Related pages

- [Evaluation & Benchmarking](/guide/eval) — accuracy metrics and LLM-as-judge.
- [Observability](/guide/observability) — where trace spans come from.
- [Control Plane](/guide/control-plane) — browse traces and eval runs in a dashboard.


# Guide: trust

# Trust & Reliability

Choosing an agent framework is a production bet. This page collects the **evidence** behind personaforge — not just what it can do, but how it is tested, secured, benchmarked, and governed.

---

## At a glance

| Signal | Detail |
|---|---|
| **License** | MIT — use commercially, fork freely, zero lock-in |
| **Telemetry** | No telemetry sent by default. Your data stays in your infrastructure. |
| **Language** | TypeScript-first — same runtime as your application |
| **Package model** | One `npm install`, 70+ tree-shakeable subpaths |
| **Security contact** | Private disclosure via [SECURITY.md](https://github.com/confused-ai/personaforge/blob/main/SECURITY.md) |
| **Response SLA** | 72-hour acknowledgement, 14-day patch cycle for critical issues |

---

## Security {#security}

personaforge ships with security controls designed for production agent workloads — not as optional plugins.

### Vulnerability disclosure

Report security issues privately via [SECURITY.md](https://github.com/confused-ai/personaforge/blob/main/SECURITY.md). **Do not** open public GitHub issues for vulnerabilities.

### Built-in protections

| Control | What it does |
|---|---|
| **Guardrails engine** | PII detection, prompt-injection defense, content moderation hooks |
| **JWT RBAC** | Role-based access on HTTP routes with timing-safe verification |
| **SSRF-protected tools** | URL allow-lists and network isolation on outbound tool calls |
| **Secret manager adapters** | AWS Secrets Manager, Azure Key Vault, HashiCorp Vault, GCP Secret Manager |
| **ShellTool isolation** | Not in the default barrel — requires explicit import and container sandboxing |
| **Rate limiting** | In-process and Redis-backed distributed rate limiters |
| **Budget enforcement** | Per-user and per-tenant USD cost caps |

### Production hardening checklist

Before shipping to production, verify:

- [ ] LLM API keys in environment variables, never in source
- [ ] `rateLimit` wired into `createHttpService`
- [ ] PII guardrails enabled for user-facing agents
- [ ] Budget caps configured per tenant
- [ ] HTTPS termination in front of the agent service
- [ ] `personaforge doctor` run in CI to validate configuration
- [ ] ShellTool disabled or running inside a sandboxed container
- [ ] OTLP tracing exported to your observability backend

See [Guardrails & Safety](./guardrails), [Production](./production), and [Secret Manager](./secret-manager) for implementation details.

---

## Testing {#testing}

Every release is gated by an automated test suite designed for agent workloads — not just unit tests on utility functions.

### By the numbers

| Metric | Value |
|---|---|
| Test cases | **1,500+** |
| Test files | **124** |
| CI coverage floor (`src/`) | **43%** lines (ratcheting toward 75%) |
| CI coverage floor (`packages/`) | **48%** lines |
| Live API calls in CI | **None** — MockLLMProvider for deterministic runs |

### How we test agents

- **MockLLMProvider** — deterministic LLM responses without API keys or network calls
- **MockToolRegistry** — fixture helpers for tool-call assertions
- **Hermetic integration tests** — full agent loops run in CI with zero external dependencies
- **Regression detection** — `replayDataset` and `diffResults` for eval regression
- **Coverage ratchet** — thresholds increase quarterly; CI blocks PRs that drop below the floor

```
# Run the full test suite locally
bun run test

# Hermetic τ-bench (always in CI)
bun run test tests/tau-bench-hermetic.test.ts
```

See [Evaluation & Benchmarking](./eval) and the [testing runbook](../runbooks/testing.md).

---

## Benchmarks {#benchmarks}

Capability claims need measurable proof. personaforge ships a τ-bench-style harness that scores agents on **tool-calling correctness** — not prose quality.

### Published results

Live run against **gpt-4o-mini** (2026-07-24):

| Domain | Passed | Total | Pass rate |
|---|---|---|---|
| Retail | 4 | 5 | 80.0% |
| Data | 5 | 5 | 100.0% |
| Coding | 3 | 3 | 100.0% |
| **All** | **12** | **13** | **92.3%** |

Scores are **verifier-based**: each task checks tool-call arguments and ordering, making results reproducible across model versions and stable in CI.

### Cross-framework protocol

The same harness runs identical tasks against personaforge, LangGraph, Agno, CrewAI, and Mastra. See [`benchmarks/tau-bench/PROTOCOL.md`](https://github.com/confused-ai/personaforge/blob/main/benchmarks/tau-bench/PROTOCOL.md) for the full protocol.

```
# Head-to-head vs Agno
bun examples/agno-vs-personaforge.ts
```

---

## Observability {#observability}

Production agents need visibility into every run — not just the final text output.

| Capability | Detail |
|---|---|
| **OTLP tracing** | Export to Jaeger, Datadog, Honeycomb, or any OTLP-compatible backend |
| **Structured logging** | Context-aware logs with run ID, session ID, and tenant |
| **Prometheus metrics** | Request counts, latency, token usage, error rates |
| **Audit log** | Tamper-evident append-only log (SQLite, Redis, or pluggable) |
| **Control plane** | Built-in dashboard for runs, sessions, and agent health |
| **Trace ↔ Dataset** | Convert production traces into eval datasets for regression testing |

See [Observability & OTLP](./observability) and [Control Plane](./control-plane).

---

## Open source governance

| Resource | Link |
|---|---|
| Source code | [github.com/confused-ai/personaforge](https://github.com/confused-ai/personaforge) |
| Changelog | [Changelog](/changelog) |
| Contributing | [CONTRIBUTING.md](https://github.com/confused-ai/personaforge/blob/main/CONTRIBUTING.md) |
| Security policy | [SECURITY.md](https://github.com/confused-ai/personaforge/blob/main/SECURITY.md) |
| License | [MIT](https://github.com/confused-ai/personaforge/blob/main/LICENSE) |
| npm package | [confused-ai](https://www.npmjs.com/package/confused-ai) |

### Supported versions

| Version | Support |
|---|---|
| 1.1.x | Current — full support |
| 1.0.x | Critical fixes only |
| < 1.0 | No support |

---

## Adopters & case studies

We believe trust comes from real production usage. If you're running personaforge in production, add yourself to [ADOPTERS.md](https://github.com/confused-ai/personaforge/blob/main/ADOPTERS.md) — one line is enough.

Longer architecture write-ups follow the [case study template on GitHub](https://github.com/confused-ai/personaforge/blob/main/docs/case-studies/case-study-template.md). We value honest tradeoffs over vanity metrics.

---

## What we don't claim

Honesty matters more than marketing copy.

| Claim | Reality |
|---|---|
| SOC2 / HIPAA **certified** | We ship audit-logging **capabilities** that support compliance workflows. We are not SOC2 or HIPAA certified. |
| Competitor benchmark superiority | Cross-framework τ-bench numbers are published via a shared protocol. We do not fabricate competitor scores. |
| 100% test coverage | Coverage is CI-gated at 43%+ and ratcheting quarterly. We publish the floor, not an aspirational target. |
| Zero bugs | We have a 72-hour security acknowledgement SLA and a public issue tracker. Report problems — we fix them. |

---

## Compare with other frameworks

See [Framework Comparisons](./comparisons) for the full capability matrix and migration guides for LangChain, CrewAI, LangGraph, Mastra, and Agno.

---

## Where to go next

- [Production](./production) — circuit breakers, retries, budget enforcement.
- [Guardrails & Safety](./guardrails) — PII, prompt injection, moderation.
- [Evaluation & Benchmarking](./eval) — LLM-as-judge, regression detection.
- [Getting Started](./getting-started) — first agent in minutes.


# Guide: video

# Video

The video module provides `VideoOrchestrator` — a pipeline that generates narrated video shorts by combining LLM-written scripts, TTS voiceovers, Pexels stock footage, and FFmpeg composition.

```
import { VideoOrchestrator } from 'personaforge';
```

> **Prerequisites**  
> `OPENAI_API_KEY` — script generation and TTS.  
> `PEXELS_API_KEY` — stock background footage. Get one free at [pexels.com/api](https://www.pexels.com/api/).  
> `ffmpeg` must be available (installed automatically via `@ffmpeg-installer/ffmpeg`).

---

## Generate a short video

```
import { VideoOrchestrator } from 'personaforge';

const orchestrator = new VideoOrchestrator();

const result = await orchestrator.generateShort('The history of the internet');

if (result.success) {
  console.log('Video saved to:', result.videoPath);
  // result.videoPath — absolute path to the generated MP4 file
} else {
  console.error('Generation failed:', result.error);
}
```

The pipeline runs in order:
1. **Script** — GPT-4o writes a 30–45 second narration script for the topic.
2. **Voiceover** — OpenAI TTS converts the script to MP3 audio.
3. **Background footage** — Pexels API returns stock video clips matching the topic.
4. **Stitch** — FFmpeg layers audio over video, trims to match duration, outputs final MP4.

---

## `VideoGenerationResult`

```
interface VideoGenerationResult {
  success: boolean;
  videoPath?: string;   // absolute path to MP4 — present on success
  error?: string;       // error message — present on failure
}
```

---

## Environment variables

| Variable | Required | Description |
|---|---|---|
| `OPENAI_API_KEY` | ✅ | Script generation (GPT-4o) + TTS voiceover |
| `PEXELS_API_KEY` | ✅ | Background stock footage |

---

## Temporary files

The orchestrator uses a `temp_videos/` directory in the current working directory for intermediate files. It cleans up per-job work directories automatically on success (final MP4 is kept).

---

## Using video in an agent tool

Expose `VideoOrchestrator` as an agent tool:

```
import { tool, createAgent } from 'personaforge';
import { VideoOrchestrator } from 'personaforge';
import { z } from 'zod';

const orchestrator = new VideoOrchestrator();

const generateVideoTool = tool({
  name: 'generate_video_short',
  description: 'Generate a 30-45 second narrated video short on any topic.',
  schema: z.object({
    topic: z.string().describe('Topic or theme for the video'),
  }),
  timeoutMs: 120_000,   // video generation can take up to 2 minutes
  execute: async ({ topic }) => {
    const result = await orchestrator.generateShort(topic);
    if (!result.success) return { error: result.error };
    return { videoPath: result.videoPath, message: 'Video generated successfully.' };
  },
});

const agent = createAgent({
  name: 'video-creator',
  instructions: 'Create short video clips for users on any topic they request.',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
  tools: [generateVideoTool],
});

const result = await agent.run('Make me a video about quantum computing.');
```

---

## Where to go next

- [Vision](./vision) — image and multimodal inputs.
- [Voice](./voice) — TTS/STT providers used internally.
- [Workflows](./workflows) — multi-stage processing pipelines.


# Guide: vision

# Vision

Vision lets you pass images, PDFs, and other media to vision-capable models. Use the `multiModal()` helper to combine text prompts with one or more image sources.

```
import {
  multiModal,
  imageUrl,
  imageFile,
  imageBuffer,
} from 'personaforge';
```

---

## Pass a remote image

```
import { createAgent, multiModal, imageUrl } from 'personaforge';

const agent = createAgent({
  name: 'vision-agent',
  instructions: 'Analyse the images provided by the user.',
  model: 'gpt-4o',
  apiKey: process.env.OPENAI_API_KEY!,
});

const result = await agent.run(
  await multiModal(
    'What is in this image?',
    imageUrl('https://example.com/photo.jpg'),
  ),
);
```

---

## Pass a local file

```
import { multiModal, imageFile } from 'personaforge';

// imageFile() is async — loads and base64-encodes the file
const result = await agent.run(
  await multiModal(
    'Describe this chart.',
    await imageFile('./chart.png'),
  ),
);
```

---

## Pass a buffer (canvas, upload, fetch response)

```
import { multiModal, imageBuffer } from 'personaforge';

const response = await fetch('https://example.com/diagram.png');
const buffer = await response.arrayBuffer();

const result = await agent.run(
  await multiModal(
    'What does this architecture diagram show?',
    imageBuffer(buffer, 'image/png'),
  ),
);
```

---

## Multiple images in one message

```
const result = await agent.run(
  await multiModal(
    'Compare these two screenshots and explain the differences.',
    imageUrl(beforeUrl),
    imageUrl(afterUrl),
  ),
);
```

---

## Image detail level

Control quality vs. speed with the `detail` option:

```
imageUrl('https://example.com/photo.jpg', { detail: 'high' })
imageUrl('https://example.com/thumbnail.jpg', { detail: 'low' })
// 'auto' (default) — model decides
```

---

## `ImageSource` types

| Type | Factory | Description |
|---|---|---|
| `ImageUrl` | `imageUrl(url, opts?)` | HTTPS or data URI |
| `ImageFile` | `await imageFile(path, opts?)` | Local file — loaded at call time (Node.js only) |
| `ImageBuffer` | `imageBuffer(data, mimeType, opts?)` | Raw ArrayBuffer / Uint8Array |

---

## Supported formats

Images: `jpg`, `jpeg`, `png`, `gif`, `webp`, `bmp`, `svg`, `tiff`, `heic`  
Audio: `mp3`, `wav`, `ogg`, `m4a`, `flac`, `webm`  
Video: `mp4`, `webm`, `mov`, `avi`, `mkv`

---

## Where to go next

- [Voice](./voice) — speech input and output.
- [Video](./video) — process and summarise video content.
- [Agents](./agents) — `agent.run()` multiModal option.


# Guide: voice

# Voice

The voice module provides TTS/STT adapters and a real-time streaming session that wires together speech input → agent reasoning → spoken output.

```
import {
  OpenAIVoiceProvider,
  ElevenLabsVoiceProvider,
  createVoiceProvider,
  VoiceStreamSession,
} from 'personaforge';
```

---

## Text-to-Speech (TTS)

```
import { OpenAIVoiceProvider } from 'personaforge';

const voice = new OpenAIVoiceProvider({ apiKey: process.env.OPENAI_API_KEY });

const result = await voice.textToSpeech('Hello, how can I help you today?', {
  voiceId: 'nova',   // 'alloy' | 'echo' | 'fable' | 'onyx' | 'nova' | 'shimmer'
  model: 'tts-1-hd',
  speed: 1.0,        // 0.25–4.0
});

// result.audio     — ArrayBuffer
// result.format    — 'mp3' | 'opus' | 'aac' | 'flac' | 'wav' | 'pcm'
// result.durationSeconds
// result.characterCount
```

---

## Speech-to-Text (STT)

```
const audioBuffer: ArrayBuffer = await readAudioFile('./question.mp3');

const transcript = await voice.speechToText(audioBuffer, { language: 'en' });
// transcript.text        — transcribed string
// transcript.language    — detected language
// transcript.confidence  — 0–1
// transcript.durationSeconds
```

---

## ElevenLabs provider

```
import { ElevenLabsVoiceProvider } from 'personaforge';

const voice = new ElevenLabsVoiceProvider({
  apiKey: process.env.ELEVENLABS_API_KEY!,
});

const result = await voice.textToSpeech('Welcome back!', {
  voiceId: 'rachel',  // ElevenLabs voice ID
});
```

---

## `createVoiceProvider` factory

```
import { createVoiceProvider } from 'personaforge';

const voice = createVoiceProvider({
  provider: process.env.VOICE_PROVIDER as 'openai' | 'elevenlabs',
  apiKey: process.env.VOICE_API_KEY!,
  voiceId: 'nova',
  model: 'tts-1',
});
```

---

## Real-time voice streaming

`VoiceStreamSession` connects a microphone stream to an agent and streams synthesised audio back:

```
import { OpenAIVoiceProvider, VoiceStreamSession } from 'personaforge';
import { createAgent } from 'personaforge';

const voiceProvider = new OpenAIVoiceProvider();
const agent = createAgent({
  name: 'voice-assistant',
  instructions: 'You are a helpful voice assistant. Be concise.',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
});

const session = new VoiceStreamSession({
  stt: voiceProvider,
  tts: voiceProvider,
  run: async (text) => {
    const result = await agent.run(text);
    return result.text;
  },
  voiceId: 'nova',
  silenceThresholdMs: 800,   // ms of silence to trigger STT
});

// Push raw PCM audio chunks in real time (from microphone, WebSocket, etc.)
microphone.on('data', (chunk) => session.pushChunk(chunk));

// Consume events
for await (const event of session.events()) {
  switch (event.type) {
    case 'transcript':
      console.log('User said:', event.text);
      break;
    case 'text_delta':
      process.stdout.write(event.delta ?? '');
      break;
    case 'audio':
      speaker.write(event.chunk);  // play synthesised audio
      break;
    case 'agent_end':
      console.log('Agent finished speaking');
      break;
    case 'error':
      console.error('Voice error:', event.error);
      break;
  }
}

await session.end();
```

### `VoiceStreamEvent` types

| `type` | Fields | Description |
|---|---|---|
| `transcript` | `text` | STT result from user speech |
| `agent_start` | — | Agent processing began |
| `text_delta` | `delta` | One token from agent response |
| `audio` | `chunk` (Uint8Array) | TTS audio chunk (MP3) |
| `agent_end` | — | Agent finished responding |
| `error` | `error` | Session-level error |

---

## `VoiceProvider` interface

Implement this to add any TTS/STT backend:

```
interface VoiceProvider {
  textToSpeech(text: string, options?: Partial<VoiceConfig>): Promise<TTSResult>;
  speechToText?(audio: ArrayBuffer | Blob, options?: { language?: string }): Promise<STTResult>;
  listVoices?(): Promise<Array<{ id: string; name: string; preview_url?: string }>>;
}
```

---

## Where to go next

- [Vision](./vision) — image and multimodal inputs.
- [WebSocket](./websocket) — realtime transport for voice sessions.
- [Hooks](./hooks) — lifecycle hooks for monitoring speech latency.


# Guide: websocket

# WebSocket & Streaming

Agents support three streaming modes out of the box: simple string chunks via `agent.stream()`, typed events via `agent.streamEvents()`, and resumable SSE streams via `ResumableStreamManager`.

```
import { createAgent } from 'personaforge';
import { ResumableStreamManager, formatSSE } from 'personaforge';
```

---

## `agent.stream()` — text chunks

The simplest streaming mode — yields string chunks as the model generates:

```
const agent = createAgent({
  name: 'streamer',
  instructions: 'Be helpful.',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
});

for await (const chunk of agent.stream('Explain async/await in TypeScript')) {
  process.stdout.write(chunk);
}
```

---

## `agent.streamEvents()` — typed events

Yields `StreamChunk` events differentiating text deltas, tool calls, step completions, and the final result:

```
for await (const event of agent.streamEvents('Research quantum computing')) {
  switch (event.type) {
    case 'text-delta':
      process.stdout.write(event.delta ?? '');
      break;
    case 'tool-call':
      console.log(`Calling tool: ${event.tool?.name}`);
      break;
    case 'tool-result':
      console.log(`Tool result: ${event.tool?.name}`);
      break;
    case 'step-finish':
      console.log(`Step ${event.stepNumber} done`);
      break;
    case 'run-finish':
      console.log(`Completed in ${event.run?.steps} steps`);
      break;
    case 'error':
      console.error('Stream error:', event.error);
      break;
  }
}
```

### `StreamChunk` fields

| `type` | Fields | Description |
|---|---|---|
| `text-delta` | `delta` | One token or phrase from the model |
| `tool-call` | `tool.name`, `tool.input` | Tool called by the agent |
| `tool-result` | `tool.name`, `tool.output` | Tool returned its result |
| `step-finish` | `stepNumber` | Reasoning step completed |
| `run-finish` | `run` (AgentRunResult) | Run complete — final result |
| `error` | `error` | Error during the run |

---

## SSE endpoint (HTTP)

Serve streaming responses as Server-Sent Events from any HTTP server:

```
import Fastify from 'fastify';
import { createAgent, formatSSE, ResumableStreamManager } from 'personaforge';

const app = Fastify();
const agent = createAgent({ ... });
const streams = new ResumableStreamManager({ maxAgeMs: 5 * 60_000 });

app.get('/stream', async (req, reply) => {
  const prompt = req.query.prompt as string;

  reply.raw.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive',
  });

  const streamId = streams.createStream();

  for await (const event of agent.streamEvents(prompt)) {
    const chunk = streams.saveChunk(streamId, {
      type:    event.type === 'text-delta' ? 'text' : 'tool_call',
      content: event.delta ?? JSON.stringify(event.tool),
    });
    if (chunk) {
      reply.raw.write(formatSSE(chunk));
    }
    if (event.type === 'run-finish' || event.type === 'error') break;
  }
  reply.raw.end();
});
```

---

## Resumable streams (reconnect after disconnect)

`ResumableStreamManager` checkpoints every chunk so clients can reconnect from where they left off:

```
import { ResumableStreamManager } from 'personaforge';

const streams = new ResumableStreamManager({
  maxAgeMs: 5 * 60_000,    // keep streams resumable for 5 minutes
  maxStreams: 1000,
});

// Create a stream
const streamId = streams.createStream();

// Save each chunk as it arrives
streams.saveChunk(streamId, { type: 'text', content: 'Hello' });
streams.saveChunk(streamId, { type: 'text', content: ' world!' });

// Client reconnects — resume from position
const checkpoint = streams.getCheckpoint(streamId);
const missedChunks = streams.getChunksSince(streamId, checkpoint.position);
```

### Reconnect endpoint

```
app.get('/stream/:id/resume', async (req, reply) => {
  const { id } = req.params as { id: string };
  const position = Number(req.query.position ?? 0);

  reply.raw.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
  });

  // Send missed chunks
  for (const chunk of streams.getChunksSince(id, position)) {
    reply.raw.write(formatSSE(chunk));
  }
  reply.raw.end();
});
```

---

## WebSocket server

Use `streamEvents` inside a WebSocket handler to push events directly:

```
import { WebSocketServer } from 'ws';

const wss = new WebSocketServer({ port: 8080 });

wss.on('connection', (ws) => {
  ws.on('message', async (data) => {
    const { prompt } = JSON.parse(data.toString());

    for await (const event of agent.streamEvents(prompt)) {
      if (ws.readyState !== ws.OPEN) break;
      ws.send(JSON.stringify(event));
    }
  });
});
```

---

## Where to go next

- [Observability](./observability) — trace and monitor streaming runs.
- [Production](./production) — graceful shutdown, rate limiting.
- [Voice](./voice) — voice streaming via `VoiceStreamSession`.


# Guide: workflow-branching

# Workflow Branching

Branching adds conditional execution paths — different stages run depending on what an earlier stage produced. In `personaforge`, branching is available in both the pipeline API (`compose`/`pipe`) and the full graph engine.

---

## Branching in pipelines (`pipe`)

Use a `when` predicate on `pipe(...).then()` to skip or stop stages:

```
import { pipe, createAgent } from 'personaforge';

const classifier = createAgent({
  name: 'classifier',
  instructions: 'Classify the user request as: simple | complex | out-of-scope.',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
});

const deepAnalyzer = createAgent({
  name: 'deep-analyzer',
  instructions: 'Perform in-depth analysis of complex requests.',
  model: 'gpt-4o',
  apiKey: process.env.OPENAI_API_KEY!,
});

const responder = createAgent({
  name: 'responder',
  instructions: 'Produce the final user-facing response.',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
});

const pipeline = pipe(classifier)
  // Only run deep analysis for 'complex' requests
  .then(deepAnalyzer, {
    when: (result) => result.text.toLowerCase().includes('complex'),
    transform: (result) => `Analyse in depth:\n${result.text}`,
  })
  .then(responder);

const result = await pipeline.run('How do I configure distributed tracing in Kubernetes?');
```

---

## Branching in graph workflows

Use a `router` node for multi-way branching:

```
import { createGraph } from 'personaforge';

const graph = createGraph('support-routing')
  .addNode('classify', {
    kind: 'task',
    execute: async (ctx) => {
      const category = await classifier.run(ctx.state.input as string);
      return { category: category.text.trim() };
    },
  })
  .addNode('billing-agent',   { kind: 'task', execute: (ctx) => billingAgent.run(ctx.state.input as string) })
  .addNode('technical-agent', { kind: 'task', execute: (ctx) => techAgent.run(ctx.state.input as string) })
  .addNode('general-agent',   { kind: 'task', execute: (ctx) => generalAgent.run(ctx.state.input as string) })
  .addNode('router', {
    kind: 'router',
    route: (state) => {
      const category = (state.results['classify'] as { category: string }).category;
      if (category.includes('billing'))   return 'billing-agent';
      if (category.includes('technical')) return 'technical-agent';
      return 'general-agent';
    },
  })
  .addEdge('classify', 'router')
  .addEdge('router', 'billing-agent')
  .addEdge('router', 'technical-agent')
  .addEdge('router', 'general-agent')
  .build();
```

---

## Conditional edges

Add conditions on any edge with `addEdge(from, to, { condition })`:

```
const graph = createGraph('quality-gate')
  .addNode('generate', { kind: 'task', execute: (ctx) => writer.run(ctx.state.input as string) })
  .addNode('review',   { kind: 'task', execute: (ctx) => reviewer.run(ctx.state.results['generate'] as string) })
  .addNode('revise',   { kind: 'task', execute: (ctx) => writer.run(`Revise based on: ${ctx.state.results['review']}`) })
  .addNode('publish',  { kind: 'task', execute: (ctx) => publisher.run(ctx.state.results['review'] as string) })
  // If review passes, publish; otherwise revise
  .addEdge('review', 'publish', {
    condition: (state) => String(state.results['review']).toLowerCase().includes('approved'),
  })
  .addEdge('review', 'revise', {
    condition: (state) => !String(state.results['review']).toLowerCase().includes('approved'),
  })
  .addEdge('generate', 'review')
  .addEdge('revise', 'publish')
  .build();
```

---

## Validation gate pattern

A common pattern: validate first, branch on pass/fail:

```
const pipeline = pipe(validator)
  .then(processor, {
    when: (result) => !result.text.toLowerCase().includes('invalid'),
    transform: (result) => `Process this validated input:\n${result.text}`,
  })
  .then(formatter);
```

---

## When to escalate to graph

If you find yourself with more than 2–3 `when` predicates on a linear pipeline, consider moving to the graph engine:

- More than 3 conditional branches → use a `router` node
- Parallel branches that join back → use `fanOut` + `fanIn` in the graph builder
- Retry loops → use `defaultRetry` on the graph or node
- Re-visiting earlier stages → the graph engine supports cycles

---

## Where to go next

- [Compose](./compose) — linear pipelines without branching.
- [Graph workflows](./graph) — DAG execution with fan-out, join, and router nodes.
- [Orchestration](./orchestration) — supervisor patterns and agent handoffs.


# Guide: workflows

# Workflows

The graph workflow engine lets you build typed, durable DAG workflows with explicit branching, parallel execution, retries, and checkpointing. Import from `personaforge/workflow`.

## Quick start

```
import { createGraph, DAGEngine } from 'personaforge/workflow';

const graph = createGraph('data-pipeline')
  .addNode('fetch', {
    kind: 'task',
    execute: async (ctx) => {
      const url = ctx.state.variables.url as string;
      return { data: await fetchData(url) };
    },
  })
  .addNode('transform', {
    kind: 'task',
    execute: async (ctx) => {
      const { data } = ctx.state.results['fetch'] as { data: unknown };
      return { transformed: transform(data) };
    },
  })
  .addNode('save', {
    kind: 'task',
    execute: async (ctx) => {
      const { transformed } = ctx.state.results['transform'] as { transformed: unknown };
      await saveToDatabase(transformed);
      return { saved: true };
    },
  })
  .chain('fetch', 'transform', 'save')  // linear shorthand for addEdge
  .build();

const engine = new DAGEngine(graph);
const result = await engine.execute({ variables: { url: 'https://api.example.com/data' } });
console.log(result.state.results);
```

---

## `GraphBuilder`

All graph types are created through the `GraphBuilder` or the `createGraph` helper.

### Node types

| Type | Purpose |
|---|---|
| `task` | Execute a function; can call LLMs, APIs, or any async work |
| `agent` | Run a `createAgent()` agent as a node |
| `router` | Branch to different nodes based on condition |
| `parallel` | Fan out to multiple nodes simultaneously |
| `join` | Wait for all branches to complete before continuing |
| `wait` | Pause for an external event (HITL, webhook, timer) |

### `task` node

```
import { GraphBuilder } from 'personaforge/workflow';

const builder = new GraphBuilder('my-graph');

builder.addNode('classify', {
  kind: 'task',
  execute: async (ctx) => {
    const label = await classifyText(ctx.state.variables.text as string);
    return { label };
  },
  retry: { maxRetries: 3, backoffMs: 1_000, exponentialBase: 2 },
  timeout: { timeoutMs: 10_000 },
});
```

### `agent` node

```
// An `agent` node is configured inline — the engine builds the agent from this
// config (instructions, model, provider, tools by name, maxSteps, temperature).
builder.addNode('research', {
  kind: 'agent',
  instructions: 'Research the given topic thoroughly.',
  model: 'gpt-4o',
  maxSteps: 5,
});
```

### `router` node — conditional branching

```
builder.addNode('route', {
  kind: 'router',
  route: async (ctx) => {
    const label = ctx.state.results['classify'] as string;
    if (label === 'technical') return 'tech-handler';
    if (label === 'billing')   return 'billing-handler';
    return 'general-handler';
  },
});

// The router follows the outgoing edge whose `label` matches its return value.
builder
  .addEdge('route', 'tech-handler',    { label: 'tech-handler' })
  .addEdge('route', 'billing-handler', { label: 'billing-handler' })
  .addEdge('route', 'general-handler', { label: 'general-handler' });
```

### `parallel` node — fan out

```
// A `parallel` node fans out to its outgoing edges; a `join` node waits for the
// incoming branches, then merges their results (keyed by node name).
builder.addNode('gather', { kind: 'parallel' });
builder.addNode('merge', {
  kind: 'join',
  strategy: 'all',
  merge: async (results) => ({
    combined: ['search-web', 'search-db', 'search-docs']
      .map((k) => String(results[k] ?? ''))
      .join('\n\n'),
  }),
});

builder
  .fanOut('gather', ['search-web', 'search-db', 'search-docs'])
  .fanIn(['search-web', 'search-db', 'search-docs'], 'merge');
```

### `wait` node — HITL and webhooks

```
builder.addNode('await-approval', {
  kind: 'wait',
  type: 'human',           // 'human' | 'webhook' | 'timer' | 'signal'
  signalName: 'human-approval',
  timeoutMs: 86_400_000,   // 24 hours
});
```

---

## Full DAG example: content pipeline

```
import { createGraph, DAGEngine } from 'personaforge/workflow';

const graph = createGraph('content-pipeline')
  .addNode('plan', {
    kind: 'agent',
    instructions: 'Create a detailed outline for the given topic.',
    model: 'gpt-4o-mini',
  })
  .addNode('write-sections', { kind: 'parallel' })
  .addNode('section-intro',      { kind: 'agent', instructions: 'Write the introduction from the outline.', model: 'gpt-4o' })
  .addNode('section-body',       { kind: 'agent', instructions: 'Write the body from the outline.',         model: 'gpt-4o' })
  .addNode('section-conclusion', { kind: 'agent', instructions: 'Write the conclusion from the outline.',   model: 'gpt-4o' })
  .addNode('assemble', {
    kind: 'join',
    strategy: 'all',
    merge: async (results) => ({
      combined: ['section-intro', 'section-body', 'section-conclusion']
        .map((k) => String(results[k] ?? ''))
        .join('\n\n'),
    }),
  })
  .addNode('review', { kind: 'agent', instructions: 'Review for accuracy and readability.', model: 'gpt-4o-mini' })
  .addNode('seo',    { kind: 'agent', instructions: 'Add SEO keywords and meta tags.',      model: 'gpt-4o-mini' })
  .addEdge('plan', 'write-sections')
  .fanOut('write-sections', ['section-intro', 'section-body', 'section-conclusion'])
  .fanIn(['section-intro', 'section-body', 'section-conclusion'], 'assemble')
  .chain('assemble', 'review', 'seo')
  .build();

const engine = new DAGEngine(graph);
const result = await engine.execute({ variables: { topic: 'The future of TypeScript in 2027' } });
console.log(result.state.results['seo']);
```

---

## `compose` and `pipe` — lightweight pipelines

For simple sequential chains without the full graph engine:

```
import { compose, pipe } from 'personaforge/workflow';
import { createAgent } from 'personaforge';

// compose: agents in sequence, output → input
const chain = compose(researchAgent, writeAgent, editAgent);
const result = await chain.run('Write a blog post on Rust async runtimes.');

// pipe: functional transform chain
const process = pipe(
  async (topic: string) => researchAgent.run(topic),
  async (r)             => writeAgent.run(r.text),
  async (r)             => editAgent.run(r.text),
);
const final = await process('Rust async runtimes');
```

---

## Retry policies

```
builder.addNode('call-external-api', {
  kind: 'task',
  execute: async (ctx) => callApi(ctx.input),
  retry: {
    maxRetries: 5,
    backoffMs: 500,
    exponentialBase: 2,     // exponential backoff
    maxBackoffMs: 10_000,
    retryOn: (err) => err instanceof Error && (err.message.includes('rate limit') || err.message.includes('timeout')),
  },
});
```

---

## Checkpointing (durable workflows)

The graph engine emits `GraphEvent`s. Plug in an `EventStore` to replay interrupted workflows:

```
import { DAGEngine } from 'personaforge/workflow';
import { SqliteEventStore } from 'personaforge';

// The event store and checkpoint cadence are passed to execute(), not the constructor.
const engine = new DAGEngine(graph);
const result = await engine.execute({
  eventStore: new SqliteEventStore('./workflow-events.db'),
  checkpointInterval: 10,   // persist a checkpoint every N node completions
});

// A paused or suspended run resumes on the same engine (optionally injecting variables):
const resumed = await engine.resume({ variables: { approved: true } });
```

---

## Where to go next

- [Graph workflow branching](./workflow-branching) — advanced conditional branching patterns.
- [Orchestration](./orchestration) — team, supervisor, and swarm patterns.
- [Production](./production) — circuit breakers, checkpoints, and durable execution.


# Runbook: adapter-redis

# Runbook: Adapter Redis

> Auto-generated from `./src/adapter-redis/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/adapter-redis`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/adapters](../guide/adapters.md)

## What it is
`personaforge/adapter-redis` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/adapter-redis';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
This entry exposes types/interfaces only. Import the symbols you need for typing:

```
import 'personaforge/adapter-redis';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/adapter-redis` with no missing-module error.
- Runtime: `node -e "import('personaforge/adapter-redis').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/adapters](../guide/adapters.md).

## Common failures
- `Cannot find module 'personaforge/adapter-redis'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/adapters](../guide/adapters.md)


# Runbook: adapters

# Runbook: Adapters

> Auto-generated from `./src/adapters/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/adapters`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/adapters](../guide/adapters.md)

## What it is
`personaforge/adapters` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/adapters';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
Real example from the adapters guide:

```
import { createAgent } from 'personaforge';
import {
  InMemoryCacheAdapter,
  InMemorySessionStoreAdapter,
  InMemoryVectorAdapter,
  createAdapterRegistry,
} from 'personaforge/adapters';

const registry = createAdapterRegistry();
registry.register(new InMemoryCacheAdapter());
registry.register(new InMemoryVectorAdapter());
registry.register(new InMemorySessionStoreAdapter());

await registry.connectAll();

const agent = createAgent({
  name: 'assistant',
  instructions: 'Use the registered adapters.',
  model: 'gpt-4o-mini',
  adapters: registry,
});

console.log(registry.toBindings());
void agent;
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/adapters` with no missing-module error.
- Runtime: `node -e "import('personaforge/adapters').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/adapters](../guide/adapters.md).

## Common failures
- `Cannot find module 'personaforge/adapters'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/adapters](../guide/adapters.md)


# Runbook: agentic

# Runbook: Agentic

> Auto-generated from `./src/agentic/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/agentic`  ·  **Public symbols:** 1  ·  **Guide:** [/guide/agents](../guide/agents.md)

## What it is
`personaforge/agentic` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import { createAgenticAgent } from 'personaforge/agentic';
```

## Public API surface
- **Factories / functions** — `createAgenticAgent`

## Minimal use
Real example from the agents guide:

```
import { AgenticRunner, createAgenticAgent } from 'personaforge';
import { OpenAIProvider } from 'personaforge';

const runner = new AgenticRunner({
  llm: new OpenAIProvider({ apiKey: process.env.OPENAI_API_KEY! }),
  tools: myToolRegistry,
  maxSteps: 10,
  timeoutMs: 60_000,
});

runner.setGuardrails(myGuardrailEngine);
runner.setHumanInTheLoop(myHITLHooks);

const result = await runner.run({
  name: 'my-agent',
  instructions: 'Process the request.',
  prompt: 'Analyse the latest sales data.',
});
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/agentic` with no missing-module error.
- Runtime: `node -e "import('personaforge/agentic').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/agents](../guide/agents.md).

## Common failures
- `Cannot find module 'personaforge/agentic'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/agents](../guide/agents.md)


# Runbook: approval

# Runbook: Approval

> Auto-generated from `./src/approval/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/approval`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/approval](../guide/approval.md)

## What it is
`personaforge/approval` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/approval';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
Real example from the approval guide:

```
import { createSqliteSuspendedRunStore } from 'personaforge/approval';

const store = createSqliteSuspendedRunStore('./agent.db');
await store.save({
  runId: 'run_123',
  agentId: 'support-bot',
  threadId: 't1',
  resourceId: 'user-7',
  status: 'approval',
  toolCalls: [{
    toolCallId: 'call_1',
    toolName: 'send_invoice',
    args: { customerId: 'c1', amount: 500 },
    requiresApproval: true,
  }],
  createdAt: new Date().toISOString(),
  updatedAt: new Date().toISOString(),
});

const pending = await store.list({ threadId: 't1' });
await store.markResolved('run_123');
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/approval` with no missing-module error.
- Runtime: `node -e "import('personaforge/approval').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/approval](../guide/approval.md).

## Common failures
- `Cannot find module 'personaforge/approval'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/approval](../guide/approval.md)


# Runbook: artifacts

# Runbook: Artifacts

> Auto-generated from `./src/artifacts/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/artifacts`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/artifacts](../guide/artifacts.md)

## What it is
`personaforge/artifacts` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/artifacts';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
Real example from the artifacts guide:

```
import {
  createTextArtifact,
  createMarkdownArtifact,
  createDataArtifact,
  createPlanArtifact,
  createReasoningArtifact,
} from 'personaforge/artifacts';

// Plain text / code file
const code = createTextArtifact({
  name: 'auth-handler.ts',
  content: `export function verifyToken(token: string) { ... }`,
  type: 'code',
  mimeType: 'text/typescript',
  tags: ['auth', 'typescript'],
  createdBy: 'code-agent',
});

// Markdown report
const report = createMarkdownArtifact({
  name: 'Q4-report.md',
  content: '## Q4 Summary\n\nRevenue up 12% YoY...',
  tags: ['report', 'q4'],
});

// Structured data
const data = createDataArtifact({
  name: 'search-results',
  content: { query: 'LLM benchmarks', results: [...] },
  type: 'json',
});

// Agent reasoning trace
const trace = createReasoningArtifact({
  steps: [
    { title: 'Analyse', action: 'Read the requirements', result: '...', confidence: 0.9 },
  ],
  conclusion: 'Use a queue-based approach.',
  model: 'gpt-4o',
});

// Execution plan
const plan = createPlanArtifact({
  goal: 'Migrate database to PostgreSQL',
  tasks: [
    { id: '1', name: 'Backup current DB', priority: 0 },
    { id: '2', name: 'Provision RDS', priority: 1, dependencies: ['1'] },
  ],
});
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/artifacts` with no missing-module error.
- Runtime: `node -e "import('personaforge/artifacts').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/artifacts](../guide/artifacts.md).

## Common failures
- `Cannot find module 'personaforge/artifacts'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/artifacts](../guide/artifacts.md)


# Runbook: background

# Runbook: Background

> Auto-generated from `./src/background/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/background`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/background-queues](../guide/background-queues.md)

## What it is
`personaforge/background` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/background';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
Real example from the background-queues guide:

```
import { createAgent } from 'personaforge';
import { InMemoryBackgroundQueue, queueHook } from 'personaforge/background';

// In-memory queue — no dependencies, good for dev/test
const queue = new InMemoryBackgroundQueue({ concurrency: 5 });

const agent = createAgent({
  name: 'analytics-agent',
  instructions: 'Help users with their questions.',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
  hooks: {
    // Dispatch post-run analytics to the queue without blocking the response
    afterRun: queueHook(queue, 'analytics', (result) => ({
      steps:  result.steps,
      tokens: result.usage?.totalTokens,
      runId:  result.runId,
    })),
  },
});

// Register the worker handler (same or separate process)
await queue.consume('analytics', async (task) => {
  await analyticsService.track('agent.run', task.payload);
});

const result = await agent.run('Help me track my order.');
// The afterRun hook fires the task to the queue and returns immediately
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/background` with no missing-module error.
- Runtime: `node -e "import('personaforge/background').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/background-queues](../guide/background-queues.md).

## Common failures
- `Cannot find module 'personaforge/background'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/background-queues](../guide/background-queues.md)


# Runbook: checkpoint

# Runbook: Checkpoint

> Auto-generated from `./src/checkpoint/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/checkpoint`  ·  **Public symbols:** 9  ·  **Guide:** [/guide/checkpoint](../guide/checkpoint.md)

## What it is
`personaforge/checkpoint` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import { InterruptSignal, InMemoryCheckpointStore, DurableExecutor } from 'personaforge/checkpoint';
```

## Public API surface
- **Classes** — `InterruptSignal`, `InMemoryCheckpointStore`, `DurableExecutor`
- **Interfaces** — `Checkpoint`, `CheckpointStore`, `InterruptContext`, `DurableExecutorConfig`, `RunResult`
- **Types** — `NodeFn`

## Minimal use
Real example from the checkpoint guide:

```
import {
  DurableExecutor, InMemoryCheckpointStore, InterruptSignal,
} from 'personaforge/checkpoint';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/checkpoint` with no missing-module error.
- Runtime: `node -e "import('personaforge/checkpoint').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/checkpoint](../guide/checkpoint.md).

## Common failures
- `Cannot find module 'personaforge/checkpoint'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/checkpoint](../guide/checkpoint.md)


# Runbook: cli

# Runbook: Cli

> Auto-generated from `./src/cli/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/cli`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/admin-api](../guide/admin-api.md)

## What it is
`personaforge/cli` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/cli';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
This entry exposes types/interfaces only. Import the symbols you need for typing:

```
import 'personaforge/cli';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/cli` with no missing-module error.
- Runtime: `node -e "import('personaforge/cli').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/admin-api](../guide/admin-api.md).

## Common failures
- `Cannot find module 'personaforge/cli'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/admin-api](../guide/admin-api.md)


# Runbook: code-mode

# Runbook: Code Mode

> Auto-generated from `./src/code-mode/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/code-mode`  ·  **Public symbols:** 3  ·  **Guide:** [/guide/code-mode](../guide/code-mode.md)

## What it is
`personaforge/code-mode` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import { createCodeMode } from 'personaforge/code-mode';
```

## Public API surface
- **Factories / functions** — `createCodeMode`
- **Interfaces** — `CodeModeOptions`, `CodeModeResult`

## Minimal use
Real example from the code-mode guide:

```
import { createCodeMode } from 'personaforge/code-mode';
import { agent } from 'personaforge';

const { tool, instructions } = createCodeMode({
  tools: { getTopProducts, getProductRatings }, // scoped tools
  sandbox: new LocalSandbox(),                   // default: isolated node process
});

const shopping = agent({
  instructions: ['You are a helpful shopping assistant.', instructions],
  tools: { execute_typescript: tool },           // one tool for the LLM
});
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/code-mode` with no missing-module error.
- Runtime: `node -e "import('personaforge/code-mode').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/code-mode](../guide/code-mode.md).

## Common failures
- `Cannot find module 'personaforge/code-mode'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/code-mode](../guide/code-mode.md)


# Runbook: compression

# Runbook: Compression

> Auto-generated from `./src/compression/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/compression`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/compression](../guide/compression.md)

## What it is
`personaforge/compression` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/compression';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
This entry exposes types/interfaces only. Import the symbols you need for typing:

```
import 'personaforge/compression';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/compression` with no missing-module error.
- Runtime: `node -e "import('personaforge/compression').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/compression](../guide/compression.md).

## Common failures
- `Cannot find module 'personaforge/compression'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/compression](../guide/compression.md)


# Runbook: config

# Runbook: Config

> Auto-generated from `./src/config/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/config`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/getting-started](../guide/getting-started.md)

## What it is
`personaforge/config` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/config';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
This entry exposes types/interfaces only. Import the symbols you need for typing:

```
import 'personaforge/config';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/config` with no missing-module error.
- Runtime: `node -e "import('personaforge/config').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/getting-started](../guide/getting-started.md).

## Common failures
- `Cannot find module 'personaforge/config'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/getting-started](../guide/getting-started.md)


# Runbook: context

# Runbook: Context

> Auto-generated from `./src/context/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/context`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/context-provider](../guide/context-provider.md)

## What it is
`personaforge/context` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/context';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
This entry exposes types/interfaces only. Import the symbols you need for typing:

```
import 'personaforge/context';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/context` with no missing-module error.
- Runtime: `node -e "import('personaforge/context').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/context-provider](../guide/context-provider.md).

## Common failures
- `Cannot find module 'personaforge/context'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/context-provider](../guide/context-provider.md)


# Runbook: contracts

# Runbook: Contracts

> Auto-generated from `./src/contracts/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/contracts`  ·  **Public symbols:** 0

## What it is
`personaforge/contracts` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/contracts';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
This entry exposes types/interfaces only. Import the symbols you need for typing:

```
import 'personaforge/contracts';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/contracts` with no missing-module error.
- Runtime: `node -e "import('personaforge/contracts').then(m => console.log(Object.keys(m)))"` lists the exports above.

## Common failures
- `Cannot find module 'personaforge/contracts'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)


# Runbook: control-plane

# Runbook: Control Plane

> Auto-generated from `./src/control-plane/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/control-plane`  ·  **Public symbols:** 5  ·  **Guide:** [/guide/control-plane](../guide/control-plane.md)

## What it is
`personaforge/control-plane` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import { createControlPlane } from 'personaforge/control-plane';
```

## Public API surface
- **Factories / functions** — `createControlPlane`
- **Interfaces** — `SystemSnapshot`, `ControlPlaneAgent`, `ControlPlaneConfig`, `ControlPlaneServer`

## Minimal use
Real example from the control-plane guide:

```
const cp = createControlPlane({
  agents: [
    { name: 'support', run: (prompt) => supportAgent.run(prompt) },
  ],
  sessionStore,
  evalStore,
  traceStore,
  approvalStore,
  knowledgeStore,
});

await cp.start(4100);
console.log('Control plane on http://localhost:4100');
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/control-plane` with no missing-module error.
- Runtime: `node -e "import('personaforge/control-plane').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/control-plane](../guide/control-plane.md).

## Common failures
- `Cannot find module 'personaforge/control-plane'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/control-plane](../guide/control-plane.md)


# Runbook: core

# Runbook: Core

> Auto-generated from `./src/core/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/core`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/concepts](../guide/concepts.md)

## What it is
`personaforge/core` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/core';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
This entry exposes types/interfaces only. Import the symbols you need for typing:

```
import 'personaforge/core';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/core` with no missing-module error.
- Runtime: `node -e "import('personaforge/core').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/concepts](../guide/concepts.md).

## Common failures
- `Cannot find module 'personaforge/core'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/concepts](../guide/concepts.md)


# Runbook: create-agent

# Runbook: Create Agent

> Auto-generated from `./src/create-agent/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/create-agent`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/agents](../guide/agents.md)

## What it is
`personaforge/create-agent` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/create-agent';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
This entry exposes types/interfaces only. Import the symbols you need for typing:

```
import 'personaforge/create-agent';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/create-agent` with no missing-module error.
- Runtime: `node -e "import('personaforge/create-agent').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/agents](../guide/agents.md).

## Common failures
- `Cannot find module 'personaforge/create-agent'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/agents](../guide/agents.md)


# Runbook: db

# Runbook: Db

> Auto-generated from `./src/db/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/db`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/database](../guide/database.md)

## What it is
`personaforge/db` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/db';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
Real example from the database guide:

```
import { createAgent, DbSessionStore } from 'personaforge';
import { createDbKnowledgeEngine, OpenAIEmbeddingProvider } from 'personaforge';
import { SqliteAgentDb } from 'personaforge/db';
import { createDbMemoryStore } from 'personaforge/memory';

const db = new SqliteAgentDb({ path: './agent.db' });
const embedder = new OpenAIEmbeddingProvider({ apiKey: process.env.OPENAI_API_KEY! });

const agent = createAgent({
  name: 'persistent-agent',
  instructions: '...',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
  sessionStore:  new DbSessionStore(db),
  memoryStore:   createDbMemoryStore(db),   // AgentDb passed positionally
  knowledgebase: createDbKnowledgeEngine({
    db,
    embed: (text) => embedder.embed(text),  // embed is an EmbeddingFn, not a provider
  }),
});
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/db` with no missing-module error.
- Runtime: `node -e "import('personaforge/db').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/database](../guide/database.md).

## Common failures
- `Cannot find module 'personaforge/db'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/database](../guide/database.md)


# Runbook: durable

# Runbook: Durable

> Auto-generated from `./src/durable/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/durable`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/durable](../guide/durable.md)

## What it is
`personaforge/durable` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/durable';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
Real example from the durable guide:

```
import { createDurableAgent } from 'personaforge/durable';
import { agent } from 'personaforge';

const researcher = agent('You research topics and return findings.');

const durable = createDurableAgent({ agent: researcher });

// Start a run — the agentic loop runs in the background.
const { runId, output, cleanup } = await durable.stream('Research TypeScript 5');

// Consume events as they arrive (text, tool, approval, goal, run-finish).
for await (const event of output.fullStream) {
  if (event.type === 'text-delta') process.stdout.write(event.delta);
}
const final = await output.runResult;

// Clean up the run subscriptions / timers when you're done.
cleanup();
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/durable` with no missing-module error.
- Runtime: `node -e "import('personaforge/durable').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/durable](../guide/durable.md).

## Common failures
- `Cannot find module 'personaforge/durable'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/durable](../guide/durable.md)


# Runbook: dx

# Runbook: Dx

> Auto-generated from `./src/dx/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/dx`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/getting-started](../guide/getting-started.md)

## What it is
`personaforge/dx` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/dx';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
This entry exposes types/interfaces only. Import the symbols you need for typing:

```
import 'personaforge/dx';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/dx` with no missing-module error.
- Runtime: `node -e "import('personaforge/dx').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/getting-started](../guide/getting-started.md).

## Common failures
- `Cannot find module 'personaforge/dx'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/getting-started](../guide/getting-started.md)


# Runbook: eval

# Runbook: Eval

> Auto-generated from `./src/eval/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/eval`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/eval](../guide/eval.md)

## What it is
`personaforge/eval` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/eval';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
Real example from the eval guide:

```
import {
  runBenchmark,
  exactMatchScorer,
  containsScorer,
  wordOverlapScorer,
  rougeLScorer,
  llmJudgeScorer,
  formatBenchmarkReport,
} from 'personaforge/eval';

const report = await runBenchmark({
  name: 'qa-benchmark-v3',
  dataset: [
    { input: 'What is the boiling point of water?', expected: '100°C' },
    { input: 'Who invented the telephone?',          expected: 'Alexander Graham Bell' },
  ],
  // `run` receives the input string as the first argument
  run: async (input) => {
    const result = await agent.run(input);
    return result.text;
  },
  // Built-in scorers take no arguments
  scorers: [
    exactMatchScorer(),
    containsScorer(),
    wordOverlapScorer(),
    rougeLScorer(),
    llmJudgeScorer({ llm, rubric: 'Award full marks for a correct, complete answer.' }),
  ],
  concurrency: 5,
});

console.log(formatBenchmarkReport(report));
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/eval` with no missing-module error.
- Runtime: `node -e "import('personaforge/eval').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/eval](../guide/eval.md).

## Common failures
- `Cannot find module 'personaforge/eval'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/eval](../guide/eval.md)


# Runbook: events

# Runbook: Events

> Auto-generated from `./src/events/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/events`  ·  **Public symbols:** 3  ·  **Guide:** [/guide/events](../guide/events.md)

## What it is
`personaforge/events` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import { eventBus } from 'personaforge/events';
```

## Public API surface
- **Factories / functions** — `eventBus`
- **Constants** — `AGENT_EVENT`
- **Interfaces** — `CoreEventMap`

## Minimal use
Real example from the events guide:

```
import { eventBus, AGENT_EVENT } from 'personaforge/events';
import { agent } from 'personaforge';

// A bus pre-wired with the core event vocabulary.
const bus = eventBus({ replayBufferSize: 100 });

bus.on(AGENT_EVENT.runFinished, (e) => {
  console.log('run finished', e.agentId, e.result);
});
bus.on('*', (type, payload) => {
  console.log('any event →', type);
});

// Emit from your own hooks:
const bot = agent({
  instructions: 'You are helpful.',
  hooks: {
    afterRun: async (result) => {
      await bus.emit(AGENT_EVENT.runFinished, { agentId: 'bot', sessionId: 's1', result });
    },
  },
});
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/events` with no missing-module error.
- Runtime: `node -e "import('personaforge/events').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/events](../guide/events.md).

## Common failures
- `Cannot find module 'personaforge/events'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/events](../guide/events.md)


# Runbook: execution

# Runbook: Execution

> Auto-generated from `./src/execution/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/execution`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/workflows](../guide/workflows.md)

## What it is
`personaforge/execution` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/execution';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
This entry exposes types/interfaces only. Import the symbols you need for typing:

```
import 'personaforge/execution';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/execution` with no missing-module error.
- Runtime: `node -e "import('personaforge/execution').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/workflows](../guide/workflows.md).

## Common failures
- `Cannot find module 'personaforge/execution'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/workflows](../guide/workflows.md)


# Runbook: goals

# Runbook: Goals

> Auto-generated from `./src/goals/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/goals`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/goals](../guide/goals.md)

## What it is
`personaforge/goals` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/goals';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
Real example from the goals guide:

```
import { createLlmJudge, createStaticJudge, createRubricScorer, createSchemaScorer } from 'personaforge/goals';

// LLM judge with a custom prompt:
const judge = createLlmJudge({
  llm: myLlmProvider,
  prompt: 'You are a strict completeness judge. Respond with JSON.',
});

// Deterministic predicate judge:
const staticJudge = createStaticJudge((text) => text.includes('DONE'));

// Rubric (checklist) scorer with a backing LLM judge:
const rubric = createRubricScorer({
  judge,
  criteria: [
    { description: 'lists acceptance criteria', required: true },
    { description: 'explains test strategy' },
  ],
  requireAll: true,
});

// Schema-validated scorer:
const schemaScorer = createSchemaScorer(myOutputSchema);
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/goals` with no missing-module error.
- Runtime: `node -e "import('personaforge/goals').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/goals](../guide/goals.md).

## Common failures
- `Cannot find module 'personaforge/goals'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/goals](../guide/goals.md)


# Runbook: graph

# Runbook: Graph

> Auto-generated from `./src/graph/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/graph`  ·  **Public symbols:** 1  ·  **Guide:** [/guide/graph](../guide/graph.md)

## What it is
`personaforge/graph` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import { wrapCoreLLM } from 'personaforge/graph';
```

## Public API surface
- **Factories / functions** — `wrapCoreLLM`

## Minimal use
Real example from the graph guide:

```
import { replay, buildReplayProvider, buildReplayTools, replayState } from 'personaforge/graph';

const result = await replay(store, executionId, {
  name: 'researcher',
  instructions: 'Research the given topic thoroughly.',
});

// …or build the replay provider / tool registry yourself:
const llm   = await buildReplayProvider(store, executionId);
const tools = await buildReplayTools(store, executionId);

// Reconstruct the full GraphState from an event log:
const events = await store.load(executionId);
const state  = replayState(events, graph);
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/graph` with no missing-module error.
- Runtime: `node -e "import('personaforge/graph').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/graph](../guide/graph.md).

## Common failures
- `Cannot find module 'personaforge/graph'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/graph](../guide/graph.md)


# Runbook: guard

# Runbook: Guard

> Auto-generated from `./src/guard/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/guard`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/guardrails](../guide/guardrails.md)

## What it is
`personaforge/guard` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/guard';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
This entry exposes types/interfaces only. Import the symbols you need for typing:

```
import 'personaforge/guard';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/guard` with no missing-module error.
- Runtime: `node -e "import('personaforge/guard').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/guardrails](../guide/guardrails.md).

## Common failures
- `Cannot find module 'personaforge/guard'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/guardrails](../guide/guardrails.md)


# Runbook: guardrails

# Runbook: Guardrails

> Auto-generated from `./src/guardrails/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/guardrails`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/guardrails](../guide/guardrails.md)

## What it is
`personaforge/guardrails` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/guardrails';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
This entry exposes types/interfaces only. Import the symbols you need for typing:

```
import 'personaforge/guardrails';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/guardrails` with no missing-module error.
- Runtime: `node -e "import('personaforge/guardrails').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/guardrails](../guide/guardrails.md).

## Common failures
- `Cannot find module 'personaforge/guardrails'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/guardrails](../guide/guardrails.md)


# Runbook: harness

# Runbook: Harness

> Auto-generated from `./src/harness/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/harness`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/eval](../guide/eval.md)

## What it is
`personaforge/harness` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/harness';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
This entry exposes types/interfaces only. Import the symbols you need for typing:

```
import 'personaforge/harness';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/harness` with no missing-module error.
- Runtime: `node -e "import('personaforge/harness').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/eval](../guide/eval.md).

## Common failures
- `Cannot find module 'personaforge/harness'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/eval](../guide/eval.md)


# Runbook: hooks

# Runbook: Hooks

> Auto-generated from `./src/hooks/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/hooks`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/hooks](../guide/hooks.md)

## What it is
`personaforge/hooks` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/hooks';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
This entry exposes types/interfaces only. Import the symbols you need for typing:

```
import 'personaforge/hooks';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/hooks` with no missing-module error.
- Runtime: `node -e "import('personaforge/hooks').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/hooks](../guide/hooks.md).

## Common failures
- `Cannot find module 'personaforge/hooks'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/hooks](../guide/hooks.md)


# Runbook: index

# Runbooks

One runbook per public export subpath. Each covers import, minimal use, verify, common failures, and rollback. Generated from `.d.ts` — regenerate with `node scripts/gen-runbooks.mjs`.

| Feature | Import | Symbols |
|---|---|---|
| [Adapter Redis](./adapter-redis.md) | `personaforge/adapter-redis` | 0 |
| [Adapters](./adapters.md) | `personaforge/adapters` | 0 |
| [Agentic](./agentic.md) | `personaforge/agentic` | 1 |
| [Approval](./approval.md) | `personaforge/approval` | 0 |
| [Artifacts](./artifacts.md) | `personaforge/artifacts` | 0 |
| [Background](./background.md) | `personaforge/background` | 0 |
| [Checkpoint](./checkpoint.md) | `personaforge/checkpoint` | 9 |
| [Cli](./cli.md) | `personaforge/cli` | 0 |
| [Code Mode](./code-mode.md) | `personaforge/code-mode` | 3 |
| [Compression](./compression.md) | `personaforge/compression` | 0 |
| [Config](./config.md) | `personaforge/config` | 0 |
| [Context](./context.md) | `personaforge/context` | 0 |
| [Contracts](./contracts.md) | `personaforge/contracts` | 0 |
| [Control Plane](./control-plane.md) | `personaforge/control-plane` | 5 |
| [Core](./core.md) | `personaforge/core` | 0 |
| [Create Agent](./create-agent.md) | `personaforge/create-agent` | 0 |
| [Db](./db.md) | `personaforge/db` | 0 |
| [Durable](./durable.md) | `personaforge/durable` | 0 |
| [Dx](./dx.md) | `personaforge/dx` | 0 |
| [Eval](./eval.md) | `personaforge/eval` | 0 |
| [Events](./events.md) | `personaforge/events` | 3 |
| [Execution](./execution.md) | `personaforge/execution` | 0 |
| [Goals](./goals.md) | `personaforge/goals` | 0 |
| [Graph](./graph.md) | `personaforge/graph` | 1 |
| [Guard](./guard.md) | `personaforge/guard` | 0 |
| [Guardrails](./guardrails.md) | `personaforge/guardrails` | 0 |
| [Harness](./harness.md) | `personaforge/harness` | 0 |
| [Hooks](./hooks.md) | `personaforge/hooks` | 0 |
| [Framework Core](./index.md) | `personaforge` | 0 |
| [Interfaces](./interfaces.md) | `personaforge/interfaces` | 0 |
| [Knowledge](./knowledge.md) | `personaforge/knowledge` | 0 |
| [Learning](./learning.md) | `personaforge/learning` | 0 |
| [Lite](./lite.md) | `personaforge/lite` | 0 |
| [Memory](./memory.md) | `personaforge/memory` | 0 |
| [Model](./model.md) | `personaforge/model` | 3 |
| [Models](./models.md) | `personaforge/models` | 0 |
| [Observability](./observability.md) | `personaforge/observability` | 0 |
| [Observe](./observe.md) | `personaforge/observe` | 0 |
| [Orchestration](./orchestration.md) | `personaforge/orchestration` | 0 |
| [Parsers](./parsers.md) | `personaforge/parsers` | 9 |
| [Planner](./planner.md) | `personaforge/planner` | 0 |
| [Playground](./playground.md) | `personaforge/playground` | 0 |
| [Plugins](./plugins.md) | `personaforge/plugins` | 0 |
| [Processors](./processors.md) | `personaforge/processors` | 0 |
| [Production](./production.md) | `personaforge/production` | 0 |
| [Providers](./providers.md) | `personaforge/providers` | 0 |
| [Reasoning](./reasoning.md) | `personaforge/reasoning` | 0 |
| [Registry](./registry.md) | `personaforge/registry` | 4 |
| [Router](./router.md) | `personaforge/router` | 5 |
| [Runnable](./runnable.md) | `personaforge/runnable` | 6 |
| [Runtime](./runtime.md) | `personaforge/runtime` | 0 |
| [Scheduler](./scheduler.md) | `personaforge/scheduler` | 0 |
| [Sdk](./sdk.md) | `personaforge/sdk` | 0 |
| [Serve](./serve.md) | `personaforge/serve` | 0 |
| [Session](./session.md) | `personaforge/session` | 0 |
| [Shared](./shared.md) | `personaforge/shared` | 0 |
| [Simulation](./simulation.md) | `personaforge/simulation` | 0 |
| [Skills](./skills.md) | `personaforge/skills` | 0 |
| [Storage](./storage.md) | `personaforge/storage` | 6 |
| [Streaming](./streaming.md) | `personaforge/streaming` | 11 |
| [Structured](./structured.md) | `personaforge/structured` | 8 |
| [System](./system.md) | `personaforge/system` | 0 |
| [Test](./test.md) | `personaforge/test` | 2 |
| [Test Utils](./test-utils.md) | `personaforge/test-utils` | 15 |
| [Test Utils: Conformance](./test-utils-conformance.md) | `personaforge/test-utils/conformance` | 10 |
| [Testing](./testing.md) | `personaforge/testing` | 0 |
| [Tool](./tool.md) | `personaforge/tool` | 0 |
| [Toolkits](./toolkits.md) | `personaforge/toolkits` | 9 |
| [Tools](./tools.md) | `personaforge/tools` | 0 |
| [Tools: Ai](./tools-ai.md) | `personaforge/tools/ai` | 0 |
| [Tools: Communication](./tools-communication.md) | `personaforge/tools/communication` | 0 |
| [Tools: Core](./tools-core.md) | `personaforge/tools/core` | 0 |
| [Tools: Crm](./tools-crm.md) | `personaforge/tools/crm` | 0 |
| [Tools: Data](./tools-data.md) | `personaforge/tools/data` | 0 |
| [Tools: Devtools](./tools-devtools.md) | `personaforge/tools/devtools` | 0 |
| [Tools: Finance](./tools-finance.md) | `personaforge/tools/finance` | 0 |
| [Tools: Mcp](./tools-mcp.md) | `personaforge/tools/mcp` | 0 |
| [Tools: Media](./tools-media.md) | `personaforge/tools/media` | 0 |
| [Tools: Memory](./tools-memory.md) | `personaforge/tools/memory` | 0 |
| [Tools: Productivity](./tools-productivity.md) | `personaforge/tools/productivity` | 0 |
| [Tools: Scraping](./tools-scraping.md) | `personaforge/tools/scraping` | 0 |
| [Tools: Search](./tools-search.md) | `personaforge/tools/search` | 0 |
| [Tools: Shell](./tools-shell.md) | `personaforge/tools/shell` | 3 |
| [Tools: Social](./tools-social.md) | `personaforge/tools/social` | 0 |
| [Tools: Utils](./tools-utils.md) | `personaforge/tools/utils` | 0 |
| [Video](./video.md) | `personaforge/video` | 0 |
| [Voice](./voice.md) | `personaforge/voice` | 0 |
| [Workflow](./workflow.md) | `personaforge/workflow` | 0 |


# Runbook: interfaces

# Runbook: Interfaces

> Auto-generated from `./src/interfaces/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/interfaces`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/concepts](../guide/concepts.md)

## What it is
`personaforge/interfaces` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/interfaces';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
This entry exposes types/interfaces only. Import the symbols you need for typing:

```
import 'personaforge/interfaces';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/interfaces` with no missing-module error.
- Runtime: `node -e "import('personaforge/interfaces').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/concepts](../guide/concepts.md).

## Common failures
- `Cannot find module 'personaforge/interfaces'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/concepts](../guide/concepts.md)


# Runbook: knowledge

# Runbook: Knowledge

> Auto-generated from `./src/knowledge/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/knowledge`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/rag](../guide/rag.md)

## What it is
`personaforge/knowledge` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/knowledge';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
This entry exposes types/interfaces only. Import the symbols you need for typing:

```
import 'personaforge/knowledge';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/knowledge` with no missing-module error.
- Runtime: `node -e "import('personaforge/knowledge').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/rag](../guide/rag.md).

## Common failures
- `Cannot find module 'personaforge/knowledge'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/rag](../guide/rag.md)


# Runbook: learning

# Runbook: Learning

> Auto-generated from `./src/learning/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/learning`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/learning-machine](../guide/learning-machine.md)

## What it is
`personaforge/learning` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/learning';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
This entry exposes types/interfaces only. Import the symbols you need for typing:

```
import 'personaforge/learning';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/learning` with no missing-module error.
- Runtime: `node -e "import('personaforge/learning').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/learning-machine](../guide/learning-machine.md).

## Common failures
- `Cannot find module 'personaforge/learning'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/learning-machine](../guide/learning-machine.md)


# Runbook: lite

# Runbook: Lite

> Auto-generated from `./src/lite.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/lite`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/getting-started](../guide/getting-started.md)

## What it is
`personaforge/lite` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/lite';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
This entry exposes types/interfaces only. Import the symbols you need for typing:

```
import 'personaforge/lite';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/lite` with no missing-module error.
- Runtime: `node -e "import('personaforge/lite').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/getting-started](../guide/getting-started.md).

## Common failures
- `Cannot find module 'personaforge/lite'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/getting-started](../guide/getting-started.md)


# Runbook: memory

# Runbook: Memory

> Auto-generated from `./src/memory/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/memory`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/memory](../guide/memory.md)

## What it is
`personaforge/memory` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/memory';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
Real example from the memory guide:

```
import { MemoryDistiller, summariseMemories, summariseConversation } from 'personaforge/memory';
import { InMemoryStore } from 'personaforge';
import { OpenAIProvider } from 'personaforge';

const llm = new OpenAIProvider({ apiKey: process.env.OPENAI_API_KEY! });
const store = new InMemoryStore();

const distiller = new MemoryDistiller({
  store,                 // the MemoryStore to read short-term entries from and write summaries to
  llm,
  agentId: 'agent-123',  // optional: scope distillation to one agent
  triggerThreshold: 20,  // auto-distill once this many short-term entries accumulate (default: 20)
  batchSize: 30,         // max entries consumed per pass (default: 30)
  // intervalMs: 60_000, // optional background polling; omit to distill manually
});

// Run a distillation pass now. Returns DistillationResult { consumed, summary, skipped }.
const result = await distiller.distillNow(true);  // force = true ignores the threshold
if (result.summary) console.log(result.consumed, result.summary.content);

// One-shot helpers (entries/messages first, llm second; each returns a string)
const memorySummary = await summariseMemories(memories, llm);
const conversationSummary = await summariseConversation(messages, llm);
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/memory` with no missing-module error.
- Runtime: `node -e "import('personaforge/memory').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/memory](../guide/memory.md).

## Common failures
- `Cannot find module 'personaforge/memory'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/memory](../guide/memory.md)


# Runbook: model

# Runbook: Model

> Auto-generated from `./src/model.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/model`  ·  **Public symbols:** 3  ·  **Guide:** [/guide/providers](../guide/providers.md)

## What it is
`personaforge/model` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import { openai, anthropic, ollama } from 'personaforge/model';
```

## Public API surface
- **Factories / functions** — `openai`, `anthropic`, `ollama`

## Minimal use
```
import { openai, anthropic, ollama } from 'personaforge/model';

// `openai` is the primary entry for this feature.
// See the type signature for full options.
const result = openai(/* opts */);
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/model` with no missing-module error.
- Runtime: `node -e "import('personaforge/model').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/providers](../guide/providers.md).

## Common failures
- `Cannot find module 'personaforge/model'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/providers](../guide/providers.md)


# Runbook: models

# Runbook: Models

> Auto-generated from `./src/models/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/models`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/providers](../guide/providers.md)

## What it is
`personaforge/models` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/models';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
This entry exposes types/interfaces only. Import the symbols you need for typing:

```
import 'personaforge/models';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/models` with no missing-module error.
- Runtime: `node -e "import('personaforge/models').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/providers](../guide/providers.md).

## Common failures
- `Cannot find module 'personaforge/models'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/providers](../guide/providers.md)


# Runbook: observability

# Runbook: Observability

> Auto-generated from `./src/observability/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/observability`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/observability](../guide/observability.md)

## What it is
`personaforge/observability` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/observability';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
This entry exposes types/interfaces only. Import the symbols you need for typing:

```
import 'personaforge/observability';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/observability` with no missing-module error.
- Runtime: `node -e "import('personaforge/observability').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/observability](../guide/observability.md).

## Common failures
- `Cannot find module 'personaforge/observability'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/observability](../guide/observability.md)


# Runbook: observe

# Runbook: Observe

> Auto-generated from `./src/observe/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/observe`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/observability](../guide/observability.md)

## What it is
`personaforge/observe` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/observe';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
This entry exposes types/interfaces only. Import the symbols you need for typing:

```
import 'personaforge/observe';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/observe` with no missing-module error.
- Runtime: `node -e "import('personaforge/observe').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/observability](../guide/observability.md).

## Common failures
- `Cannot find module 'personaforge/observe'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/observability](../guide/observability.md)


# Runbook: orchestration

# Runbook: Orchestration

> Auto-generated from `./src/orchestration/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/orchestration`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/orchestration](../guide/orchestration.md)

## What it is
`personaforge/orchestration` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/orchestration';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
Real example from the orchestration guide:

```
import { createSupervisor, createRole } from 'personaforge/orchestration';
import { createAgent } from 'personaforge';

const supervisor = createSupervisor({
  name: 'triage',
  description: 'Coordinates specialist agents to resolve each request.',
  // Each sub-agent is paired with a role describing its responsibilities.
  subAgents: [
    { agent: createAgent({ name: 'billing', instructions: 'Handle billing and payment questions.', model: 'gpt-4o-mini', apiKey: '...' }), role: createRole('billing', ['Handle billing and payment questions']) },
    { agent: createAgent({ name: 'tech',    instructions: 'Solve technical product issues.',        model: 'gpt-4o',      apiKey: '...' }), role: createRole('tech',    ['Solve technical product issues']) },
    { agent: createAgent({ name: 'general', instructions: 'Answer general questions.',               model: 'gpt-4o-mini', apiKey: '...' }), role: createRole('general', ['Answer general questions']) },
  ],
  guidelines: ['Assign each request to the most relevant specialist.'],
  // coordinationType?: 'sequential' (default) | 'parallel'
});

const result = await supervisor.run('My invoice shows the wrong amount.');
console.log(result);
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/orchestration` with no missing-module error.
- Runtime: `node -e "import('personaforge/orchestration').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/orchestration](../guide/orchestration.md).

## Common failures
- `Cannot find module 'personaforge/orchestration'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/orchestration](../guide/orchestration.md)


# Runbook: parsers

# Runbook: Parsers

> Auto-generated from `./src/parsers/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/parsers`  ·  **Public symbols:** 9  ·  **Guide:** [/guide/output-parsers](../guide/output-parsers.md)

## What it is
`personaforge/parsers` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import { StringOutputParser, JsonOutputParser, CsvListParser } from 'personaforge/parsers';
```

## Public API surface
- **Classes** — `StringOutputParser`, `JsonOutputParser`, `CsvListParser`, `RegexParser`, `OutputFixingParser`, `RetryWithErrorParser`, `ParseError`
- **Interfaces** — `OutputParser`, `JsonOutputParserOptions`

## Minimal use
Real example from the output-parsers guide:

```
import {
  StringOutputParser, JsonOutputParser, CsvListParser, RegexParser,
  OutputFixingParser, RetryWithErrorParser, ParseError,
} from 'personaforge/parsers';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/parsers` with no missing-module error.
- Runtime: `node -e "import('personaforge/parsers').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/output-parsers](../guide/output-parsers.md).

## Common failures
- `Cannot find module 'personaforge/parsers'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/output-parsers](../guide/output-parsers.md)


# Runbook: planner

# Runbook: Planner

> Auto-generated from `./src/planner/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/planner`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/planner](../guide/planner.md)

## What it is
`personaforge/planner` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/planner';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
This entry exposes types/interfaces only. Import the symbols you need for typing:

```
import 'personaforge/planner';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/planner` with no missing-module error.
- Runtime: `node -e "import('personaforge/planner').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/planner](../guide/planner.md).

## Common failures
- `Cannot find module 'personaforge/planner'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/planner](../guide/planner.md)


# Runbook: playground

# Runbook: Playground

> Auto-generated from `./src/playground/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/playground`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/websocket](../guide/websocket.md)

## What it is
`personaforge/playground` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/playground';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
This entry exposes types/interfaces only. Import the symbols you need for typing:

```
import 'personaforge/playground';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/playground` with no missing-module error.
- Runtime: `node -e "import('personaforge/playground').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/websocket](../guide/websocket.md).

## Common failures
- `Cannot find module 'personaforge/playground'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/websocket](../guide/websocket.md)


# Runbook: plugins

# Runbook: Plugins

> Auto-generated from `./src/plugins/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/plugins`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/plugins](../guide/plugins.md)

## What it is
`personaforge/plugins` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/plugins';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
Real example from the plugins guide:

```
import { createAgent } from 'personaforge';
import {
  createPluginRegistry,
  createLoggingPlugin,
  createRateLimitPlugin,
} from 'personaforge/plugins';

const plugins = createPluginRegistry();

plugins.register(createLoggingPlugin());
plugins.register(createRateLimitPlugin({ maxRpm: 60 }));

const agent = createAgent({
  name: 'my-agent',
  instructions: '...',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
});

// There is no `plugins` option on createAgent — a registry is applied
// manually around each run. `runBeforeHooks` folds every plugin's beforeRun
// over the input (in registration order) and may transform it:
const context = { agentId: 'my-agent', logger: console, metadata: {} };
const input = await plugins.runBeforeHooks({ prompt: 'Summarize the latest report.' }, context);

const result = await agent.run(input.prompt);

// Collect the combined tool middleware from every plugin. Run the after /
// error hooks with `plugins.runAfterHooks(output, context)` and
// `plugins.runErrorHooks(error, context)`.
const toolMiddleware = plugins.getToolMiddleware();
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/plugins` with no missing-module error.
- Runtime: `node -e "import('personaforge/plugins').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/plugins](../guide/plugins.md).

## Common failures
- `Cannot find module 'personaforge/plugins'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/plugins](../guide/plugins.md)


# Runbook: processors

# Runbook: Processors

> Auto-generated from `./src/processors/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/processors`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/processors](../guide/processors.md)

## What it is
`personaforge/processors` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/processors';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
Real example from the processors guide:

```
import { agent } from 'personaforge';
import {
  TokenLimiter,
  PIIDetector,
  ModerationProcessor,
  PromptInjectionDetector,
} from 'personaforge/processors';

const bot = agent({
  instructions: 'You are a helpful assistant.',
  inputProcessors: [
    new TokenLimiter(64_000),                       // cap input size
    new PIIDetector({ strategy: 'redact' }),        // redact PII
    new PromptInjectionDetector({ strategy: 'block' }),
    new ModerationProcessor({ strategy: 'block' }), // content moderation
  ],
});
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/processors` with no missing-module error.
- Runtime: `node -e "import('personaforge/processors').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/processors](../guide/processors.md).

## Common failures
- `Cannot find module 'personaforge/processors'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/processors](../guide/processors.md)


# Runbook: production

# Runbook: Production

> Auto-generated from `./src/production/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/production`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/production](../guide/production.md)

## What it is
`personaforge/production` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/production';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
Real example from the production guide:

```
import { createAgent } from 'personaforge';
import { withResilience } from 'personaforge/production';

const agent = createAgent({
  name: 'production-agent',
  instructions: 'You are a customer service assistant.',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
});

const resilientAgent = withResilience(agent, {
  circuitBreaker: {
    failureThreshold: 5,      // open after 5 failures
    resetTimeoutMs: 30_000,   // retry after 30s
  },
  rateLimit: { maxRpm: 60 },  // max requests per minute
  healthCheck: true,
  gracefulShutdown: true,
  retry: { maxRetries: 2, backoffMs: 500 },
});

// Use exactly like a regular agent
const result = await resilientAgent.run('Help me with my order.', {
  sessionId: 'session-1',
  userId: 'user-42',
  runId: 'run-abc',       // used for idempotency
});
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/production` with no missing-module error.
- Runtime: `node -e "import('personaforge/production').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/production](../guide/production.md).

## Common failures
- `Cannot find module 'personaforge/production'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/production](../guide/production.md)


# Runbook: providers

# Runbook: Providers

> Auto-generated from `./src/providers/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/providers`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/providers](../guide/providers.md)

## What it is
`personaforge/providers` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/providers';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
This entry exposes types/interfaces only. Import the symbols you need for typing:

```
import 'personaforge/providers';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/providers` with no missing-module error.
- Runtime: `node -e "import('personaforge/providers').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/providers](../guide/providers.md).

## Common failures
- `Cannot find module 'personaforge/providers'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/providers](../guide/providers.md)


# Runbook: reasoning

# Runbook: Reasoning

> Auto-generated from `./src/reasoning/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/reasoning`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/reasoning](../guide/reasoning.md)

## What it is
`personaforge/reasoning` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/reasoning';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
This entry exposes types/interfaces only. Import the symbols you need for typing:

```
import 'personaforge/reasoning';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/reasoning` with no missing-module error.
- Runtime: `node -e "import('personaforge/reasoning').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/reasoning](../guide/reasoning.md).

## Common failures
- `Cannot find module 'personaforge/reasoning'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/reasoning](../guide/reasoning.md)


# Runbook: registry

# Runbook: Registry

> Auto-generated from `./src/registry/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/registry`  ·  **Public symbols:** 4  ·  **Guide:** [/guide/registry](../guide/registry.md)

## What it is
`personaforge/registry` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import { createAgentRegistry, AgentRegistry } from 'personaforge/registry';
```

## Public API surface
- **Factories / functions** — `createAgentRegistry`
- **Classes** — `AgentRegistry`
- **Interfaces** — `AgentRecord`, `AgentRegistryEntry`

## Minimal use
Real example from the registry guide:

```
import { createAgentRegistry } from 'personaforge/registry';
import { agent } from 'personaforge';

const registry = createAgentRegistry();

registry.register({
  name: 'translator',
  description: 'Translate text into another language',
  tags: ['language', 'nlp'],
  agent: agent('You translate text.'),
});

registry.register({
  name: 'summarizer',
  description: 'Summarize long documents',
  tags: ['nlp', 'summarization'],
  agent: agent('You summarize documents.'),
});

// O(1) lookup by name
const t = registry.get('translator');              // AgentRecord

// Case-insensitive discovery across name/description/tags
const matches = registry.search('translate');      // → [translator record]
const scoped = registry.search('nlp');             // → [translator, summarizer]

// Delegate to any agent as an LLM tool
const translateTool = registry.asTool('translator');

// Or expose every registered agent as a delegation toolkit:
const tools = registry.toTools();                  // one tool per agent
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/registry` with no missing-module error.
- Runtime: `node -e "import('personaforge/registry').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/registry](../guide/registry.md).

## Common failures
- `Cannot find module 'personaforge/registry'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/registry](../guide/registry.md)


# Runbook: router

# Runbook: Router

> Auto-generated from `./src/router/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/router`  ·  **Public symbols:** 5  ·  **Guide:** [/guide/llm-router](../guide/llm-router.md)

## What it is
`personaforge/router` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import { createCostOptimizedRouter } from 'personaforge/router';
```

## Public API surface
- **Factories / functions** — `createCostOptimizedRouter`
- **Constants** — `DEFAULT_COSTS`
- **Interfaces** — `ModelCost`, `RouterOptions`, `RoutingDecision`

## Minimal use
Real example from the llm-router guide:

```
import { createCostOptimizedRouter, createQualityFirstRouter, createSpeedOptimizedRouter } from 'personaforge';

const cheap   = createCostOptimizedRouter(entries);
const quality = createQualityFirstRouter(entries);
const fast    = createSpeedOptimizedRouter(entries);
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/router` with no missing-module error.
- Runtime: `node -e "import('personaforge/router').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/llm-router](../guide/llm-router.md).

## Common failures
- `Cannot find module 'personaforge/router'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/llm-router](../guide/llm-router.md)


# Runbook: runnable

# Runbook: Runnable

> Auto-generated from `./src/runnable/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/runnable`  ·  **Public symbols:** 6  ·  **Guide:** [/guide/runnable](../guide/runnable.md)

## What it is
`personaforge/runnable` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import { Runnable, RunnableLambda, RunnableSequence } from 'personaforge/runnable';
```

## Public API surface
- **Classes** — `Runnable`, `RunnableLambda`, `RunnableSequence`, `RunnableParallel`, `RunnablePassthrough`
- **Interfaces** — `RunnableConfig`

## Minimal use
Real example from the runnable guide:

```
import {
  Runnable, RunnableLambda, RunnableSequence,
  RunnableParallel, RunnablePassthrough,
} from 'personaforge/runnable';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/runnable` with no missing-module error.
- Runtime: `node -e "import('personaforge/runnable').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/runnable](../guide/runnable.md).

## Common failures
- `Cannot find module 'personaforge/runnable'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/runnable](../guide/runnable.md)


# Runbook: runtime

# Runbook: Runtime

> Auto-generated from `./src/runtime/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/runtime`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/production](../guide/production.md)

## What it is
`personaforge/runtime` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/runtime';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
This entry exposes types/interfaces only. Import the symbols you need for typing:

```
import 'personaforge/runtime';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/runtime` with no missing-module error.
- Runtime: `node -e "import('personaforge/runtime').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/production](../guide/production.md).

## Common failures
- `Cannot find module 'personaforge/runtime'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/production](../guide/production.md)


# Runbook: scheduler

# Runbook: Scheduler

> Auto-generated from `./src/scheduler/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/scheduler`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/scheduler](../guide/scheduler.md)

## What it is
`personaforge/scheduler` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/scheduler';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
Real example from the scheduler guide:

```
import { createAgent } from 'personaforge';
import { ScheduleManager } from 'personaforge/scheduler';

const agent = createAgent({
  name: 'daily-reporter',
  instructions: 'Generate a concise daily business summary.',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
});

const scheduler = new ScheduleManager();

// Register a handler function by key
scheduler.register('daily-report', async () => {
  const result = await agent.run('Generate the daily business summary for today.');
  await saveReport(result.text);
  console.log('Daily report saved.');
});

// Create a schedule — create() returns the new schedule's id (a string).
const id = await scheduler.create({
  name: 'Daily Business Report',
  cronExpr: '0 8 * * *',        // 08:00 every day (evaluated in UTC)
  endpoint: 'daily-report',     // matches the registered handler key
  enabled: true,
  maxRetries: 3,
  retryDelaySeconds: 300,
});

// Start the schedule runner (poll-based)
scheduler.start();

// Later, stop cleanly
process.on('SIGTERM', () => scheduler.stop());
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/scheduler` with no missing-module error.
- Runtime: `node -e "import('personaforge/scheduler').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/scheduler](../guide/scheduler.md).

## Common failures
- `Cannot find module 'personaforge/scheduler'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/scheduler](../guide/scheduler.md)


# Runbook: sdk

# Runbook: Sdk

> Auto-generated from `./src/sdk/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/sdk`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/getting-started](../guide/getting-started.md)

## What it is
`personaforge/sdk` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/sdk';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
This entry exposes types/interfaces only. Import the symbols you need for typing:

```
import 'personaforge/sdk';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/sdk` with no missing-module error.
- Runtime: `node -e "import('personaforge/sdk').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/getting-started](../guide/getting-started.md).

## Common failures
- `Cannot find module 'personaforge/sdk'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/getting-started](../guide/getting-started.md)


# Runbook: serve

# Runbook: Serve

> Auto-generated from `./src/serve/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/serve`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/websocket](../guide/websocket.md)

## What it is
`personaforge/serve` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/serve';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
This entry exposes types/interfaces only. Import the symbols you need for typing:

```
import 'personaforge/serve';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/serve` with no missing-module error.
- Runtime: `node -e "import('personaforge/serve').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/websocket](../guide/websocket.md).

## Common failures
- `Cannot find module 'personaforge/serve'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/websocket](../guide/websocket.md)


# Runbook: session

# Runbook: Session

> Auto-generated from `./src/session/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/session`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/session](../guide/session.md)

## What it is
`personaforge/session` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/session';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
This entry exposes types/interfaces only. Import the symbols you need for typing:

```
import 'personaforge/session';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/session` with no missing-module error.
- Runtime: `node -e "import('personaforge/session').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/session](../guide/session.md).

## Common failures
- `Cannot find module 'personaforge/session'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/session](../guide/session.md)


# Runbook: shared

# Runbook: Shared

> Auto-generated from `./src/shared/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/shared`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/getting-started](../guide/getting-started.md)

## What it is
`personaforge/shared` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/shared';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
This entry exposes types/interfaces only. Import the symbols you need for typing:

```
import 'personaforge/shared';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/shared` with no missing-module error.
- Runtime: `node -e "import('personaforge/shared').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/getting-started](../guide/getting-started.md).

## Common failures
- `Cannot find module 'personaforge/shared'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/getting-started](../guide/getting-started.md)


# Runbook: simulation

# Runbook: Simulation

> Auto-generated from `./src/simulation/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/simulation`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/eval](../guide/eval.md)

## What it is
`personaforge/simulation` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/simulation';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
This entry exposes types/interfaces only. Import the symbols you need for typing:

```
import 'personaforge/simulation';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/simulation` with no missing-module error.
- Runtime: `node -e "import('personaforge/simulation').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/eval](../guide/eval.md).

## Common failures
- `Cannot find module 'personaforge/simulation'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/eval](../guide/eval.md)


# Runbook: skills

# Runbook: Skills

> Auto-generated from `./src/skills/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/skills`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/skills](../guide/skills.md)

## What it is
`personaforge/skills` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/skills';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
Real example from the skills guide:

```
import { webResearchSkill } from 'personaforge/skills';

const agent = defineAgent('researcher')
  .instructions('Research questions using the web.')
  .model('openai:gpt-4o-mini')
  .skills([webResearchSkill])
  .build();

const result = await agent.run('What is the latest version of Node.js?');
// Agent will call fetch_page('https://nodejs.org/en/download/releases') internally
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/skills` with no missing-module error.
- Runtime: `node -e "import('personaforge/skills').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/skills](../guide/skills.md).

## Common failures
- `Cannot find module 'personaforge/skills'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/skills](../guide/skills.md)


# Runbook: storage

# Runbook: Storage

> Auto-generated from `./src/storage/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/storage`  ·  **Public symbols:** 6  ·  **Guide:** [/guide/storage](../guide/storage.md)

## What it is
`personaforge/storage` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import { createStorage, MemoryStorageAdapter, FileStorageAdapter } from 'personaforge/storage';
```

## Public API surface
- **Factories / functions** — `createStorage`
- **Classes** — `MemoryStorageAdapter`, `FileStorageAdapter`
- **Interfaces** — `StorageAdapter`, `Storage`, `StorageOptions`

## Minimal use
Real example from the storage guide:

```
import type { StorageAdapter } from 'personaforge';
import { S3Client, GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3';

class S3StorageAdapter implements StorageAdapter {
  private s3 = new S3Client({});
  private bucket = process.env.S3_BUCKET!;

  async get(key: string): Promise<string | undefined> {
    try {
      const res = await this.s3.send(new GetObjectCommand({ Bucket: this.bucket, Key: key }));
      return res.Body?.transformToString();
    } catch { return undefined; }
  }

  async set(key: string, value: string, ttl?: number): Promise<void> {
    await this.s3.send(new PutObjectCommand({ Bucket: this.bucket, Key: key, Body: value }));
  }

  async delete(key: string): Promise<void> { /* ... */ }
  async list(prefix?: string): Promise<string[]> { /* ... */ return []; }
  async has(key: string): Promise<boolean> { /* ... */ return false; }
}

const store = createStorage({ adapter: new S3StorageAdapter() });
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/storage` with no missing-module error.
- Runtime: `node -e "import('personaforge/storage').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/storage](../guide/storage.md).

## Common failures
- `Cannot find module 'personaforge/storage'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/storage](../guide/storage.md)


# Runbook: streaming

# Runbook: Streaming

> Auto-generated from `./src/streaming/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/streaming`  ·  **Public symbols:** 11  ·  **Guide:** [/guide/stream-utils](../guide/stream-utils.md)

## What it is
`personaforge/streaming` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import { createStreamableRun, StreamEventBus, StreamContext } from 'personaforge/streaming';
```

## Public API surface
- **Factories / functions** — `createStreamableRun`
- **Classes** — `StreamEventBus`, `StreamContext`
- **Interfaces** — `ValueEvent`, `UpdateEvent`, `TokenEvent`, `ToolCallEvent`, `DebugEvent`, `CustomEvent`
- **Types** — `StreamMode`, `StreamEvent`

## Minimal use
```
import { createStreamableRun, StreamEventBus, StreamContext } from 'personaforge/streaming';

// `createStreamableRun` is the primary entry for this feature.
// See the type signature for full options.
const result = createStreamableRun(/* opts */);
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/streaming` with no missing-module error.
- Runtime: `node -e "import('personaforge/streaming').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/stream-utils](../guide/stream-utils.md).

## Common failures
- `Cannot find module 'personaforge/streaming'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/stream-utils](../guide/stream-utils.md)


# Runbook: structured

# Runbook: Structured

> Auto-generated from `./src/structured/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/structured`  ·  **Public symbols:** 8  ·  **Guide:** [/guide/structured-output](../guide/structured-output.md)

## What it is
`personaforge/structured` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import { detectProviderKind, generateStructured } from 'personaforge/structured';
```

## Public API surface
- **Factories / functions** — `detectProviderKind`, `generateStructured`
- **Interfaces** — `JsonSchema`, `StructuredSchema`, `StructuredOutputOptions`, `StructuredOutputResult`
- **Types** — `AnyStructuredSchema`, `ProviderKind`

## Minimal use
Real example from the structured-output guide:

```
import { detectProviderKind } from 'personaforge/structured';

const kind = detectProviderKind(llm);
// 'openai' | 'anthropic' | 'gemini' | 'bedrock' | 'unknown'
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/structured` with no missing-module error.
- Runtime: `node -e "import('personaforge/structured').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/structured-output](../guide/structured-output.md).

## Common failures
- `Cannot find module 'personaforge/structured'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/structured-output](../guide/structured-output.md)


# Runbook: system

# Runbook: System

> Auto-generated from `./src/system/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/system`  ·  **Public symbols:** 0

## What it is
`personaforge/system` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/system';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
This entry exposes types/interfaces only. Import the symbols you need for typing:

```
import 'personaforge/system';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/system` with no missing-module error.
- Runtime: `node -e "import('personaforge/system').then(m => console.log(Object.keys(m)))"` lists the exports above.

## Common failures
- `Cannot find module 'personaforge/system'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)


# Runbook: test

# Runbook: Test

> Auto-generated from `./src/test.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/test`  ·  **Public symbols:** 2  ·  **Guide:** [/guide/eval](../guide/eval.md)

## What it is
`personaforge/test` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import { mockAgent, scenario } from 'personaforge/test';
```

## Public API surface
- **Factories / functions** — `mockAgent`, `scenario`

## Minimal use
```
import { mockAgent, scenario } from 'personaforge/test';

// `mockAgent` is the primary entry for this feature.
// See the type signature for full options.
const result = mockAgent(/* opts */);
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/test` with no missing-module error.
- Runtime: `node -e "import('personaforge/test').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/eval](../guide/eval.md).

## Common failures
- `Cannot find module 'personaforge/test'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/eval](../guide/eval.md)


# Runbook: test-utils

# Runbook: Test Utils

> Auto-generated from `./src/test-utils/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/test-utils`  ·  **Public symbols:** 15  ·  **Guide:** [/guide/eval](../guide/eval.md)

## What it is
`personaforge/test-utils` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import { createMockLLM, createMockAgent, runScenario } from 'personaforge/test-utils';
```

## Public API surface
- **Factories / functions** — `createMockLLM`, `createMockAgent`, `runScenario`
- **Classes** — `MockLLMProvider`
- **Interfaces** — `AgentRunResult`, `MockableAgent`, `MockLLMCall`, `MockLLMOptions`, `MockLLMHandle`, `MockAgentRun`, `MockAgentOptions`, `MockAgentHandle`, `ScenarioStep`, `ScenarioStepResult`, `ScenarioResult`

## Minimal use
```
import { createMockLLM, createMockAgent, runScenario } from 'personaforge/test-utils';

// `createMockLLM` is the primary entry for this feature.
// See the type signature for full options.
const result = createMockLLM(/* opts */);
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/test-utils` with no missing-module error.
- Runtime: `node -e "import('personaforge/test-utils').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/eval](../guide/eval.md).

## Common failures
- `Cannot find module 'personaforge/test-utils'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/eval](../guide/eval.md)


# Runbook: test-utils-conformance

# Runbook: Test Utils: Conformance

> Auto-generated from `./src/test-utils/conformance.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/test-utils/conformance`  ·  **Public symbols:** 10  ·  **Guide:** [/guide/eval](../guide/eval.md)

## What it is
`personaforge/test-utils/conformance` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import { runSessionStoreConformance, runMemoryStoreConformance, runProviderConformance } from 'personaforge/test-utils/conformance';
```

## Public API surface
- **Factories / functions** — `runSessionStoreConformance`, `runMemoryStoreConformance`, `runProviderConformance`, `runVectorStoreConformance`, `runToolConformance`, `runKVStoreConformance`
- **Interfaces** — `Assertion`, `TestRunner`, `VectorStoreAdapter`, `KVStoreLike`

## Minimal use
```
import { runSessionStoreConformance, runMemoryStoreConformance, runProviderConformance } from 'personaforge/test-utils/conformance';

// `runSessionStoreConformance` is the primary entry for this feature.
// See the type signature for full options.
const result = runSessionStoreConformance(/* opts */);
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/test-utils/conformance` with no missing-module error.
- Runtime: `node -e "import('personaforge/test-utils/conformance').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/eval](../guide/eval.md).

## Common failures
- `Cannot find module 'personaforge/test-utils/conformance'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/eval](../guide/eval.md)


# Runbook: testing

# Runbook: Testing

> Auto-generated from `./src/testing/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/testing`  ·  **Public symbols:** 0

## What it is
`personaforge/testing` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/testing';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
This entry exposes types/interfaces only. Import the symbols you need for typing:

```
import 'personaforge/testing';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/testing` with no missing-module error.
- Runtime: `node -e "import('personaforge/testing').then(m => console.log(Object.keys(m)))"` lists the exports above.

## Common failures
- `Cannot find module 'personaforge/testing'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)


# Runbook: tool

# Runbook: Tool

> Auto-generated from `./src/tool.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/tool`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/tools](../guide/tools.md)

## What it is
`personaforge/tool` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/tool';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
Real example from the tools guide:

```
import { extendTool, wrapTool, pipeTools } from 'personaforge/tool';

// Normalise inputs and trim results around an existing tool
const reliableSearch = extendTool(searchTool, {
  name: 'reliable_search',
  transformInput: (params) => ({ ...params, query: params.query.trim() }),
  transformOutput: (results) => (Array.isArray(results) ? results.slice(0, 3) : results),
  timeoutMs: 10_000,
});

// Wrap with a middleware pipeline: (params, ctx, next)
const wrappedSearch = wrapTool(searchTool, [
  async (params, ctx, next) => {
    const sanitised = { ...params, query: params.query.trim() };
    const result = await next(sanitised, ctx);
    return { ...result, source: 'search' };
  },
]);

// Chain tools: output of tool1 becomes input of tool2
const pipeline = pipeTools(fetchPageTool, summariseTool, {
  name: 'fetch_and_summarise',
  description: 'Fetch a page then summarise it.',
  adapter: (page) => ({ text: page.body }),
});
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/tool` with no missing-module error.
- Runtime: `node -e "import('personaforge/tool').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/tools](../guide/tools.md).

## Common failures
- `Cannot find module 'personaforge/tool'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/tools](../guide/tools.md)


# Runbook: toolkits

# Runbook: Toolkits

> Auto-generated from `./src/toolkits/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/toolkits`  ·  **Public symbols:** 9  ·  **Guide:** [/guide/toolkits](../guide/toolkits.md)

## What it is
`personaforge/toolkits` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import { sqlToolkit, httpToolkit, fileToolkit } from 'personaforge/toolkits';
```

## Public API surface
- **Factories / functions** — `sqlToolkit`, `httpToolkit`, `fileToolkit`, `combineToolkits`
- **Interfaces** — `Tool`, `PromptedToolkit`, `SqlToolkitConfig`, `HttpToolkitConfig`, `FileToolkitConfig`

## Minimal use
Real example from the toolkits guide:

```
const kit = sqlToolkit({
  execute: async (q) => db.query(q),
  listTables: async () => ['users', 'orders'],
  describeTable: async (t) => db.getSchema(t),
});

const analyst = agent({
  name: 'analyst',
  instructions: [baseInstructions, kit.promptFragment].join('\n'),
  tools: kit.tools,   // sql_list_tables, sql_describe_table, sql_query
});
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/toolkits` with no missing-module error.
- Runtime: `node -e "import('personaforge/toolkits').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/toolkits](../guide/toolkits.md).

## Common failures
- `Cannot find module 'personaforge/toolkits'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/toolkits](../guide/toolkits.md)


# Runbook: tools

# Runbook: Tools

> Auto-generated from `./src/tools/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/tools`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/tools](../guide/tools.md)

## What it is
`personaforge/tools` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/tools';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
This entry exposes types/interfaces only. Import the symbols you need for typing:

```
import 'personaforge/tools';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/tools` with no missing-module error.
- Runtime: `node -e "import('personaforge/tools').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/tools](../guide/tools.md).

## Common failures
- `Cannot find module 'personaforge/tools'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/tools](../guide/tools.md)


# Runbook: tools-ai

# Runbook: Tools: Ai

> Auto-generated from `./src/tools/ai/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/tools/ai`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/tools](../guide/tools.md)

## What it is
`personaforge/tools/ai` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/tools/ai';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
This entry exposes types/interfaces only. Import the symbols you need for typing:

```
import 'personaforge/tools/ai';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/tools/ai` with no missing-module error.
- Runtime: `node -e "import('personaforge/tools/ai').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/tools](../guide/tools.md).

## Common failures
- `Cannot find module 'personaforge/tools/ai'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/tools](../guide/tools.md)


# Runbook: tools-communication

# Runbook: Tools: Communication

> Auto-generated from `./src/tools/communication/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/tools/communication`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/tools](../guide/tools.md)

## What it is
`personaforge/tools/communication` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/tools/communication';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
Real example from the tools guide:

```
import {
  SlackToolkit,
  GmailToolkit,
  EmailToolkit,
  DiscordToolkit,
  TelegramTool,
  TwilioToolkit,
  ZoomToolkit,
  ResendToolkit,
} from 'personaforge/tools/communication';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/tools/communication` with no missing-module error.
- Runtime: `node -e "import('personaforge/tools/communication').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/tools](../guide/tools.md).

## Common failures
- `Cannot find module 'personaforge/tools/communication'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/tools](../guide/tools.md)


# Runbook: tools-core

# Runbook: Tools: Core

> Auto-generated from `./src/tools/core/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/tools/core`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/tools](../guide/tools.md)

## What it is
`personaforge/tools/core` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/tools/core';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
This entry exposes types/interfaces only. Import the symbols you need for typing:

```
import 'personaforge/tools/core';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/tools/core` with no missing-module error.
- Runtime: `node -e "import('personaforge/tools/core').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/tools](../guide/tools.md).

## Common failures
- `Cannot find module 'personaforge/tools/core'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/tools](../guide/tools.md)


# Runbook: tools-crm

# Runbook: Tools: Crm

> Auto-generated from `./src/tools/crm/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/tools/crm`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/tools](../guide/tools.md)

## What it is
`personaforge/tools/crm` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/tools/crm';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
This entry exposes types/interfaces only. Import the symbols you need for typing:

```
import 'personaforge/tools/crm';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/tools/crm` with no missing-module error.
- Runtime: `node -e "import('personaforge/tools/crm').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/tools](../guide/tools.md).

## Common failures
- `Cannot find module 'personaforge/tools/crm'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/tools](../guide/tools.md)


# Runbook: tools-data

# Runbook: Tools: Data

> Auto-generated from `./src/tools/data/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/tools/data`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/tools](../guide/tools.md)

## What it is
`personaforge/tools/data` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/tools/data';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
Real example from the tools guide:

```
import {
  BigQueryToolkit,
  CsvToolkit,
  DatabaseToolkit,
  Neo4jToolkit,
  RedisToolkit,
} from 'personaforge/tools/data';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/tools/data` with no missing-module error.
- Runtime: `node -e "import('personaforge/tools/data').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/tools](../guide/tools.md).

## Common failures
- `Cannot find module 'personaforge/tools/data'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/tools](../guide/tools.md)


# Runbook: tools-devtools

# Runbook: Tools: Devtools

> Auto-generated from `./src/tools/devtools/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/tools/devtools`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/tools](../guide/tools.md)

## What it is
`personaforge/tools/devtools` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/tools/devtools';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
Real example from the tools guide:

```
import {
  GitHubToolkit,
  GitLabToolkit,
  DockerToolkit,
  E2BToolkit,        // sandboxed code execution
  CodeExecToolkit,   // local code execution
} from 'personaforge/tools/devtools';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/tools/devtools` with no missing-module error.
- Runtime: `node -e "import('personaforge/tools/devtools').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/tools](../guide/tools.md).

## Common failures
- `Cannot find module 'personaforge/tools/devtools'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/tools](../guide/tools.md)


# Runbook: tools-finance

# Runbook: Tools: Finance

> Auto-generated from `./src/tools/finance/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/tools/finance`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/tools](../guide/tools.md)

## What it is
`personaforge/tools/finance` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/tools/finance';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
Real example from the tools guide:

```
import {
  StripeToolkit,
  YFinanceTool,      // Yahoo Finance market data
} from 'personaforge/tools/finance';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/tools/finance` with no missing-module error.
- Runtime: `node -e "import('personaforge/tools/finance').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/tools](../guide/tools.md).

## Common failures
- `Cannot find module 'personaforge/tools/finance'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/tools](../guide/tools.md)


# Runbook: tools-mcp

# Runbook: Tools: Mcp

> Auto-generated from `./src/tools/mcp/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/tools/mcp`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/mcp](../guide/mcp.md)

## What it is
`personaforge/tools/mcp` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/tools/mcp';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
This entry exposes types/interfaces only. Import the symbols you need for typing:

```
import 'personaforge/tools/mcp';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/tools/mcp` with no missing-module error.
- Runtime: `node -e "import('personaforge/tools/mcp').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/mcp](../guide/mcp.md).

## Common failures
- `Cannot find module 'personaforge/tools/mcp'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/mcp](../guide/mcp.md)


# Runbook: tools-media

# Runbook: Tools: Media

> Auto-generated from `./src/tools/media/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/tools/media`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/tools](../guide/tools.md)

## What it is
`personaforge/tools/media` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/tools/media';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
This entry exposes types/interfaces only. Import the symbols you need for typing:

```
import 'personaforge/tools/media';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/tools/media` with no missing-module error.
- Runtime: `node -e "import('personaforge/tools/media').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/tools](../guide/tools.md).

## Common failures
- `Cannot find module 'personaforge/tools/media'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/tools](../guide/tools.md)


# Runbook: tools-memory

# Runbook: Tools: Memory

> Auto-generated from `./src/tools/memory/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/tools/memory`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/tools](../guide/tools.md)

## What it is
`personaforge/tools/memory` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/tools/memory';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
This entry exposes types/interfaces only. Import the symbols you need for typing:

```
import 'personaforge/tools/memory';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/tools/memory` with no missing-module error.
- Runtime: `node -e "import('personaforge/tools/memory').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/tools](../guide/tools.md).

## Common failures
- `Cannot find module 'personaforge/tools/memory'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/tools](../guide/tools.md)


# Runbook: tools-productivity

# Runbook: Tools: Productivity

> Auto-generated from `./src/tools/productivity/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/tools/productivity`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/tools](../guide/tools.md)

## What it is
`personaforge/tools/productivity` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/tools/productivity';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
Real example from the tools guide:

```
import {
  JiraToolkit,
  NotionToolkit,
  ConfluenceToolkit,
  LinearToolkit,
  ClickUpToolkit,
  GoogleDriveToolkit,
  GoogleSheetsToolkit,
  GoogleCalendarToolkit,
} from 'personaforge/tools/productivity';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/tools/productivity` with no missing-module error.
- Runtime: `node -e "import('personaforge/tools/productivity').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/tools](../guide/tools.md).

## Common failures
- `Cannot find module 'personaforge/tools/productivity'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/tools](../guide/tools.md)


# Runbook: tools-scraping

# Runbook: Tools: Scraping

> Auto-generated from `./src/tools/scraping/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/tools/scraping`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/tools](../guide/tools.md)

## What it is
`personaforge/tools/scraping` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/tools/scraping';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
This entry exposes types/interfaces only. Import the symbols you need for typing:

```
import 'personaforge/tools/scraping';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/tools/scraping` with no missing-module error.
- Runtime: `node -e "import('personaforge/tools/scraping').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/tools](../guide/tools.md).

## Common failures
- `Cannot find module 'personaforge/tools/scraping'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/tools](../guide/tools.md)


# Runbook: tools-search

# Runbook: Tools: Search

> Auto-generated from `./src/tools/search/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/tools/search`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/tools](../guide/tools.md)

## What it is
`personaforge/tools/search` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/tools/search';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
Real example from the tools guide:

```
import {
  TavilySearchTool,       // AI-optimised web search
  BraveSearchTool,        // privacy-first web search
  ExaSearchTool,          // neural search
  PerplexitySearchTool,   // web-grounded LLM search
  ArxivSearchTool,        // academic papers
  PubMedSearchTool,       // biomedical papers
  YouTubeSearchTool,
  RedditSearchTool,
  OpenWeatherToolkit,
  GoogleMapsToolkit,
} from 'personaforge/tools/search';

const agent = createAgent({
  name: 'researcher',
  instructions: 'Research the topic thoroughly.',
  model: 'gpt-4o',
  apiKey: process.env.OPENAI_API_KEY!,
  tools: [new TavilySearchTool({ apiKey: process.env.TAVILY_API_KEY! })],
});
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/tools/search` with no missing-module error.
- Runtime: `node -e "import('personaforge/tools/search').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/tools](../guide/tools.md).

## Common failures
- `Cannot find module 'personaforge/tools/search'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/tools](../guide/tools.md)


# Runbook: tools-shell

# Runbook: Tools: Shell

> Auto-generated from `./src/tools/shell.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/tools/shell`  ·  **Public symbols:** 3  ·  **Guide:** [/guide/tools](../guide/tools.md)

## What it is
`personaforge/tools/shell` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import { createShellTool } from 'personaforge/tools/shell';
```

## Public API surface
- **Factories / functions** — `createShellTool`
- **Constants** — `shell`
- **Interfaces** — `ShellToolOptions`

## Minimal use
Real example from the tools guide:

```
import {
  httpClient,        // HTTP requests
  fileSystem,        // read/write local files
  browserTool,       // headless browser
  createShellTool,   // run shell commands
} from 'personaforge/tool';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/tools/shell` with no missing-module error.
- Runtime: `node -e "import('personaforge/tools/shell').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/tools](../guide/tools.md).

## Common failures
- `Cannot find module 'personaforge/tools/shell'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/tools](../guide/tools.md)


# Runbook: tools-social

# Runbook: Tools: Social

> Auto-generated from `./src/tools/social/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/tools/social`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/tools](../guide/tools.md)

## What it is
`personaforge/tools/social` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/tools/social';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
This entry exposes types/interfaces only. Import the symbols you need for typing:

```
import 'personaforge/tools/social';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/tools/social` with no missing-module error.
- Runtime: `node -e "import('personaforge/tools/social').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/tools](../guide/tools.md).

## Common failures
- `Cannot find module 'personaforge/tools/social'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/tools](../guide/tools.md)


# Runbook: tools-utils

# Runbook: Tools: Utils

> Auto-generated from `./src/tools/utils/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/tools/utils`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/tools](../guide/tools.md)

## What it is
`personaforge/tools/utils` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/tools/utils';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
This entry exposes types/interfaces only. Import the symbols you need for typing:

```
import 'personaforge/tools/utils';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/tools/utils` with no missing-module error.
- Runtime: `node -e "import('personaforge/tools/utils').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/tools](../guide/tools.md).

## Common failures
- `Cannot find module 'personaforge/tools/utils'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/tools](../guide/tools.md)


# Runbook: video

# Runbook: Video

> Auto-generated from `./src/video/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/video`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/video](../guide/video.md)

## What it is
`personaforge/video` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/video';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
This entry exposes types/interfaces only. Import the symbols you need for typing:

```
import 'personaforge/video';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/video` with no missing-module error.
- Runtime: `node -e "import('personaforge/video').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/video](../guide/video.md).

## Common failures
- `Cannot find module 'personaforge/video'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/video](../guide/video.md)


# Runbook: voice

# Runbook: Voice

> Auto-generated from `./src/voice/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/voice`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/voice](../guide/voice.md)

## What it is
`personaforge/voice` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/voice';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
This entry exposes types/interfaces only. Import the symbols you need for typing:

```
import 'personaforge/voice';
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/voice` with no missing-module error.
- Runtime: `node -e "import('personaforge/voice').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/voice](../guide/voice.md).

## Common failures
- `Cannot find module 'personaforge/voice'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/voice](../guide/voice.md)


# Runbook: workflow

# Runbook: Workflow

> Auto-generated from `./src/workflow/index.ts`. Do not edit by hand — run `node scripts/gen-runbooks.mjs`.

**Import path:** `personaforge/workflow`  ·  **Public symbols:** 0  ·  **Guide:** [/guide/workflows](../guide/workflows.md)

## What it is
`personaforge/workflow` is a public entry point of personaforge. Import it directly; you only pull in this feature's code (subpath exports are tree-shakeable and optional native deps load lazily).

## Install
```
npm i personaforge
# or: bun add personaforge · pnpm add personaforge · yarn add personaforge
```

## Import
```
import 'personaforge/workflow';
```

## Public API surface
- _No named runtime exports; import for side effects or types._

## Minimal use
Real example from the workflows guide:

```
import { createGraph, DAGEngine } from 'personaforge/workflow';

const graph = createGraph('data-pipeline')
  .addNode('fetch', {
    kind: 'task',
    execute: async (ctx) => {
      const url = ctx.state.variables.url as string;
      return { data: await fetchData(url) };
    },
  })
  .addNode('transform', {
    kind: 'task',
    execute: async (ctx) => {
      const { data } = ctx.state.results['fetch'] as { data: unknown };
      return { transformed: transform(data) };
    },
  })
  .addNode('save', {
    kind: 'task',
    execute: async (ctx) => {
      const { transformed } = ctx.state.results['transform'] as { transformed: unknown };
      await saveToDatabase(transformed);
      return { saved: true };
    },
  })
  .chain('fetch', 'transform', 'save')  // linear shorthand for addEdge
  .build();

const engine = new DAGEngine(graph);
const result = await engine.execute({ variables: { url: 'https://api.example.com/data' } });
console.log(result.state.results);
```

## Verify it works
- Type-check: `npx tsc --noEmit` resolves `personaforge/workflow` with no missing-module error.
- Runtime: `node -e "import('personaforge/workflow').then(m => console.log(Object.keys(m)))"` lists the exports above.
- Behavior: follow the runnable example in [/guide/workflows](../guide/workflows.md).

## Common failures
- `Cannot find module 'personaforge/workflow'` — package not installed or stale build; run `npm i personaforge` and rebuild.
- `Cannot find module '<peer>'` at call time — this feature lazy-loads an optional native/SDK dep; install the one named in the error.
- Type errors after upgrade — check `CHANGELOG.md` for the symbol you import; names above are the current contract.

## Rollback
- Remove the import and the feature is gone from your bundle (subpaths are isolated; nothing else depends on importing it).
- Pin a known-good version: `npm i personaforge@<version>`.

## Related
- Full index: [/runbooks/](./index.md) · [llms.txt](../llms.txt)
- Concept guide: [/guide/workflows](../guide/workflows.md)