---
title: ReactiveAgentBuilder
description: Complete API reference for the ReactiveAgentBuilder.
---

The `ReactiveAgentBuilder` is the primary entry point for creating agents. It provides a fluent API for composing capabilities.

:::tip[Guided stacks]
For copy-paste **recipe chains** (minimal LLM, ReAct + tools, memory, streaming, serialization), see [Common builder stacks](/cookbook/builder-stacks/). For defaults and env vars in one table, see [Configuration](/reference/configuration/).
:::

:::note[Composition order: prefer `HarnessProfile` presets]
Per architecture model §11.1, the canonical composition order is:

1. **`HarnessProfile.lean()/balanced()/intelligent()`** — primary. One
   line that composes the registry's default-on capability set.
2. **`.compose(harness => ...)`** — advanced. For users overriding
   specific tags / phases / hooks.
3. **`.withX()` methods** — backward compatible and fully supported.
   Each composes cleanly with presets; reach for a preset when you want
   the whole default-on set in one line, or `.compose(...)` for a
   precise chokepoint. No breaking changes.

```typescript
import { ReactiveAgents, HarnessProfile } from 'reactive-agents'

const agent = await ReactiveAgents.create()
  .withName('research-agent')
  .withProvider('anthropic')
  .withProfile(HarnessProfile.balanced())  // ← canonical entry
  .withTools()
  .build()
```
:::

## Jump to section

