` form.
### `rax version`
[Section titled “rax version”](#rax-version)
```bash
rax version
rax --version
rax -v
```
### `rax help`
[Section titled “rax help”](#rax-help)
```bash
rax help
rax --help
rax -h
```
# Compose API
> Reference for .compose(), harness transforms, phase hooks, and pattern matching
The Compose API lets you intercept and reshape any signal the agent kernel emits — from system prompts to tool results to nudges — using a declarative composition model.
## Quick start
[Section titled “Quick start”](#quick-start)
```ts
import { ReactiveAgents } from 'reactive-agents';
import { maxIterations, budgetLimit } from 'reactive-agents/compose/killswitches';
const agent = await ReactiveAgents.create()
.withProvider('anthropic')
.compose(budgetLimit({ maxTokens: 50_000 }))
.compose(maxIterations(20))
.compose((harness) => {
harness.tap('observation.tool-result', (result, ctx) => {
console.log(`[iter ${ctx.iteration}] tool result:`, result.content);
});
})
.build();
```
## `.compose(fn)`
[Section titled “.compose(fn)”](#composefn)
**Signature:** `compose(fn: (harness: Harness) => void): this`
Registers a composition block. Multiple `.compose()` calls accumulate in registration order.
`fn` receives a `Harness` instance with methods to register transforms, taps, and phase hooks. All registrations are compiled once at `.build()` time.
`.compose()` is the canonical entry point. `.withHarness()` is an identical alias.
## `harness.on(pattern, fn)` — Transform
[Section titled “harness.on(pattern, fn) — Transform”](#harnessonpattern-fn--transform)
Intercept and replace an emission’s payload.
**Signature:**
```ts
harness.on(
pattern: TagPattern | TagPattern[],
fn: (payload: PayloadFor, ctx: ContextFor
) =>
| PayloadFor
// replace payload
| undefined // keep current payload
| null // suppress emission
| Promise<...>
): Harness
```
**Pattern types:**
| Pattern | Matches |
| ------------------ | -------------------------------------------------- |
| `'prompt.system'` | Exact tag |
| `'prompt.*'` | All single-segment `prompt.X` tags |
| `'nudge.**'` | All `nudge.X` and `nudge.X.Y` tags (multi-segment) |
| `'**'` | Every tag |
| `(tag) => boolean` | Custom predicate |
**Transform semantics:**
* Return a value → **replaces** current payload
* Return `undefined` → **keeps** current payload (pass-through)
* Return `null` → **suppresses** the emission (removed from pipeline)
* Multiple transforms on same tag chain in order: broadest pattern first, most-specific last
**Example — suppress all nudges in a bare-LLM ablation:**
```ts
harness.on('nudge.*', () => null)
```
**Example — localize system prompt:**
```ts
harness.on('prompt.system', (text, ctx) => `[locale: fr]\n${text}`)
```
## `harness.tap(pattern, fn)` — Side Effect
[Section titled “harness.tap(pattern, fn) — Side Effect”](#harnesstappattern-fn--side-effect)
Observe an emission without changing it. Runs after all transforms.
**Signature:**
```ts
harness.tap(
pattern: TagPattern | TagPattern[],
fn: (payload: PayloadFor
, ctx: ContextFor
) => void | Promise
): Harness
```
Taps run in registration order, after transforms are finalized. A tap that throws is a bug — they run unconditionally with the final value.
**Example — telemetry:**
```ts
harness.tap('**', (payload, ctx) => {
otel.record(ctx.phase, ctx.iteration, payload);
});
```
## `harness.before(phase, fn)` — Phase Pre-Hook
[Section titled “harness.before(phase, fn) — Phase Pre-Hook”](#harnessbeforephase-fn--phase-pre-hook)
Run before a kernel phase. Can abort or skip the iteration.
**Signature:**
```ts
harness.before(
phase: Phase,
fn: (ctx: { phase: Phase; iteration: number; state: KernelStateLike }) =>
| void
| Promise
| { readonly abort: 'stop' | 'terminate'; readonly reason?: string }
| { readonly skip: true }
): Harness
```
**Return values:**
| Return | Effect |
| ------------------------ | ------------------------------------ |
| `void` / `undefined` | Continue normally |
| `{ abort: 'stop' }` | End loop gracefully (status: done) |
| `{ abort: 'terminate' }` | End loop as failure (status: failed) |
| `{ skip: true }` | Skip this iteration, continue loop |
**Example — custom iteration limit:**
```ts
harness.before('think', (ctx) => {
if (ctx.iteration >= 15) return { abort: 'stop', reason: 'custom-limit' };
});
```
## `harness.after(phase, fn)` — Phase Post-Hook
[Section titled “harness.after(phase, fn) — Phase Post-Hook”](#harnessafterphase-fn--phase-post-hook)
Run after a kernel phase completes. Same signature as `.before()` but fires after.
## `harness.onError(phase, fn)` — Error Hook
[Section titled “harness.onError(phase, fn) — Error Hook”](#harnessonerrorphase-fn--error-hook)
Run when a phase throws. Can optionally recover by returning a replacement state.
**Signature:**
```ts
harness.onError(
phase: Phase | '*',
fn: (error: unknown, ctx: { phase: Phase | '*'; iteration: number }) =>
| void
| Promise
| { readonly recover: KernelStateLike }
): Harness
```
Use `'*'` to catch errors from any phase. Return `{ recover: newState }` to inject a replacement state and continue the loop.
## `harness.emit(tag, payload)` — Inject at Build Time
[Section titled “harness.emit(tag, payload) — Inject at Build Time”](#harnessemittag-payload--inject-at-build-time)
Inject a payload directly at build time. Use for initial seeding.
## `harness.use(fn)` — Sub-composition
[Section titled “harness.use(fn) — Sub-composition”](#harnessusefn--sub-composition)
Nest a composition block. Useful for reusable plugin patterns.
```ts
harness.use((h) => {
h.tap('observation.tool-result', logFn);
h.before('act', approvalFn);
});
```
## Available Phases
[Section titled “Available Phases”](#available-phases)
```plaintext
bootstrap → guardrail → cost-route → strategy-select → think → act
→ observe → verify → memory-flush → cost-track → audit → complete
```
Phase hooks fire in this order per iteration. `bootstrap` and `complete` fire once per run.
## Context Fields
[Section titled “Context Fields”](#context-fields)
All hook/transform callbacks receive a `ctx` with at minimum:
```ts
{
iteration: number; // 0-indexed
phase: Phase; // current phase name
state: KernelStateLike; // current kernel state snapshot
strategy: string; // active reasoning strategy ('reactive', 'tot', etc.)
}
```
Some tags carry richer contexts — see [Harness Tag Reference](/reference/harness-tags).
## Killswitches
[Section titled “Killswitches”](#killswitches)
Prebuilt compositions from `reactive-agents/compose/killswitches`:
```ts
import {
budgetLimit, timeoutAfter, maxIterations,
requireApprovalFor, watchdog
} from 'reactive-agents/compose/killswitches';
```
See [Composition Recipes](/cookbook/composition-recipes) for usage examples.
# Configuration Reference
> Complete reference of all builder methods, defaults, and environment variables
# Configuration Reference
[Section titled “Configuration Reference”](#configuration-reference)
Every aspect of Reactive Agents is configurable through the builder API. This page documents all available options, their defaults, and how they affect agent behavior. For ready-made chains, see [Common builder stacks](/cookbook/builder-stacks/).
## Builder Methods
[Section titled “Builder Methods”](#builder-methods)
### Core
[Section titled “Core”](#core)
| Method | Default | Description |
| --------------------------------------------- | ---------------- | ------------------------------------------------------------------------------------------------ |
| `.withName(name)` | `"agent"` | Agent identifier used in logs and metrics |
| `.withProvider(provider)` | `"test"` | LLM provider: `"anthropic"` \| `"openai"` \| `"gemini"` \| `"ollama"` \| `"litellm"` \| `"test"` |
| `.withModel(model)` | Provider default | Model string or `ModelParams` (`model`, `thinking?`, `temperature?`, `maxTokens?`) |
| `.withSystemPrompt(prompt)` | none | Custom system prompt prepended to all LLM calls |
| `.withPersona(persona)` | none | Structured persona: `{ name?, role?, background?, instructions?, tone? }` |
| `.withEnvironment(context)` | none | Extra `Record` merged into system prompt (beyond built-in date/tz/platform) |
| `.withMaxIterations(n)` | `10` | Maximum reasoning loop iterations before stopping |
| `.withTimeout(ms)` | none | Per-execution timeout in milliseconds |
| `.withStrictValidation()` | off | Missing API keys / mismatches become build errors |
| `.withRetryPolicy({ maxRetries, backoffMs })` | `maxRetries: 0` | Transient LLM retries |
| `.withErrorHandler(fn)` | none | Observe-only callback when `run()` fails |
### Reasoning
[Section titled “Reasoning”](#reasoning)
| Method | Default | Description |
| -------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `.withReasoning(options?)` | disabled | Strategies, ICS (`synthesis`, `synthesisModel`, …), strategy switching, `adaptive`, per-strategy bundles (may include e.g. `kernelMaxIterations` on `reflexion`). See [Reasoning](/guides/reasoning/) and [Builder API](/reference/builder-api/) |
### Tools & context
[Section titled “Tools & context”](#tools--context)
| Method | Default | Description |
| ---------------------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `.withTools(options?)` | disabled | `{ tools?` (custom defs + **Effect** handlers), `resultCompression?`, `allowedTools?`, `adaptive?` } |
| `.withDocuments(docs)` | none | `DocumentSpec[]` ingested at build for `rag-search` |
| `.withRequiredTools(config)` | none | `{ tools?, adaptive?, maxRetries? }` |
| `.withMCP(config)` | none | MCP: `{ name, transport, command?, args?, endpoint?, headers?, env?, cwd? }` (see [Builder API](/reference/builder-api/) transport table) |
| `.withMetaTools(config?)` | on with tools | Conductor suite; pass `false` to disable defaults |
### LLM resilience & pricing
[Section titled “LLM resilience & pricing”](#llm-resilience--pricing)
| Method | Default | Description |
| ------------------------------- | ------------- | -------------------------------------------------------------- |
| `.withCircuitBreaker(config?)` | off until set | Provider circuit breaker (`failureThreshold`, `cooldownMs`, …) |
| `.withRateLimiting(config?)` | off until set | RPM / TPM / concurrency limits |
| `.withModelPricing(registry)` | none | Static $/1M token overrides |
| `.withDynamicPricing(provider)` | none | Fetch pricing at build |
| `.withFallbacks(config)` | none | Provider/model chain + `errorThreshold` |
| `.withCacheTimeout(ms)` | `3_600_000` | Semantic cache TTL (1h) |
### Memory
[Section titled “Memory”](#memory)
| Method | Default | Description |
| ----------------------------------- | -------- | ------------------------------------------------------------------------------------- |
| `.withMemory(options?)` | disabled | Enable memory. No args = standard tier. Options: `{ tier: "standard" \| "enhanced" }` |
| `.withMemoryConsolidation(config?)` | disabled | Background memory intelligence: `{ threshold?, decayFactor?, pruneThreshold? }` |
| `.withExperienceLearning()` | disabled | Cross-agent tool-use pattern learning |
### Safety & control
[Section titled “Safety & control”](#safety--control)
| Method | Default | Description |
| ------------------------------------ | -------- | -------------------------------------------------------------------------------------------------------- |
| `.withGuardrails(options?)` | disabled | Toggles: `{ injection?, pii?, toxicity? }` (default **true** each when enabled), plus `customBlocklist?` |
| `.withVerification(options?)` | disabled | Strategy toggles + thresholds (`passThreshold`, `hallucinationDetection`, …) |
| `.withKillSwitch()` | disabled | Pause / resume / stop / terminate |
| `.withBehavioralContracts(contract)` | none | Behavioral contract passed to guardrails layer |
### Cost & context
[Section titled “Cost & context”](#cost--context)
| Method | Default | Description |
| ------------------------------ | ------------- | ----------------------------------------------------------------------------------------------------- |
| `.withCostTracking(options?)` | disabled | Budget enforcement (USD): `{ perRequest?, perSession?, daily?, monthly? }` |
| `.withContextProfile(profile)` | auto-detected | Model-adaptive context budgets / compaction — see [Context engineering](/guides/context-engineering/) |
### Observability & streaming
[Section titled “Observability & streaming”](#observability--streaming)
| Method | Default | Description |
| ------------------------------ | --------------------------------- | ---------------------------------------------------------------------------- |
| `.withObservability(options?)` | disabled | `{ verbosity?, live?, file?` (JSONL), `logPrefix?, logModelIO? }` |
| `.withStreaming(options?)` | `"tokens"` | Default `agent.runStream()` density: `{ density?: "tokens" \| "full" }` |
| `.withTelemetry(config?)` | `{ mode: "isolated" }` if enabled | Telemetry privacy / contribute modes |
| `.withLogging(config)` | none | Structured logs: level, format, `output` (console / file / stream), rotation |
| `.withAudit()` | disabled | Compliance audit logging |
| `.withEvents()` | — | Wire EventBus for `agent.subscribe()` |
### Identity & Orchestration
[Section titled “Identity & Orchestration”](#identity--orchestration)
| Method | Default | Description |
| ------------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------- |
| `.withIdentity()` | disabled | Ed25519 agent certificates + RBAC |
| `.withOrchestration()` | disabled | Multi-agent workflow engine |
| `.withInteraction()` | disabled | 5 autonomy modes + checkpoints |
| `.withSelfImprovement()` | disabled | Cross-task strategy outcome learning |
| `.withReactiveIntelligence(false)` | on | Pass `false` to disable entropy/controller/telemetry stack |
| `.withReactiveIntelligence(options?)` | defaults | Entropy, controller, hooks, `autonomy`, `constraints` — see [Reactive Intelligence](/features/reactive-intelligence/) |
| `.withHealthCheck()` | disabled | Exposes `agent.health()` |
### Sub-agents & A2A
[Section titled “Sub-agents & A2A”](#sub-agents--a2a)
| Method | Default | Description |
| --------------------------------- | ---------------- | ---------------------------------------------- |
| `.withA2A(options?)` | `{ port: 3000 }` | Local A2A JSON-RPC server (`port`, `basePath`) |
| `.withAgentTool(name, config)` | none | Register a static sub-agent as a tool |
| `.withDynamicSubAgents(options?)` | disabled | Allow LLM to spawn sub-agents at runtime |
| `.withRemoteAgent(name, url)` | none | Connect to a remote agent via A2A protocol |
### Gateway
[Section titled “Gateway”](#gateway)
| Method | Default | Description |
| ------------------------ | -------- | --------------------------------------------------------------------------------------------- |
| `.withGateway(options?)` | disabled | Persistent autonomous harness: `{ heartbeat?, crons?, webhooks?, policies?, accessControl? }` |
### Build, test & serialization
[Section titled “Build, test & serialization”](#build-test--serialization)
| Method | Default | Description |
| ------------------------------------------------------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `.withTestScenario(turns)` | none | Deterministic **test** provider. `TestTurn[]` from `@reactive-agents/llm-provider`; forces `provider: "test"`. |
| `.withLayers(layers)` | none | Merge custom Effect `Layer`s into the runtime |
| `.withSkills(config?)` | disabled | Living skills: `paths`, `packages`, `evolution`, `overrides` |
| `.toConfig()` / `ReactiveAgents.fromConfig()` / `fromJSON()` | — | **Agent as Data** — round-trip via `agentConfigToJSON` / `agentConfigFromJSON` (`reactive-agents` or `@reactive-agents/runtime`) |
| `agentFn` / `pipe` / `parallel` / `race` | — | Promise-based multi-agent composition (see [Builder API](/reference/builder-api/)) |
| `agent.registerTool()` / `unregisterTool()` / `ingest()` | — | Runtime tool + RAG ingestion on built agents |
## Environment Variables
[Section titled “Environment Variables”](#environment-variables)
| Variable | Required For | Default | Description |
| ---------------------- | --------------------------- | -------------------------- | --------------------------------------------------------------------- |
| `ANTHROPIC_API_KEY` | Anthropic provider | — | Anthropic API key |
| `OPENAI_API_KEY` | OpenAI/LiteLLM provider | — | OpenAI API key |
| `GOOGLE_API_KEY` | Gemini provider | — | Google AI API key |
| `TAVILY_API_KEY` | Web search tool (primary) | — | Tavily search API key |
| `BRAVE_SEARCH_API_KEY` | Web search tool (secondary) | — | Brave Search API key (`X-Subscription-Token`); alias: `BRAVE_API_KEY` |
| `EMBEDDING_PROVIDER` | Enhanced memory tier | `"openai"` | Embedding provider |
| `EMBEDDING_MODEL` | Enhanced memory tier | `"text-embedding-3-small"` | Embedding model name |
| `LLM_DEFAULT_MODEL` | All providers | Provider default | Override default model |
## Hardcoded Defaults
[Section titled “Hardcoded Defaults”](#hardcoded-defaults)
These values have sensible defaults but are not currently configurable via the builder:
| Value | Default | Where | Notes |
| ------------------------- | ------------ | ------------------------- | -------------------------------------------- |
| Max sub-agent iterations | 4 | `packages/tools/src/` | Sub-agents capped at 4 iterations |
| Max recursion depth | 3 | `packages/tools/src/` | Nested sub-agent limit |
| Parent context forwarding | 2000 chars | `packages/tools/src/` | Max parent context sent to sub-agents |
| Memory decay half-life | 7 days | `packages/memory/src/` | Episodic memory decay rate |
| Compaction trigger | 6 iterations | `packages/reasoning/src/` | Steps before context compaction (local tier) |
# Harness Tag Reference
> Complete catalog of harness emission tags, payloads, and contexts (Wave A–D)
Harness tags are the interception points that `.compose()` blocks can observe and reshape. Each tag has a typed payload and a typed context.
> **Note:** This catalog covers the Wave A–D tag set (7 tags). The full v0.12 catalog will expand to 24+ tags via build-time codegen.
## Tag Catalog
[Section titled “Tag Catalog”](#tag-catalog)
### `prompt.system`
[Section titled “prompt.system”](#promptsystem)
Emitted when the kernel assembles the system prompt for an LLM call.
**Payload:** `string` — the full system prompt text\
**Context:** `BaseCtx`\
**Phase:** `think`
```ts
harness.on('prompt.system', (text, ctx) => {
return `[tenant: ${ctx.strategy}]\n${text}`;
});
```
***
### `nudge.loop-detected`
[Section titled “nudge.loop-detected”](#nudgeloop-detected)
Emitted when the loop detector identifies a repetitive pattern.
**Payload:** `string` — the nudge message injected into context\
**Context:** `NudgeCtx` — includes `trigger: string`, `severity: 'info' | 'warn' | 'critical'`\
**Phase:** `think`
```ts
harness.on('nudge.loop-detected', (msg, ctx) => {
console.warn(`Loop at iter ${ctx.iteration} [${ctx.severity}]: ${ctx.trigger}`);
return msg; // pass through unchanged
});
```
***
### `nudge.healing-failure`
[Section titled “nudge.healing-failure”](#nudgehealing-failure)
Emitted when tool call healing fails after all recovery stages.
**Payload:** `string` — the healing failure nudge message\
**Context:** `NudgeCtx` — includes `trigger: string`, `severity`\
**Phase:** `act`
***
### `message.tool-result`
[Section titled “message.tool-result”](#messagetool-result)
Emitted when a tool result is added to the conversation thread (what the LLM sees).
**Payload:** `KernelMessageLike` — the message object:
```ts
type KernelMessageLike =
| { role: 'assistant'; content: string; toolCalls?: unknown[] }
| { role: 'tool_result'; toolCallId: string; toolName: string; content: string; isError?: boolean }
| { role: 'user'; content: string }
```
**Context:** `ToolResultCtx` — includes `toolName`, `callId`, `healed: boolean`, `durationMs`\
**Phase:** `act`
```ts
// Redact PII from tool results before LLM sees them
harness.on('message.tool-result', (msg) => {
if (msg.role === 'tool_result') {
return { ...msg, content: redact(msg.content) };
}
return msg;
});
```
***
### `observation.tool-result`
[Section titled “observation.tool-result”](#observationtool-result)
Emitted when a tool result is recorded as an observation step (what systems observe).
**Payload:** `ObservationStepLike`:
```ts
type ObservationStepLike = {
type: string;
content?: string;
metadata?: Record;
}
```
**Context:** `ToolResultCtx`\
**Phase:** `act`
```ts
harness.tap('observation.tool-result', (obs, ctx) => {
metrics.record('tool.duration', ctx.durationMs, { tool: ctx.toolName });
});
```
***
### `lifecycle.failure`
[Section titled “lifecycle.failure”](#lifecyclefailure)
Emitted when the agent enters a failure state.
**Payload:** `LifecycleFailurePayload`:
```ts
type LifecycleFailurePayload = {
reason: 'tool-error' | 'llm-refusal' | 'verifier-rejection';
errorMessage: string;
attemptNumber: number;
failureStreak: number;
currentStrategy: string;
}
```
**Context:** `BaseCtx`
```ts
harness.tap('lifecycle.failure', (failure) => {
alerting.trigger({ reason: failure.reason, streak: failure.failureStreak });
});
```
***
### `control.strategy-evaluated`
[Section titled “control.strategy-evaluated”](#controlstrategy-evaluated)
Emitted when the strategy evaluator scores the current strategy.
**Payload:** `ControlStrategyEvaluatedPayload`:
```ts
type ControlStrategyEvaluatedPayload = {
currentStrategy: string;
score: number;
failureStreak: number;
recommendedAction: 'continue' | 'switch' | 'escalate';
availableStrategies: string[];
}
```
**Context:** `BaseCtx`
```ts
harness.tap('control.strategy-evaluated', (eval) => {
if (eval.recommendedAction === 'escalate') {
notify.ops(`Strategy escalation: ${eval.currentStrategy} (score: ${eval.score})`);
}
});
```
***
## Context Types
[Section titled “Context Types”](#context-types)
### `BaseCtx`
[Section titled “BaseCtx”](#basectx)
```ts
{
iteration: number;
phase: Phase;
state: Readonly;
strategy: string;
}
```
### `NudgeCtx` (extends BaseCtx)
[Section titled “NudgeCtx (extends BaseCtx)”](#nudgectx-extends-basectx)
```ts
{
trigger: string; // what triggered the nudge
severity: 'info' | 'warn' | 'critical';
}
```
### `ToolResultCtx` (extends BaseCtx)
[Section titled “ToolResultCtx (extends BaseCtx)”](#toolresultctx-extends-basectx)
```ts
{
toolName: string;
callId: string;
healed: boolean; // true if tool call was auto-healed
durationMs: number; // wall-clock tool execution time
}
```
# API Stability & Versioning
> SemVer commitments, stability tiers, what's stable vs experimental in v0.10, and the deprecation policy.
This page is the honest answer to “is this safe to depend on?”
Reactive Agents follows **Semantic Versioning** (`major.minor.patch`). The framework is currently in `0.x`, which under SemVer means **minor bumps may include breaking changes** to anything not marked stable below. We document each break in the [CHANGELOG](https://github.com/tylerjrbuell/reactive-agents-ts/blob/main/CHANGELOG.md) and ship a migration note for anything user-facing.
## Stability tiers
[Section titled “Stability tiers”](#stability-tiers)
Every public surface falls into one of three tiers. Tier is declared by JSDoc tag on the export — `@stable`, `@unstable`, `@experimental` — and summarized below.
| Tier | Promise | Breaks allowed |
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------- |
| **Stable** (`@stable`) | Source-compatible across `0.x` minor bumps. Behavior changes get a deprecation warning + one minor cycle before removal. | Patch versions only fix bugs. |
| **Unstable** (`@unstable`) | API may change between minor versions with a CHANGELOG note. Suitable for production if you pin exactly. | Yes, between minors, with a migration note. |
| **Experimental** (`@experimental`) | Active R\&D. May change shape between any release. Use at your own risk; expect to update code on each upgrade. | Anytime, including patch. |
## What’s stable in v0.10
[Section titled “What’s stable in v0.10”](#whats-stable-in-v010)
The following surfaces are tier-1 stable. We will not break these without a major bump.
* **Builder entry point** — `ReactiveAgents.create()` and the `.with*()` chain syntax
* **Provider selection** — `.withProvider("anthropic" | "openai" | "google" | "ollama" | "litellm" | "local")` and the `LLMProvider` interface
* **Reasoning core** — `.withReasoning()` with the documented `ReasoningOptions` shape; the five strategies (`ReAct`, `Reflexion`, `Plan-Execute`, `Tree-of-Thought`, `Adaptive`)
* **Tool surface** — `.withTools()`, `defineTool()`, MCP attachment via `.withMCP()`, and the `Tool` interface
* **Event bus** — All event tags consumed by the public observability layer (`ToolCallStarted`, `ToolCallCompleted`, `LLMExchangeEmitted`, `StrategySwitched`, `VerifierVerdictEmitted`, plus the 30+ tags listed in `event-bus.ts`)
* **Lifecycle hooks** — `.withHook(phase, timing, fn)` for the 12 phases and `before` / `after` / `on-error` timings
* **Compose API** — `.compose()` (alias: `.withHarness()`) for harness composition; `.on()`, `.tap()`, `.before()`, `.after()`, `.onError()` transforms and hooks; all 12-phase composition and tag pattern matching
* **Snapshot & Replay** (v0.11) — `@reactive-agents/replay` package: `loadRecordedRun`, `replay`, `makeReplayController`, `makeReplayToolLayer`, `diffTraces`, `computeArgsHash`. The `ToolCallCompleted` event payload’s new `args`, `result`, `error`, `resultTruncated` fields are also stable.
* **AgentResult shape** — `.run()` and `.runStream()` return values
* **Raw provider clients** — `AnthropicProviderLive`, `OpenAIProviderLive`, `LocalProviderLive`, `GeminiProviderLive`, `LiteLLMProviderLive` exported as standalone Effect Layers (you can skip the harness entirely)
## What’s `@unstable` in v0.10
[Section titled “What’s @unstable in v0.10”](#whats-unstable-in-v010)
These work, but the **shape may change** in `0.11`. Pin exact versions if you depend on them.
* **`KernelHooks` interface** — the inner-loop event taps (`onThought`, `onAction`, `onObservation`, etc.). The 12-phase outer hooks are stable; the inner kernel taps may consolidate.
* **Healing pipeline stages** — `runHealingPipeline` and the 4 built-in stages are exported, but the stage list is not user-extensible yet. A `.withHealing(stages)` builder is planned for 0.11.
* **Context curator internals** — `withContextProfile` is stable; the curator’s compression strategy is not user-replaceable yet.
* **Arbitrator** — `withCustomTermination(predicate)` is stable for boolean overrides; full `withArbitrator(impl)` for replacing the termination pipeline is Phase 2.
* **Verifier strategy** — `withVerification(options)` accepts options today; replaceable verifier impl is Phase 2.
* **Cost router policy** — `withCostTracking()` records spend (stable); routing policy itself is not user-replaceable yet.
* **Strategy switcher heuristic** — toggleable via `ReasoningOptions.strategySwitching` (stable); the heuristic itself is not yet replaceable.
* **Calibration field schema** — fields are growing; consumer count is small. Expect additions and possible renames.
## What’s `@experimental` in v0.10
[Section titled “What’s @experimental in v0.10”](#whats-experimental-in-v010)
Use at your own risk. Will change.
* **A2A protocol surface** (`packages/a2a`) — wire format and JSON-RPC method names may change as the spec evolves
* **Reactive observer / entropy scoring tunables** — thresholds, scoring functions
* **Living Skills runtime in Cortex** — UI and persistence schema not finalized
* **Sub-agent delegation API** — `.withSubAgents()` shape under iteration
## Deprecation policy
[Section titled “Deprecation policy”](#deprecation-policy)
When a stable surface is being replaced:
1. The old API stays functional and gets `@deprecated` JSDoc with a pointer to the replacement
2. A console warning fires at runtime (suppressible via `RA_SUPPRESS_DEPRECATION=1`)
3. Removal happens **no sooner than** one full minor cycle later (e.g., deprecated in 0.11 → removed earliest in 0.12)
4. The CHANGELOG lists the migration step for every removal
We will **never** silently change the behavior of a stable API. If a bugfix changes observable behavior, it ships behind a flag or in a major bump.
## How to depend on Reactive Agents
[Section titled “How to depend on Reactive Agents”](#how-to-depend-on-reactive-agents)
| Risk tolerance | Recommendation |
| ------------------------------------ | ---------------------------------------------------------------------------------------------------- |
| Production app, low-touch upgrades | Pin patch versions (`"reactive-agents": "0.10.2"`). Consume only `@stable` APIs. |
| Active development, monthly upgrades | Pin minor (`"~0.10.0"`). Read the CHANGELOG before bumping. `@unstable` OK if covered by your tests. |
| Following main, contributing | Pin to a commit SHA or use `workspace:*`. `@experimental` is fair game. |
## What we want feedback on
[Section titled “What we want feedback on”](#what-we-want-feedback-on)
If you’ve adopted Reactive Agents and want a specific component promoted from `@unstable` to `@stable`, [open an issue](https://github.com/tylerjrbuell/reactive-agents-ts/issues). The promotion criteria are: 30+ days at current shape with no reported design issues, and at least one production user.
We’d rather under-promise on stability today than break your code tomorrow.