| Category | Methods |
| -------- | ------- |
| [ReactiveAgents factory](#reactiveagents-factory) | `create`, `quick`, `fromConfig`, `fromJSON` |
| [Core Identity](#identity--prompts) | `withName`, `withPersona`, `withSystemPrompt`, `withEnvironment` |
| [Model & Provider](#model--provider) | `withModel`, `withProvider` |
| [Reasoning & Context](#execution) | `withReasoning`, `withMemory`, `withContextProfile`, `withMaxIterations`, `withMinIterations` |
| [Tools & MCP](#optional-features) | `withTools`, `withRequiredTools`, `withMCP`, `withMetaTools`, `withSkills`, `withAgentTool`, `withDynamicSubAgents`, `withRemoteAgent` |
| [Observability & Telemetry](#optional-features) | `withObservability`, `withTelemetry`, `withCortex`, `withStreaming`, `withLogging`, `withEvents` |
| [Safety & Resilience](#optional-features) | `withGuardrails`, `withKillSwitch`, `withBehavioralContracts`, `withVerification`, `withGrounding`, `withCircuitBreaker`, `withRateLimiting`, `withIdentity` |
| [Cost & Performance](#optional-features) | `withCostTracking`, `withModelRouting`, `withBudget`, `withModelPricing`, `withDynamicPricing`, `withCacheTimeout`, `withRetryPolicy`, `withTimeout`, `withLlmTimeout` |
| [Lifecycle & Hooks](#lifecycle) | `withHook`, `withHealthCheck`, `withErrorHandler`, `withFallbacks`, `withAudit` |
| [Advanced](#advanced) | `withOrchestration`, `withA2A`, `withGateway`, `withReactiveIntelligence`, `withPrompts`, `withInteraction`, `withDocuments`, `withTaskContext`, `withLayers` |
| [Building & Running](#build-methods) | `build`, `buildEffect`, `runOnce` |
| [Agent Methods](#reactiveagent) | `run`, `runStream`, `chat`, `session`, `health`, `cancel`, `pause`, `resume`, `dispose` |
| [Result Reference](#agentresult) | `AgentResult`, `AgentDebrief`, stream event types |

## `ReactiveAgents` factory

| API                                 | Description                                                                      |
| ----------------------------------- | -------------------------------------------------------------------------------- |
| `ReactiveAgents.create()`           | New empty builder (defaults: `name: "agent"`, `provider: "test"`).               |
| `ReactiveAgents.quick(options?)`    | Async — resolve provider + model + `maxIterations` from the environment and `build()` a ready-to-run agent in one call. |
| `ReactiveAgents.fromConfig(config)` | Async — rebuild a builder from an `AgentConfig` object (`agentConfigToBuilder`). |
| `ReactiveAgents.fromJSON(json)`     | Async — parse JSON → validate → same as `fromConfig`.                            |

```typescript
import { ReactiveAgents } from 'reactive-agents'
// or: import { ReactiveAgents } from "@reactive-agents/runtime";

const builder = ReactiveAgents.create()
```

### `ReactiveAgents.quick()`

The two-line first-touch entry point — returns a **built `ReactiveAgent`**, not a builder:

```typescript
const agent = await ReactiveAgents.quick()
const result = await agent.run('Say hello')
```

Every field resolves from an environment variable, then a sensible default, so `quick()` with no arguments works out of the box:

```typescript
interface QuickOptions {
    provider?: ProviderName // Default: REACTIVE_AGENTS_PROVIDER, else the first of anthropic/openai/gemini/groq/xai whose key is present, else "ollama"
    model?: string          // Default: REACTIVE_AGENTS_MODEL, else the provider's default model
    maxIterations?: number  // Default: REACTIVE_AGENTS_MAX_ITERATIONS, else 10
}
```

A misconfigured environment (e.g. missing key) warns at build and surfaces a clean typed error at `run()` time; use `ReactiveAgents.create()....withStrictValidation()` when you want a hard failure at build instead.

### Agent as Data (`toConfig` / serialization)

On a configured builder:

-   **`toConfig()`** → `AgentConfig` (plain object, JSON-serializable except documented exceptions).
-   Use **`agentConfigToJSON`** / **`agentConfigFromJSON`** from **`reactive-agents`** or **`@reactive-agents/runtime`** for string round-trips.

## Builder methods

All chain methods return `this` unless noted.

### Identity & prompts

| Method             | Signature                                   | Description                                                                                             |
| ------------------ | ------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| `withName`         | `(name: string) => this`                    | Display name / `agentId` basis                                                                          |
| `withPersona`      | `(persona: AgentPersona) => this`           | Structured steering: `{ name?, role?, background?, instructions?, tone? }`                              |
| `withSystemPrompt` | `(prompt: string) => this`                  | Custom system prompt; if persona is set, persona text is prepended                                      |
| `withEnvironment`  | `(context: Record<string, string>) => this` | Extra key/value context merged into the system prompt (framework already injects date/time/tz/platform) |

### Model & Provider

| Method         | Signature                                                                                    | Description                                                                |
| -------------- | -------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| `withModel`    | `(model: string) => this`                                                                    | Set the LLM model by name (e.g., `"claude-sonnet-4-6"`)             |
| `withModel`    | `(params: ModelParams) => this`                                                              | Set model with advanced parameters: `thinking`, `temperature`, `maxTokens`, `numCtx` |
| `withThinking` | `(options?: boolean \| ThinkingOptions) => this`                                             | Enable native thinking / reasoning mode with optional effort + budget. The rich-config home for thinking; `.withModel({ thinking })` remains the quick boolean. `true` / absent enables, `false` disables, or pass `{ effort, budgetTokens }`. Off unless enabled. |
| `withProvider` | `(provider: "anthropic" \| "openai" \| "ollama" \| "gemini" \| "groq" \| "xai" \| "litellm" \| "test") => this` | Set the LLM provider                                                       |

#### ModelParams

```typescript
interface ModelParams {
    model: string // Model identifier (provider-specific)
    thinking?: boolean // Enable thinking/reasoning mode (auto-detected if omitted)
    temperature?: number // Sampling temperature 0.0–1.0
    maxTokens?: number // Maximum output tokens
    numCtx?: number // Exact provider context window (Ollama num_ctx); ignored by providers without a context knob
}
```

```typescript
// String form — simple model selection
.withModel("claude-opus-4-8")

// ModelParams form — local model with thinking mode
.withModel({ model: "qwen3:14b", thinking: true, temperature: 0.7 })

// ModelParams form — cap token budget
.withModel({ model: "gpt-4o", maxTokens: 2048 })

// ModelParams form — pin the exact context window sent to the provider
.withModel({ model: "qwen3:14b", numCtx: 32768 })
```

`numCtx` overrides the assumed/maximum context length with the exact window the
provider receives. Honored by providers that expose a context-window knob
(Ollama maps it to `num_ctx`); cloud providers that don't expose one ignore it.
It is also a first-class [`AgentConfig`](/reference/configuration/) field, so it
round-trips through `toConfig()` / `fromJSON()` and the config-driven path.

#### ThinkingOptions

`.withThinking()` is the rich-config home for native reasoning mode across all
providers. `.withModel({ thinking: true })` remains the quick boolean shortcut.
Thinking stays **off unless explicitly enabled** — `undefined` never
auto-enables.

```typescript
interface ThinkingOptions {
    enabled?: boolean // Tri-state mirror of the thinking flag
    effort?: "low" | "medium" | "high" // OpenAI reasoning_effort; advisory for other providers
    budgetTokens?: number // Explicit thinking budget in tokens (still clamped)
}
```

```typescript
// Boolean form — enable / disable
.withThinking()            // enable
.withThinking(false)       // disable

// Rich form — effort + budget
.withThinking({ effort: "high", budgetTokens: 4096 })
```

### Memory

| Method       | Signature                                         | Description                                                                                                                                                                         |
| ------------ | ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `withMemory` | `(options?: MemoryOptions \| "1" \| "2") => this` | Enable memory — **OFF by default as of v0.12** (a bare build is stateless). Prefer `.withMemory()` or `.withMemory({ tier: "enhanced", ... })`. Strings `"1"` / `"2"` still work with a deprecation warning (`"1"` → standard, `"2"` → enhanced). Also enabled by `HarnessProfile.balanced()` / `.intelligent()`. |

#### MemoryOptions

| Field                 | Type                              | Default / notes                                                  |
| --------------------- | --------------------------------- | ---------------------------------------------------------------- |
| `tier`                | `"standard" \| "enhanced"`        | `"standard"` — enhanced = 4-layer memory + embeddings            |
| `dbPath`              | `string`                          | SQLite path (default under `.reactive-agents/memory/{agentId}/`) |
| `maxEntries`          | `number`                          | Compaction cap                                                   |
| `capacity`            | `number`                          | Working memory slots (default `7`)                               |
| `evictionPolicy`      | `"fifo" \| "lru" \| "importance"` | Working set eviction                                             |
| `retainDays`          | `number`                          | Episodic retention                                               |
| `importanceThreshold` | `number`                          | Semantic inclusion threshold                                     |

### Execution

| Method                 | Signature                                    | Description                                                                                      |
| ---------------------- | -------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| `withMaxIterations`    | `(n: number) => this`                        | Max agent loop iterations (default: 10)                                                          |
| `withMinIterations`    | `(n: number) => this`                        | Minimum iterations before `final-answer` is permitted — prevents fast-path exit on complex tasks |
| `withContextProfile`   | `(profile: Partial<ContextProfile>) => this` | Model-adaptive context overrides: compaction thresholds, tool result size limits, budget         |
| `withStrictValidation` | `() => this`                                 | Throw at build time if required config is missing (provider, model, etc.)                        |
| `withTimeout`          | `(ms: number) => this`                       | Execution timeout in milliseconds for the **whole agent run** (all iterations combined). Throws `TimeoutError` if exceeded |
| `withLlmTimeout`       | `(ms: number) => this`                       | Per-LLM-call timeout in milliseconds — bounds a **single provider request**, distinct from `withTimeout`. Honored by the Ollama/local provider (maps to `LLMConfig.ollamaTimeoutMs`; equivalent to the `OLLAMA_TIMEOUT_MS` env var but scoped to this agent — useful to tolerate cold model loads, e.g. `.withLlmTimeout(600_000)`). Hosted providers (Anthropic/OpenAI/Gemini) ignore it |
| `withRetryPolicy`      | `(policy: RetryPolicy) => this`              | Retry on transient LLM failures. `{ maxRetries: number, backoffMs: number }`                     |
| `withCacheTimeout`     | `(ms: number) => this`                       | Semantic cache TTL in milliseconds. Entries older than this are evicted                          |

#### ContextProfile fields

| Field                      | Type                                            | Description                                      |
| -------------------------- | ----------------------------------------------- | ------------------------------------------------ |
| `tier`                     | `"local" \| "mid" \| "large" \| "frontier"`     | Model tier — controls which defaults are applied |
| `budgetTokens`             | `number`                                        | Max tokens to include in the context window      |
| `toolResultMaxChars`       | `number`                                        | Truncate tool results beyond this length         |
| `compactionLevel`          | `"full" \| "summary" \| "grouped" \| "dropped"` | How aggressively to compact older steps          |
| `maxStepsBeforeCompaction` | `number`                                        | Steps to keep in full detail before compacting   |

```typescript
// Lean context for local small models
.withContextProfile({ tier: "local" })

// Manual overrides for a specific task
.withContextProfile({
  budgetTokens: 4000,
  toolResultMaxChars: 800,
  compactionLevel: "grouped",
})
```

See [Context Engineering](/guides/context-engineering/) for full tier defaults.

### Optional features

| Method                               | Description                                                                                                                                                                                                                                                                                                                                                                                                  |
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `withGuardrails(options?)`           | Toggle detectors: `{ injection?, pii?, toxicity?, customBlocklist? }`. All default **on** when guardrails are enabled.                                                                                                                                                                                                                                                                                       |
| `withKillSwitch()`                   | Pause / resume / stop / terminate via `KillSwitchService`                                                                                                                                                                                                                                                                                                                                                    |
| `withBehavioralContracts(contract)`  | Rules such as `deniedTools`, `allowedTools`, `maxIterations`, etc.                                                                                                                                                                                                                                                                                                                                           |
| `withContract(contract)`             | Declare a `TaskContract`: `{ prompt, tools: ToolRequirement[], fixtures?, modelFloor?, success }`. Required tools become an execute-time gate; forbidden tools are excluded from the tool schema. Validated at `build()`.                                                                                                                                                                                       |
| `withVerification(options?)`         | Post-output checks — toggles and thresholds: `semanticEntropy`, `factDecomposition`, `multiSource`, `hallucinationDetection`, `passThreshold`, …                                                                                                                                                                                                                                                             |
| `withGrounding(options)`             | Opt-in numeric evidence-grounding (off by default): `{ mode: "block" \| "warn", tolerance?, maxRetries? }`. Checks figures in the final answer against the full tool data with rounding tolerance. `warn` = advisory; `block` = one corrective retry then degrade to warn (never hard-fails). Scaffold-leak detection (`[STORED:]`/`_tool_result_N` echoed as the answer) is always-on, independent of this. |
| `withFabricationGuard(mode?)`        | Configure the always-on verifier check that rejects invented empirical performance measurements (benchmark timings, % speed-ups) absent from the tool-observation corpus. **On by default (`"block"`)** — no call needed for protection. Use this only to soften (`"warn"`, advisory) or disable (`"off"`). High-precision: only perf measurements are policed; counts, prices, and Big-O are ignored, and a claim grounded by a real benchmark/execution tool always passes. Also settable via `RA_FABRICATION_GUARD` env var (this method wins). `mode: "block" \| "warn" \| "off"`. |
| `withStallPolicy(policy)`            | Tune the stall / no-progress policy — how the harness reacts when the model ignores required-tool nudges. Sensible defaults apply when unset: tolerate **2** consecutive ignored nudges before fast-escalating (deliver accumulated artifacts, else fail) instead of looping to the full nudge cap, and **escalate** nudge wording on repeats. Bounds wasted iterations/tokens on stuck runs; legitimately-progressing runs are untouched (progress resets the ignored streak). `{ ignoredNudgeTolerance?, escalateNudgeContent? }`. |
| `withCostTracking(options?)`         | Budgets in USD: `{ perRequest?, perSession?, daily?, monthly? }` plus cost estimation / analytics                                                                                                                                                                                                                                                                                                            |
| `withBudget(limits)`                 | Hard in-loop killswitch: `{ tokenLimit?, costLimit? }`. Caps cumulative tokens / USD and stops the loop when hit — distinct from `withCostTracking()` accounting. Also set by `HarnessProfile` budget composition.                                                                                                                                                                                            |
| `withModelRouting(options?)`         | **Opt-in cost-aware model routing (off by default).** Routes each run to the cheapest *capable* model of the configured provider, picked by task complexity, on both the inline and reasoning paths. Stays within the provider's tiers (`haiku`/`sonnet`/`opus` cost ladder → the provider's models); capability-gated (never routes a large-input task below a model whose context window fits); advisory (degrades to the configured model on any error). `{ tierModels?: Partial<Record<"haiku"\|"sonnet"\|"opus", string>>, minTier? }`. |
| `withModelPricing(registry)`         | Per-model $/1M tokens: `{ "model-id": { input, output } }`                                                                                                                                                                                                                                                                                                                                                   |
| `withDynamicPricing(provider)`       | Remote pricing (`openRouterPricingProvider`, etc.) fetched at build time                                                                                                                                                                                                                                                                                                                                     |
| `withCircuitBreaker(config?)`        | LLM call circuit breaker (`@reactive-agents/llm-provider` `CircuitBreakerConfig`)                                                                                                                                                                                                                                                                                                                            |
| `withRateLimiting(config?)`          | Throttle LLM requests (`requestsPerMinute`, `tokensPerMinute`, concurrency, …)                                                                                                                                                                                                                                                                                                                               |
| `withReasoning(options?)`            | Strategies + ICS — see [ReasoningOptions](#reasoningoptions)                                                                                                                                                                                                                                                                                                                                                 |
| `withTools(options?)`                | Tool layer — see [ToolsOptions](#toolsoptions) below                                                                                                                                                                                                                                                                                                                                                         |
| `withDocuments(docs)`                | Chunk + index `DocumentSpec[]` for RAG (`rag-search`). Enables tools if needed                                                                                                                                                                                                                                                                                                                               |
| `withRequiredTools(config)`          | Tools that must run before success — `{ tools?, adaptive?, maxRetries? }`. When `adaptive: true`, the framework also auto-sets a per-tool call budget of 3 for search-type tools to prevent infinite research loops.                                                                                                                                                                                         |
| `withIdentity()`                     | Ed25519 identity + RBAC                                                                                                                                                                                                                                                                                                                                                                                      |
| `withObservability(options?)`        | Metrics dashboard, tracing, verbosity. Options: `verbosity` (`"minimal"\|"normal"\|"verbose"\|"debug"`), `live` (stream phase events), `file` (JSONL path), `logPrefix`, `logModelIO` (when `true` or when `verbosity: "debug"`, logs the complete FC conversation thread with role labels `[USER]`/`[ASSISTANT]`/`[TOOL]` and raw LLM response for every iteration — essential for debugging prompt issues). **Note:** observability is enabled at `"normal"` verbosity by default — you only need `.withObservability()` to customize the verbosity level or output format. |
| `withCortex(url?)`                   | Enable best-effort Cortex reporting. Streams all EventBus events to the [Cortex local studio](/features/cortex/) over WebSocket (`/ws/ingest`). URL priority: explicit `url` arg → `CORTEX_URL` env → `http://localhost:4321`. Connection is non-blocking — if Cortex is unreachable the agent continues normally. See [Cortex Studio](/features/cortex/) for the full feature reference.                    |
| `withStreaming(options?)`            | Default density for `agent.runStream()`: `{ density?: "tokens" \| "full" }`                                                                                                                                                                                                                                                                                                                                  |
| `withTelemetry(config?)`             | Opt-in run telemetry / privacy modes (`@reactive-agents/observability` `TelemetryConfig`; default mode `isolated` if omitted)                                                                                                                                                                                                                                                                                |
| `withInteraction()`                  | Collaboration / approval flows                                                                                                                                                                                                                                                                                                                                                                               |
| `withPrompts(options?)`              | `{ templates?: PromptTemplate[] }`                                                                                                                                                                                                                                                                                                                                                                           |
| `withOrchestration()`                | Multi-agent workflows                                                                                                                                                                                                                                                                                                                                                                                        |
| `withExperienceLearning()`           | `ExperienceStore` cross-agent tips                                                                                                                                                                                                                                                                                                                                                                           |
| `withLearning(opts?)`                | Enable the cross-run learning store: `{ tier?: "standard" \| "enhanced", dbPath? }`. Experience + skill learning that compounds across sessions.                                                                                                                                                                                                                                                              |
| `withSkillPersistence(enabled?)`     | Persist learned `SkillRecord`s across process restarts (SQLite-backed). Defaults to `true` when called; also enabled by `HarnessProfile.intelligent()`.                                                                                                                                                                                                                                                       |
| `withMemoryConsolidation(config?)`   | Background consolidation: `{ threshold?, decayFactor?, pruneThreshold? }`                                                                                                                                                                                                                                                                                                                                    |
| `withSelfImprovement()`              | Strategy outcome logging for later bootstrap hints                                                                                                                                                                                                                                                                                                                                                           |
| `withAudit()`                        | Audit trail                                                                                                                                                                                                                                                                                                                                                                                                  |
| `withEvents()`                       | Ensures EventBus wiring for `agent.subscribe()`                                                                                                                                                                                                                                                                                                                                                              |
| `withGateway(options?)`              | Heartbeats, crons, webhooks, policies, `port`, `accessControl`, …                                                                                                                                                                                                                                                                                                                                              |
| `withErrorHandler(handler)`          | Observe-only callback on `agent.run()` failures — does not swallow errors                                                                                                                                                                                                                                                                                                                                    |
| `withFallbacks(config)`              | `{ providers?, models?, errorThreshold? }` fallback chain                                                                                                                                                                                                                                                                                                                                                    |
| `withLogging(config)`                | `makeLoggerService` — `{ level?, format?, output?: "console" \| "file" \| WritableStream, filePath?, maxFileSizeBytes?, maxFiles? }`                                                                                                                                                                                                                                                                         |
| `withHealthCheck()`                  | Enables `agent.health()`                                                                                                                                                                                                                                                                                                                                                                                     |
| `withVerificationStep(config?)`      | Post-answer LLM self-review. `{ mode: "reflect" \| "loop", prompt? }`. Reflect mode adds one LLM confirmation call; loop mode (V1.1) re-enters the ReAct loop                                                                                                                                                                                                                                                |
| `withOutputValidator(fn, opts?)`     | Validate output before accepting. `fn(output) => { valid, feedback? }`. Failed validation injects feedback and retries (`opts.maxRetries`, default 2)                                                                                                                                                                                                                                                        |
| `withCustomTermination(fn)`          | Re-run until `fn({ output }) === true`, up to 3 additional times. For domain-specific completion criteria                                                                                                                                                                                                                                                                                                    |
| `withTaskContext(record)`            | `Record<string, string>` of background facts injected into reasoning context — distinct from system prompt instructions                                                                                                                                                                                                                                                                                      |
| `withProgressCheckpoint(n, opts?)`   | Store checkpoint config every N iterations. `{ autoResume? }`. PlanStore write execution is V1.1                                                                                                                                                                                                                                                                                                             |
| `withReactiveIntelligence(false)`    | Disable the Reactive Intelligence layer (enabled by default).                                                                                                                                                                                                                                                                                                                                                |
| `withReactiveIntelligence(options?)` | Entropy, controller, telemetry, hooks (`onEntropyScored`, `onControllerDecision`, …), `constraints`, `autonomy`. See [Reactive Intelligence](/features/reactive-intelligence/)                                                                                                                                                                                                                               |
| `withSkills(config?)`                | `{ paths?, packages?, evolution?: { mode?, refinementThreshold?, rollbackOnRegression? }, overrides? }`                                                                                                                                                                                                                                                                                                      |
| `withMetaTools(config?)`             | Conductor meta-tools; pass **`false`** to turn off defaults when using `.withTools()`. See [MetaToolsConfig](#metatoolsconfig)                                                                                                                                                                                                                                                                               |
| `withHarness(fn)`                    | Alias for `.compose(fn)` — attach a harness transform over tagged chokepoints. See [Compose API](/reference/compose-api/).                                                                                                                                                                                                                                                                                    |
| `withProfile(profile)`               | Apply a `HarnessProfile` preset (`lean()` / `balanced()` / `intelligent()`) — the canonical one-line capability composition. Later `.withX()` calls override the preset.                                                                                                                                                                                                                                       |
| `withLeanHarness()`                  | Disable the default harness capabilities. **Superseded by `HarnessProfile.lean()`**, which additionally disables reactive intelligence (this method does not). Still functional.                                                                                                                                                                                                                              |
| `withCalibration(mode)`              | Control per-model adaptive calibration: `CalibrationMode` (`"auto" \| "off" \| …`). Auto-enabled with `.withReasoning()`. See [LLM Providers](/features/llm-providers/).                                                                                                                                                                                                                                       |
| `withTerminalTools(options?)`        | Register the built-in `git-cli` / `gh-cli` / `gws-cli` terminal tools (`ShellExecuteConfig`).                                                                                                                                                                                                                                                                                                                |
| `withTracing(opts?)`                 | Write structured trace files (`{ dir? }`) for `rax diagnose`. Governed separately from the metrics dashboard; also toggled by `REACTIVE_AGENTS_TRACE`. Pair with `.withoutTracing()` to disable.                                                                                                                                                                                                              |
| `withChannels(config)`               | Wire `@reactive-agents/channels` sender-policy access control (`ChannelsConfig`). See [Messaging Channels](/guides/messaging-channels/).                                                                                                                                                                                                                                                                      |
| `withOutputSchema(schema, options?)` | **Typed structured output.** Attach any Standard Schema (Zod / Valibot / ArkType) or Effect Schema; the result carries a typed `result.object` (or `result.objectError` on parse failure). Options: `{ mode?: "auto" \| "grounded", onParseFail?: "lenient" \| "throw" }`. Builder-only — set before `.build()`. See [Typed Structured Output](/guides/structured-output/).                                       |
| `withDurableRuns(options?)`          | Persist run state so a crashed or paused run can resume from its last checkpoint. Exposes `agent.resumeRun(runId)` and `agent.listRuns({ status? })`. Config hash = system prompt + provider. See [Durable Execution](/guides/durable-execution/).                                                                                                                                                            |
| `withApprovalPolicy(policy)`         | Durable human-in-the-loop. `{ tools?, requireFor?, mode? }` — gated tool calls pause the run (`mode: "detach"`, default with durable runs) and persist `awaiting-approval`. Exposes `agent.approveRun`/`denyRun`/`listPendingApprovals`. `detach` requires `.withDurableRuns()`. See [Durable HITL](/guides/durable-hitl/).                                                                                  |

#### ToolsOptions

| Field               | Description                                                                                                               |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `tools`             | `{ definition: ToolDefinition, handler: (args) => Effect.Effect<unknown> }[]` — custom tools (handlers return **Effect**) |
| `resultCompression` | `ResultCompressionConfig` — previews, overflow keys, transforms                                                           |
| `allowedTools`      | If set, only these tool names are exposed to the model (others filtered)                                                  |
| `adaptive`          | Adaptive tool listing from task text (heuristic), reduces noise for small models                                          |

#### MetaToolsConfig

| Field                                       | Description                                                                 |
| ------------------------------------------- | --------------------------------------------------------------------------- |
| `brief`, `find`, `pulse`, `recall`          | Enable each Conductor meta-tool                                             |
| `harnessSkill`                              | `boolean`, path string, or `{ frontier?, local? }` for harness skill source |
| `findConfig`, `pulseConfig`, `recallConfig` | Fine-tuning (scopes, previews, LLM pulse behavior, …)                       |

#### ReasoningOptions

```typescript
interface ReasoningOptions {
    /**
     * Which strategy to use. Defaults to "reactive".
     * "adaptive" requires adaptive.enabled: true.
     */
    defaultStrategy?:
        | 'reactive'
        | 'reflexion'
        | 'plan-execute-reflect'
        | 'tree-of-thought'
        | 'adaptive'

    /**
     * Per-strategy overrides (iterations, temperatures, plan knobs, etc.).
     * Each bundle may also set ICS fields (`synthesis`, `synthesisModel`, `synthesisProvider`,
     * `synthesisStrategy`, `synthesisTemperature`) — they override the top-level synthesis
     * options for that strategy only (see Intelligent Context Synthesis).
     */
    strategies?: Partial<{
        reactive: ReasoningConfig['strategies']['reactive'] &
            StrategySynthesisFields
        planExecute: ReasoningConfig['strategies']['planExecute'] &
            StrategySynthesisFields
        treeOfThought: ReasoningConfig['strategies']['treeOfThought'] &
            StrategySynthesisFields
        reflexion: ReasoningConfig['strategies']['reflexion'] &
            StrategySynthesisFields
    }>

    /** Adaptive strategy config. Must set enabled: true when defaultStrategy is "adaptive". */
    adaptive?: {
        enabled?: boolean // Required for adaptive strategy
        learning?: boolean // Enable cross-run learning (default: false)
    }

    /** Max iterations of the reasoning loop (default: 10). */
    maxIterations?: number

    /**
     * Automatically switch to a better-suited strategy when the current one appears stuck
     * (repeated tool calls, repeated thoughts, or consecutive think-only steps).
     * Default: false.
     */
    enableStrategySwitching?: boolean

    /**
     * Maximum number of strategy switches allowed in a single run.
     * Default: 1.
     */
    maxStrategySwitches?: number

    /**
     * When set, bypasses the LLM evaluator and always switches to this strategy on loop
     * detection. Useful when you want deterministic switching without the extra LLM call.
     * Example: "plan-execute-reflect"
     */
    fallbackStrategy?: string

    /** ICS default mode: auto (heuristic), fast (templates), deep (LLM), custom, or off. */
    synthesis?: 'auto' | 'fast' | 'deep' | 'custom' | 'off'
    /** Model for deep synthesis when different from the executing model. */
    synthesisModel?: string
    /** Provider for the synthesis model when different from the executing provider. */
    synthesisProvider?: string
    /** Custom synthesis pipeline when `synthesis: "custom"`. */
    synthesisStrategy?: SynthesisStrategy
    /** Temperature for deep synthesis LLM calls. */
    synthesisTemperature?: number
}

/** ICS-only fields allowed on each `strategies.*` bundle (merged with top-level synthesis). */
interface StrategySynthesisFields {
    synthesis?: 'auto' | 'fast' | 'deep' | 'custom' | 'off'
    synthesisModel?: string
    synthesisProvider?: string
    synthesisStrategy?: SynthesisStrategy
    synthesisTemperature?: number
}
```

Per-strategy objects under `strategies` also accept strategy-specific fields from `@reactive-agents/reasoning` (for example `kernelMaxIterations` on the `reflexion` bundle).

At runtime, `ReasoningOptions` may also include a non-JSON `synthesisStrategy` function when using `synthesis: "custom"` (omitted from `toConfig()` / JSON).

**Examples:**

```typescript
// Default: ReAct with no options
.withReasoning()

// Switch to Plan-Execute-Reflect strategy
.withReasoning({ defaultStrategy: "plan-execute-reflect" })

// Adaptive strategy (must set adaptive.enabled)
.withReasoning({ defaultStrategy: "adaptive", adaptive: { enabled: true } })

// Auto-switch when stuck, up to 2 times, via LLM evaluator
.withReasoning({ enableStrategySwitching: true, maxStrategySwitches: 2 })

// Auto-switch deterministically (no extra LLM call) to plan-execute-reflect
.withReasoning({ enableStrategySwitching: true, fallbackStrategy: "plan-execute-reflect" })

// ICS: fast templates globally, but deep LLM synthesis when running ReAct
.withReasoning({
  synthesis: "fast",
  strategies: { reactive: { synthesis: "deep", synthesisModel: "claude-haiku-4-5-20251001" } },
})
```

When `enableStrategySwitching` is active, two EventBus events are emitted around each switch:

-   `StrategySwitchEvaluated` — after the evaluator runs, before the switch (includes `willSwitch`, `rationale`, `recommendedStrategy`)
-   `StrategySwitched` — after the new strategy takes over (includes `fromStrategy`, `toStrategy`, `switchNumber`, `stepsCarriedOver`)

See [Automatic Strategy Switching](/guides/choosing-strategies/#automatic-strategy-switching) for full details on loop detection triggers, handoff context, and EventBus subscription examples.

#### RequiredToolsConfig

```typescript
interface RequiredToolsConfig {
    /** Static list of tool names the agent MUST call before answering. */
    tools?: string[]
    /** Enable adaptive inference — LLM analyzes task + tools to determine required tools. */
    adaptive?: boolean
    /** Number of retry loops if required tools are missed (default: 2). */
    maxRetries?: number
}
```

**Examples:**

```typescript
// Static required tools — agent must call web-search before answering
.withRequiredTools({ tools: ["web-search"] })

// Adaptive inference — LLM determines which tools are required per-task
.withRequiredTools({ adaptive: true })

// Both — static list as baseline, adaptive for additional inference
.withRequiredTools({ tools: ["web-search"], adaptive: true, maxRetries: 3 })
```

When `adaptive: true`, the framework calls the LLM with the task description and available tool schemas to infer which tools are required. The inferred list is merged with any static `tools` list. A hallucination guard ensures only actual tool names are included.

### A2A protocol

| Method                 | Signature                                                                                                                                                                                           | Description                                                             |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
| `withA2A`              | `(options?: A2AOptions) => this`                                                                                                                                                                    | A2A JSON-RPC server — `port` (default `3000`), `basePath` (default `/`) |
| `withAgentTool`        | `(name: string, agent: { name: string; description?: string; provider?: string; model?: string; tools?: string[]; maxIterations?: number; systemPrompt?: string; persona?: AgentPersona }) => this` | Static sub-agent as a tool                                              |
| `withDynamicSubAgents` | `(options?: { maxIterations?: number }) => this`                                                                                                                                                    | `spawn-agent` for runtime sub-agents                                    |
| `withRemoteAgent`      | `(name: string, remoteUrl: string) => this`                                                                                                                                                         | Remote A2A agent as a tool                                              |

### MCP

| Method    | Signature                                                | Description                                                                                     |
| --------- | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| `withMCP` | `(config: MCPServerConfig \| MCPServerConfig[]) => this` | Connect to MCP servers. Accepts a single config or array. Automatically enables `.withTools()`. |

#### MCPServerConfig

| Field       | Type                                                   | Transport                       | Description                                                                                          |
| ----------- | ------------------------------------------------------ | ------------------------------- | ---------------------------------------------------------------------------------------------------- |
| `name`      | `string`                                               | all                             | Unique name for this server. Tool names are prefixed `{name}/`                                       |
| `transport` | `"stdio" \| "streamable-http" \| "sse" \| "websocket"` | all                             | Protocol to use. Use `"streamable-http"` for modern remote servers, `"stdio"` for local subprocesses |
| `command`   | `string`                                               | stdio                           | Executable to launch (`"bunx"`, `"docker"`, `"python"`, absolute path, etc.)                         |
| `args`      | `string[]`                                             | stdio                           | Arguments passed to `command`. Includes package names, flags, Docker image, etc.                     |
| `env`       | `Record<string, string>`                               | stdio                           | Extra env vars merged on top of the parent process environment. Use for per-server secrets           |
| `cwd`       | `string`                                               | stdio                           | Working directory for the subprocess. Defaults to parent process `cwd`                               |
| `endpoint`  | `string`                                               | streamable-http, sse, websocket | HTTP/WebSocket URL (`"https://mcp.example.com"`, `"ws://localhost:8000/mcp"`)                        |
| `headers`   | `Record<string, string>`                               | streamable-http, sse            | HTTP headers sent on every request. Use for `Authorization`, `x-api-key`, etc.                       |

**Examples:**

```typescript
// stdio: npm package via bunx
{ name: "filesystem", transport: "stdio", command: "bunx",
  args: ["-y", "@modelcontextprotocol/server-filesystem", "."] }

// stdio: with per-server secret
{ name: "github", transport: "stdio", command: "bunx",
  args: ["-y", "@modelcontextprotocol/server-github"],
  env: { GITHUB_PERSONAL_ACCESS_TOKEN: process.env.GH_TOKEN ?? "" } }

// stdio: Docker container with networking
{ name: "my-server", transport: "stdio", command: "docker",
  args: ["run", "-i", "--rm", "--network", "host", "ghcr.io/org/mcp-server"] }

// streamable-http: modern cloud server with Bearer auth
{ name: "stripe", transport: "streamable-http",
  endpoint: "https://mcp.stripe.com",
  headers: { Authorization: `Bearer ${process.env.STRIPE_KEY}` } }

// sse: legacy remote server with API key
{ name: "legacy", transport: "sse",
  endpoint: "https://api.example.com/mcp",
  headers: { "x-api-key": process.env.API_KEY ?? "" } }
```

### Lifecycle

| Method     | Signature                       | Description               |
| ---------- | ------------------------------- | ------------------------- |
| `withHook` | `(hook: LifecycleHook) => this` | Register a lifecycle hook |

#### LifecycleHook

Use the exported `LifecycleHook` type from `@reactive-agents/runtime`. Handlers return **`Effect.Effect<ExecutionContext, ExecutionError>`** (import `Effect` from `"effect"`).

`LifecyclePhase` values include: `bootstrap`, `guardrail`, `cost-route`, `strategy-select`, `think`, `act`, `observe`, `verify`, `memory-flush`, `cost-track`, `audit`, `complete`.

### Testing

| Method             | Signature                     | Description                                                                                                                                                                                                                                 |
| ------------------ | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `withTestScenario` | `(turns: TestTurn[]) => this` | Deterministic **test** provider. Forces `provider: "test"`. Turns are `TestTurn` values from `@reactive-agents/llm-provider`: `{ text? }`, `{ toolCall? }`, `{ toolCalls? }`, `{ json? }`, `{ error? }`, optional `match?` (regex) per turn |

See [Testing agents](/cookbook/testing-agents/) and [Configuration](/reference/configuration/) for examples.

### Advanced

| Method       | Signature                           | Description                             |
| ------------ | ----------------------------------- | --------------------------------------- |
| `withLayers` | `(layers: Layer<any, any>) => this` | Add custom Effect Layers to the runtime |

## Build Methods

### `build()`

```typescript
async build(): Promise<ReactiveAgent>
```

Creates the agent, resolving the full Layer stack. Returns a `ReactiveAgent` instance.

### `buildEffect()`

```typescript
buildEffect(): Effect.Effect<ReactiveAgent, Error>
```

Creates the agent as an Effect for composition in Effect programs.

### `runOnce(input: string): Promise<AgentResult>`

Builds the agent, runs a single task, disposes all resources, and returns the result — in one call. Use this for one-shot scripts where you don't need to hold a reference to the agent.

```typescript
const result = await ReactiveAgents.create()
    .withProvider('anthropic')
    .withReasoning()
    .runOnce('Summarize the README in one paragraph')

console.log(result.output)
// Resources are already cleaned up
```

## ReactiveAgent

The facade returned by `build()`.

### Resource Management

Agents that use MCP servers (stdio transport) or other subprocess-based resources **must be disposed** after use, otherwise the process will hang on open pipes. Three patterns are available:

#### Pattern 1 — `await using` (recommended)

Uses the [Explicit Resource Management](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html) protocol introduced in TypeScript 5.2. The agent is disposed automatically when the enclosing block exits, whether normally or via an exception.

```typescript
await using agent = await ReactiveAgents.create()
  .withProvider("anthropic")
  .withMCP({ name: "filesystem", transport: "stdio", command: "npx", args: ["@modelcontextprotocol/server-filesystem", "."] })
  .withReasoning()
  .build();

const result = await agent.run("List the project files.");
console.log(result.output);
// agent.dispose() is called automatically here
```

Requires `"lib": ["ES2022", "ESNext"]` or `"target": "ES2022"` in your `tsconfig.json`.

#### Pattern 2 — `runOnce()` (one-shot)

If you only need a single result and don't want to manage the agent handle at all, use the builder's `runOnce()` method. It builds, runs, and disposes in one call.

```typescript
const result = await ReactiveAgents.create()
    .withProvider('anthropic')
    .withMCP({
        name: 'filesystem',
        transport: 'stdio',
        command: 'npx',
        args: ['@modelcontextprotocol/server-filesystem', '.'],
    })
    .withReasoning()
    .runOnce('List the project files.')

console.log(result.output)
// Resources already cleaned up
```

#### Pattern 3 — `dispose()` (explicit)

Call `dispose()` manually in a `finally` block when you need to reuse the agent across multiple calls before cleaning up.

```typescript
const agent = await ReactiveAgents.create()
    .withProvider('anthropic')
    .withReasoning()
    .build()

try {
    const r1 = await agent.run('First task')
    const r2 = await agent.run('Second task')
    console.log(r1.output, r2.output)
} finally {
    await agent.dispose()
}
```

| Pattern       | When to use                                                 |
| ------------- | ----------------------------------------------------------- |
| `await using` | General purpose — automatic cleanup, works with `try/catch` |
| `runOnce()`   | Single-shot scripts and one-liners                          |
| `dispose()`   | Multiple sequential runs before teardown                    |

### `run(input, options?): Promise<AgentResult>`

Run a task with the given input. Returns the result with output and metadata.

Options: `{ taskId?, history?, onApproval? }`. On a durable agent with
`.withApprovalPolicy({ mode: "detach" })`, a gated tool call **pauses**: the
result carries `status: "awaiting-approval"` + `pendingApproval` (resume with
`approveRun`/`denyRun`). Pass `onApproval` to handle the pause→decide→resume loop
in this one call — `(pending) => boolean | { approve, reason }` (sync or async);
returns the final result. See [Durable HITL](/guides/durable-hitl/).

### `runStream(input, options?): AsyncGenerator<AgentStreamEvent>`

Token and phase streaming. Options: `{ density?: "tokens" | "full", signal?: AbortSignal }`. Default density comes from `.withStreaming()` or `"tokens"`. Ends with `StreamCompleted`, `StreamError`, or `StreamCancelled`.

### `runEffect(input: string): Effect.Effect<AgentResult, Error>`

Run a task as an Effect for composition (see [Effect-TS primer](/concepts/effect-ts/)).

### `streamObject(input): AsyncGenerator<{ object: DeepPartial<T> }>`

Stream typed structured output field-by-field as it fills in. Requires `.withOutputSchema()`. Each yield carries a deep-partial of the schema type; the final yield is the validated object. See [Typed Structured Output](/guides/structured-output/).

### `resumeRun(runId: string): Promise<AgentResult>`

Resume a crashed or paused durable run from its last checkpoint. Requires `.withDurableRuns()`. See [Durable Execution](/guides/durable-execution/).

### `listRuns(filter?: { status? }): Promise<readonly RunRecord[]>`

List persisted durable runs, optionally filtered by lifecycle status (e.g. `{ status: "running" }`). Requires `.withDurableRuns()`.

### `listPendingApprovals(): Promise<readonly PendingApproval[]>`

List runs paused awaiting a human decision, each with the pending action (`runId`, `gateId`, `toolName`, `args`, `task`). Requires `.withDurableRuns()`. See [Durable HITL](/guides/durable-hitl/).

### `approveRun(runId, opts?): Promise<AgentResult>`

Approve a paused run and resume it to completion — the agent executes the gated call. Callable from any process. Throws `ApprovalStateError` if the run has no pending approval.

### `denyRun(runId, reason): Promise<AgentResult>`

Deny a paused run's action and resume to completion — the agent observes the denial and continues without running the call.

### Dynamic tools & RAG (runtime)

| Method                                      | Description                                               |
| ------------------------------------------- | --------------------------------------------------------- |
| `registerTool(definition, handler)`         | Register a tool after build; `handler` returns `Effect`   |
| `unregisterTool(name)`                      | Remove a previously registered custom tool                |
| `ingest(content, { source, format?, ... })` | Ingest text into RAG when tools / `withDocuments` enabled |

### `chat(message: string, options?: ChatOptions): Promise<ChatReply>`

Conversational Q&A with the agent. Routes automatically:

-   **Direct LLM path** — for questions, summaries, and status checks (fast, no tools)
-   **ReAct loop path** — for tool-capable requests (search, fetch, write, create, etc.)

Injects context from the last run's debrief so the agent can answer "what did you do last time?" accurately.

```typescript
const reply = await agent.chat('What did you accomplish last run?')
console.log(reply.message)

// Force tool-capable path
const reply2 = await agent.chat('Search for the latest AI news', {
    useTools: true,
})
console.log(reply2.toolsUsed) // ["web-search"]
```

```typescript
interface ChatReply {
    message: string
    toolsUsed?: string[] // Set when tools were invoked
    fromMemory?: boolean // Set when answered from debrief context
}

interface ChatOptions {
    useTools?: boolean // Override auto-routing
    maxIterations?: number // Cap for tool-capable path (default: 5)
}
```

### `session(options?: SessionOptions): AgentSession`

Start a multi-turn conversation session with auto-managed history. Conversation history is forwarded to the LLM on every subsequent turn.

Pass `{ persist: true, id: "my-session" }` to persist conversation history to SQLite via `SessionStoreService`. Persistent sessions survive process restarts and can be resumed by passing the same `id`.

```typescript
// In-memory session (default)
const session = agent.session()

const r1 = await session.chat('What are the key findings from your last run?')
const r2 = await session.chat('Tell me more about the first finding')
// r2 has full context of r1

// Persisted session — survives process restarts
const persistedSession = agent.session({
    persist: true,
    id: 'research-session-1',
})
await persistedSession.chat('Start researching quantum computing')
// On next run, restore the session:
const restoredSession = agent.session({
    persist: true,
    id: 'research-session-1',
})
await restoredSession.chat('Continue where we left off')

const history = session.history() // ChatMessage[]
await session.end() // Clears history (and DB record if persisted)
```

```typescript
interface SessionOptions {
    persist?: boolean // Persist history to SQLite via SessionStoreService
    id?: string // Session ID for persistence (auto-generated if omitted)
}

interface AgentSession {
    chat(message: string): Promise<ChatReply>
    history(): ChatMessage[]
    end(): Promise<void>
}
```

### `health(): Promise<HealthResult>`

Requires `.withHealthCheck()` to be enabled.

Returns a structured health snapshot of all agent subsystems. Use for readiness probes, liveness checks, and monitoring dashboards.

```typescript
const health = await agent.health()
console.log(health.status) // "healthy" | "degraded" | "unhealthy"

for (const check of health.checks) {
    console.log(`${check.name}: ${check.status} — ${check.message}`)
}
```

```typescript
interface HealthResult {
    status: 'healthy' | 'degraded' | 'unhealthy'
    checks: Array<{
        name: string
        status: 'pass' | 'warn' | 'fail'
        message?: string
        durationMs?: number
    }>
}
```

### `cancel(taskId: string): Promise<void>`

Cancel a running task by its ID.

### `getContext(taskId: string): Promise<unknown>`

Get the execution context of a running or completed task.

### Lifecycle Control

Requires `.withKillSwitch()` to be enabled.

| Method              | Signature                           | Description                                                                   |
| ------------------- | ----------------------------------- | ----------------------------------------------------------------------------- |
| `pause()`           | `() => Promise<void>`               | Pause execution at the next phase boundary. Blocks until `resume()` is called |
| `resume()`          | `() => Promise<void>`               | Resume a paused agent                                                         |
| `stop(reason)`      | `(reason: string) => Promise<void>` | Graceful stop — signals intent; agent completes current phase then exits      |
| `terminate(reason)` | `(reason: string) => Promise<void>` | Immediate termination (also triggers kill switch)                             |

### Event Subscription

Requires an EventBus to be wired (any feature that enables it, e.g., `.withObservability()`).

`subscribe` is overloaded — pass a tag for type-narrowed access, or omit it for a catch-all:

```typescript
// ── Tag-filtered: event is narrowed to the exact payload type ──────────────
const unsub = await agent.subscribe('AgentCompleted', (event) => {
    // TypeScript knows event has: taskId, agentId, success, totalIterations,
    // totalTokens, durationMs — no _tag check, no cast needed
    console.log(`Done in ${event.durationMs}ms, ${event.totalTokens} tokens`)
})
unsub()

// ── Catch-all: receives the full AgentEvent union ──────────────────────────
const unsub2 = await agent.subscribe((event) => {
    // Discriminate via event._tag when handling multiple types in one handler
    if (event._tag === 'ToolCallStarted') console.log(`Tool: ${event.toolName}`)
    if (event._tag === 'LLMRequestStarted') console.log(`Model: ${event.model}`)
})
unsub2()
```

TypeScript signatures:

```typescript
// Tag-filtered — event type is automatically narrowed
subscribe<T extends AgentEventTag>(
  tag: T,
  handler: (event: Extract<AgentEvent, { _tag: T }>) => void,
): Promise<() => void>;

// Catch-all — full AgentEvent union
subscribe(handler: (event: AgentEvent) => void): Promise<() => void>;
```

The `AgentEventTag` and `TypedEventHandler<T>` helpers are exported from `@reactive-agents/core` for use in your own service code:

```typescript
import { Effect } from 'effect'
import type { AgentEventTag, TypedEventHandler } from '@reactive-agents/core'

// Build a typed handler outside of an inline callback
const onStepComplete: TypedEventHandler<'ReasoningStepCompleted'> = (event) => {
    // event.thought, event.action, event.observation — all typed
    return Effect.log(`Step ${event.step}: ${event.thought ?? event.action}`)
}

yield * eventBus.on('ReasoningStepCompleted', onStepComplete)
```

**Subscribable event tags:**

| Tag                          | Payload fields                                                                     |
| ---------------------------- | ---------------------------------------------------------------------------------- |
| `AgentStarted`               | `taskId`, `agentId`, `provider`, `model`, `timestamp`                              |
| `AgentCompleted`             | `taskId`, `agentId`, `success`, `totalIterations`, `totalTokens`, `durationMs`     |
| `LLMRequestStarted`          | `taskId`, `requestId`, `model`, `provider`, `contextSize`                          |
| `LLMRequestCompleted`        | `taskId`, `requestId`, `tokensUsed`, `durationMs`                                  |
| `ReasoningStepCompleted`     | `taskId`, `strategy`, `step`, `thought\|action\|observation`                       |
| `ToolCallStarted`            | `taskId`, `toolName`, `callId`                                                     |
| `ToolCallCompleted`          | `taskId`, `toolName`, `callId`, `success`, `durationMs`                            |
| `FinalAnswerProduced`        | `taskId`, `strategy`, `answer`, `iteration`, `totalTokens`                         |
| `GuardrailViolationDetected` | `taskId`, `violations`, `score`, `blocked`                                         |
| `ExecutionPhaseEntered`      | `taskId`, `phase`                                                                  |
| `ExecutionPhaseCompleted`    | `taskId`, `phase`, `durationMs`                                                    |
| `ExecutionHookFired`         | `taskId`, `phase`, `timing`                                                        |
| `ExecutionCancelled`         | `taskId`                                                                           |
| `MemoryBootstrapped`         | `agentId`, `tier`                                                                  |
| `MemoryFlushed`              | `agentId`                                                                          |
| `AgentPaused`                | `agentId`, `taskId`                                                                |
| `AgentResumed`               | `agentId`, `taskId`                                                                |
| `AgentStopped`               | `agentId`, `taskId`, `reason`                                                      |
| `TaskCompleted`              | `taskId`, `success`                                                                |
| `GatewayStarted`             | `agentId`, `timestamp`                                                             |
| `GatewayStopped`             | `agentId`, `reason`                                                                |
| `GatewayEventReceived`       | `agentId`, `eventId`, `source`, `category`                                         |
| `ProactiveActionInitiated`   | `agentId`, `eventId`, `action`                                                     |
| `ProactiveActionCompleted`   | `agentId`, `eventId`, `success`, `durationMs`                                      |
| `ProactiveActionSuppressed`  | `agentId`, `eventId`, `reason`                                                     |
| `PolicyDecisionMade`         | `agentId`, `eventId`, `action`, `policyTag`                                        |
| `HeartbeatSkipped`           | `agentId`, `consecutiveSkips`, `reason`                                            |
| `EventsMerged`               | `agentId`, `mergedCount`, `mergeKey`                                               |
| `BudgetExhausted`            | `agentId`, `tokensUsed`, `dailyBudget`                                             |
| `StrategySwitchEvaluated`    | `taskId`, `fromStrategy`, `recommendedStrategy`, `rationale`, `willSwitch`         |
| `StrategySwitched`           | `taskId`, `fromStrategy`, `toStrategy`, `switchNumber`, `stepsCarriedOver`         |
| `ProviderFallbackActivated`  | `taskId`, `fromProvider`, `toProvider`, `reason`, `attemptNumber`                  |
| `DebriefCompleted`           | `taskId`, `agentId`, `debrief`                                                     |
| `ChatTurn`                   | `taskId`, `sessionId`, `role`, `content`, `routedVia`, `tokensUsed?`               |
| `MemorySnapshot`             | `taskId`, `iteration`, `working`, `episodicCount`, `semanticCount`, `skillsActive` |
| `ContextPressure`            | `taskId`, `utilizationPct`, `tokensUsed`, `tokensAvailable`, `level`               |
| `AgentHealthReport`          | `agentId`, `status`, `checks[]`, `uptimeMs`                                        |
| `AgentConnected`             | `agentId`, `runId`, `cortexUrl`                                                    |
| `AgentDisconnected`          | `agentId`, `runId`, `reason`                                                       |

## AgentResult

```typescript
interface AgentResult {
    output: string // The agent's response
    success: boolean // Whether the task completed successfully
    taskId: string // Unique task identifier
    agentId: string // Agent that ran the task
    metadata: {
        duration: number // Execution time in milliseconds
        cost: number // Estimated cost in USD
        tokensUsed: number // Total tokens consumed across all LLM calls
        strategyUsed?: string // Reasoning strategy used (if reasoning enabled)
        stepsCount: number // Number of reasoning steps / iterations
        confidence?: 'high' | 'medium' | 'low' // From final-answer tool
    }

    // Enriched fields (present when reasoning is enabled)
    format?: 'text' | 'json' | 'markdown' | 'csv' | 'html' // Output format declared by agent
    terminatedBy?:
        | 'final_answer_tool' // Exited via the final-answer tool call
        | 'final_answer'      // Exited via inline FINAL ANSWER: text
        | 'max_iterations'    // Hit the iteration/llmCalls ceiling
        | 'end_turn'          // LLM stopped generating (no tool call, no final answer)
        | 'llm_error'         // LLM request or stream failed (provider error, network, etc.)
        | 'abstained'         // Agent honestly declined — could not ground an answer (see abstention below)
    llmCalls?: number // Number of LLM calls made during the kernel loop (available when reasoning is enabled)

    // Abstention (present iff terminatedBy === 'abstained')
    abstention?: {
        reason: string    // Why the agent declined rather than fabricating
        missing: string[] // What was needed, e.g. "tool:web-search", a clarification
    }

    // Durable HITL (present when a run paused for human approval — see Durable HITL guide)
    status?: 'completed' | 'awaiting-approval' | 'failed' // defaults to 'completed' when absent
    pendingApproval?: {
        runId: string    // pass to approveRun(runId) / denyRun(runId, reason)
        gateId: string
        toolName: string // the gated tool call awaiting a decision
        args: unknown
    }

    // Debrief (present when .withMemory() + .withReasoning() are enabled)
    debrief?: AgentDebrief
}
```

### Abstention (`terminatedBy: "abstained"`)

When grounding an answer is structurally impossible — a declared required tool is
absent from the registered tool set, or synthesis was repeatedly rejected as
ungrounded — the harness **forces an honest `abstained` terminal** instead of
grinding to `max_iterations` or letting fabrication through. A genuine
deliverable is never overridden. When this happens, `result.terminatedBy` is
`"abstained"` and `result.abstention` carries the reason plus what was missing:

```typescript
const result = await agent.run('Summarize the current HN front page')
if (result.terminatedBy === 'abstained') {
    console.log(result.abstention?.reason)  // "required tool unavailable; could not ground an answer"
    console.log(result.abstention?.missing) // ["tool:web-search"]
}
```

`goalAchieved` is `false` for abstained runs (honest non-achievement). This
run-level surface is distinct from the per-field structured-output `abstained`
map (`.withOutputSchema({ abstainBelow })`) — the two are unrelated and may coexist.

### `AgentDebrief`

A structured post-run synthesis produced automatically when memory is enabled:

```typescript
interface AgentDebrief {
    outcome: 'success' | 'partial' | 'failed'
    summary: string // 2-3 sentence narrative
    keyFindings: string[]
    errorsEncountered: string[]
    lessonsLearned: string[] // Auto-fed to ExperienceStore
    confidence: 'high' | 'medium' | 'low'
    caveats?: string
    toolsUsed: { name: string; calls: number; successRate: number }[]
    metrics: {
        tokens: number
        duration: number
        iterations: number
        cost: number
    }
    markdown: string // Pre-rendered Markdown version
}
```

Access it from any run result:

```typescript
const result = await agent.run('Fetch the latest commits and summarize')

// result.debrief — instant deterministic fallback (never blocks run()).
if (result.debrief) {
    console.log(result.debrief.summary)
    console.log(result.debrief.markdown)
}

// result.debriefRich() — awaits the LLM-synthesized rich debrief, which the
// engine forks off the critical path (v0.12.0+). Returns undefined when no
// debrief was scheduled (e.g. .withoutMemory()).
const rich = await result.debriefRich?.()
console.log(rich?.markdown)
```

## Full Example

```typescript
import { ReactiveAgents } from "reactive-agents";
import { Effect } from "effect";

// await using — agent is disposed automatically when this block exits
await using agent = await ReactiveAgents.create()
  .withName("research-assistant")
  .withProvider("anthropic")
  .withModel("claude-sonnet-4-6")
  .withPersona({
    role: "CRISPR Research Specialist",
    background: "Expert in gene editing and molecular biology",
    instructions: "Provide detailed technical analysis with citations",
    tone: "professional",
  })
  .withMemory()
  .withReasoning({ defaultStrategy: "adaptive", adaptive: { enabled: true } })
  .withTools()              // Built-in tools (web search, file I/O, etc.)
  .withGuardrails()
  .withVerification()
  .withCostTracking()
  .withObservability()
  .withAudit()
  .withInteraction()
  .withMaxIterations(15)
  .withHook({
    phase: "think",
    timing: "after",
    handler: (ctx) => {
      console.log(`Iteration ${ctx.iteration}, tokens: ${ctx.tokensUsed}`);
      return Effect.succeed(ctx);
    },
  })
  .build();

// Run a task
const result = await agent.run("Research the latest advances in CRISPR gene editing");
console.log(result.output);
console.log(`Cost: $${result.metadata.cost.toFixed(4)}`);
console.log(`Tokens: ${result.metadata.tokensUsed}`);
console.log(`Strategy: ${result.metadata.strategyUsed}`);
// agent.dispose() is called automatically here
```
