` 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.
The power tier
Reactive Agents has three tiers, in ascending order of control: `createAgent(config)` (the declarative front door, 90% of cases) → the fluent `ReactiveAgents.create().withX()` builder (conditional/imperative construction) → **`.compose(...)`** (this page — harness-level phase transforms and killswitches for library authors and precise chokepoints). Each is a strict superset of the last; reach for compose only when config keys and withers can’t express what you need.
## 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. `requireApprovalFor` gates run on the same approval mechanism as [Interaction Modes](/features/interaction/).
# 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/).
## Declarative config: `createAgent(config)`
[Section titled “Declarative config: createAgent(config)”](#declarative-config-createagentconfig)
The declarative front door takes a single `AgentConfig` object and returns the same agent the fluent builder produces — `createAgent({ tools: { allowedTools } })` ≡ `.withTools({ allowedTools })` (same key, same result). Unknown or malformed keys are rejected loudly with the path named.
```typescript
import { createAgent } from 'reactive-agents'
const agent = await createAgent({
name: 'researcher',
provider: 'anthropic',
model: 'claude-opus-4-8',
profile: 'balanced',
tools: { allowedTools: ['web-search', 'file-write'] },
})
const result = await agent.run('Summarize the latest on X')
```
`profile` (`"lean" | "balanced" | "intelligent"`) sets a preset baseline applied FIRST; explicit sibling keys override it.
### Complete `AgentConfig` field reference
[Section titled “Complete AgentConfig field reference”](#complete-agentconfig-field-reference)
Every key of `AgentConfig`, its type, and whether it is required. This table is **generated** from `AgentConfigSchema` (the single source of truth) — see the [Builder API](/reference/builder-api/) for the fluent method that sets each key.
| Config key | Type | Required | Description |
| ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- | -------- | ----------- |
| `adaptiveHarness` | `boolean` | no | |
| `agentId` | `string` | no | |
| `budget.costLimit` | `number` | no | |
| `budget.tokenLimit` | `number` | no | |
| `budget.warningRatio` | `number` | no | |
| `circuitBreaker` | `unknown` | no | |
| `costTracking.daily` | `number` | no | |
| `costTracking.monthly` | `number` | no | |
| `costTracking.perRequest` | `number` | no | |
| `costTracking.perSession` | `number` | no | |
| `durableRuns.checkpointEvery` | `number` | no | |
| `durableRuns.dir` | `string` | no | |
| `execution.maxIterations` | `number` | no | |
| `execution.minIterations` | `number` | no | |
| `execution.retryPolicy.backoffMs` | `number` | **yes** | |
| `execution.retryPolicy.maxRetries` | `number` | **yes** | |
| `execution.strictValidation` | `boolean` | no | |
| `execution.timeoutMs` | `number` | no | |
| `fabricationGuard` | `off` \| `warn` \| `block` | no | |
| `fallbacks.providers` | `array` | no | |
| `features.audit` | `boolean` | no | |
| `features.costTracking` | `boolean` | no | |
| `features.guardrails` | `boolean` | no | |
| `features.healthCheck` | `boolean` | no | |
| `features.killSwitch` | `boolean` | no | |
| `features.memory` | `boolean` | no | |
| `features.observability` | `boolean` | no | |
| `features.prompts` | `boolean` | no | |
| `features.reactiveIntelligence` | `boolean` | no | |
| `features.reasoning` | `boolean` | no | |
| `features.selfImprovement` | `boolean` | no | |
| `features.streaming` | `boolean` | no | |
| `features.tools` | `boolean` | no | |
| `features.verification` | `boolean` | no | |
| `gateway.accessControl.accessPolicy` | `allowlist` \| `blocklist` \| `open` | no | |
| `gateway.accessControl.allowedSenders` | `array` | no | |
| `gateway.accessControl.blockedSenders` | `array` | no | |
| `gateway.accessControl.mode` | `chat` \| `task` | no | |
| `gateway.accessControl.replyToUnknown` | `string` | no | |
| `gateway.accessControl.sessionTtlDays` | `number` | no | |
| `gateway.accessControl.unknownSenderAction` | `skip` \| `escalate` | no | |
| `gateway.crons` | `array` | no | |
| `gateway.heartbeat.instruction` | `string` | no | |
| `gateway.heartbeat.intervalMs` | `number` | no | |
| `gateway.heartbeat.maxConsecutiveSkips` | `number` | no | |
| `gateway.heartbeat.policy` | `always` \| `adaptive` \| `conservative` | no | |
| `gateway.persistMemoryAcrossRuns` | `boolean` | no | |
| `gateway.policies.dailyTokenBudget` | `number` | no | |
| `gateway.policies.heartbeatPolicy` | `always` \| `adaptive` \| `conservative` | no | |
| `gateway.policies.maxActionsPerHour` | `number` | no | |
| `gateway.policies.mergeWindowMs` | `number` | no | |
| `gateway.policies.requireApprovalFor` | `array` | no | |
| `gateway.port` | `number` | no | |
| `gateway.timezone` | `string` | no | |
| `gateway.webhooks` | `array` | no | |
| `grounding.maxRetries` | `number` | no | |
| `grounding.mode` | `block` \| `warn` | **yes** | |
| `grounding.tolerance` | `number` | no | |
| `guardrails.customBlocklist` | `array` | no | |
| `guardrails.injection` | `boolean` | no | |
| `guardrails.pii` | `boolean` | no | |
| `guardrails.toxicity` | `boolean` | no | |
| `horizonProfile` | `long` | no | |
| `logging.filePath` | `string` | no | |
| `logging.format` | `text` \| `json` | no | |
| `logging.level` | `debug` \| `info` \| `warn` \| `error` | no | |
| `logging.maxFiles` | `number` | no | |
| `logging.maxFileSizeBytes` | `number` | no | |
| `logging.output` | `console` \| `file` | no | |
| `maxTokens` | `number` | no | |
| `mcpServers` | `array` | no | |
| `memory.capacity` | `number` | no | |
| `memory.dbPath` | `string` | no | |
| `memory.evictionPolicy` | `fifo` \| `lru` \| `importance` | no | |
| `memory.experienceLearning` | `boolean` | no | |
| `memory.importanceThreshold` | `number` | no | |
| `memory.maxEntries` | `number` | no | |
| `memory.memoryConsolidation` | `boolean` | no | |
| `memory.retainDays` | `number` | no | |
| `memory.tier` | `standard` \| `enhanced` | no | |
| `model` | `string` | no | |
| `name` | `string` | **yes** | |
| `numCtx` | `number` | no | |
| `observability.audit` | `boolean` | no | |
| `observability.cortex` | `unknown` | no | |
| `observability.costs` | `unknown` | no | |
| `observability.file` | `string` | no | |
| `observability.health` | `boolean` | no | |
| `observability.live` | `boolean` | no | |
| `observability.logging.filePath` | `string` | no | |
| `observability.logging.format` | `text` \| `json` | no | |
| `observability.logging.level` | `debug` \| `info` \| `warn` \| `error` | no | |
| `observability.logging.maxFiles` | `number` | no | |
| `observability.logging.maxFileSizeBytes` | `number` | no | |
| `observability.logging.output` | `console` \| `file` | no | |
| `observability.logModelIO` | `boolean` | no | |
| `observability.telemetry` | `unknown` | no | |
| `observability.tracing` | `unknown` | no | |
| `observability.verbosity` | `minimal` \| `normal` \| `verbose` \| `debug` | no | |
| `outputSchemaOptions.abstainBelow` | `number` | no | |
| `outputSchemaOptions.mode` | `auto` \| `fast` \| `grounded` | no | |
| `outputSchemaOptions.onParseFail` | `degrade` \| `throw` | no | |
| `persona.background` | `string` | no | |
| `persona.instructions` | `string` | no | |
| `persona.name` | `string` | no | |
| `persona.role` | `string` | no | |
| `persona.tone` | `string` | no | |
| `pricingRegistry` | `object` | no | |
| `profile` | `lean` \| `balanced` \| `intelligent` | no | |
| `provider` | `anthropic` \| `openai` \| `ollama` \| `gemini` \| `litellm` \| `groq` \| `xai` \| `test` | **yes** | |
| `rateLimiting.maxConcurrent` | `number` | no | |
| `rateLimiting.requestsPerMinute` | `number` | no | |
| `rateLimiting.tokensPerMinute` | `number` | no | |
| `reactiveIntelligence.enabled` | `boolean` | no | |
| `reasoning.auditRationale` | `boolean` | no | |
| `reasoning.defaultStrategy` | `reactive` \| `plan-execute-reflect` \| `tree-of-thought` \| `reflexion` \| `adaptive` \| `direct` \| `code-action` \| `blueprint` | no | |
| `reasoning.enableStrategySwitching` | `boolean` | no | |
| `reasoning.fallbackStrategy` | `string` | no | |
| `reasoning.harness.assemblyDebug` | `boolean` | no | |
| `reasoning.harness.auditRationale` | `boolean` | no | |
| `reasoning.harness.lazyDisclosure` | `boolean` | no | |
| `reasoning.harness.promptDumpPathPrefix` | `string` | no | |
| `reasoning.harness.recencyBudgetChars` | `number` | no | |
| `reasoning.harness.thoughtContinuity` | `boolean` | no | |
| `reasoning.harness.toolDiscovery` | `boolean` | no | |
| `reasoning.harness.toolIndex` | `boolean` | no | |
| `reasoning.harness.toolIndexMaxEntries` | `number` | no | |
| `reasoning.harness.toolObserveSymmetry` | `boolean` | no | |
| `reasoning.harness.toolResultBudgetChars` | `number` | no | |
| `reasoning.harness.treeOfThoughtExploreBudgetMs` | `number` | no | |
| `reasoning.harness.verboseRules` | `boolean` | no | |
| `reasoning.maxStrategySwitches` | `number` | no | |
| `requiredTools.adaptive` | `boolean` | no | |
| `requiredTools.maxRetries` | `number` | no | |
| `requiredTools.tools` | `array` | no | |
| `skillPersistence` | `boolean` | no | |
| `stallPolicy.escalateNudgeContent` | `boolean` | no | |
| `stallPolicy.ignoredNudgeTolerance` | `number` | no | |
| `systemPrompt` | `string` | no | |
| `taskContext` | `object` | no | |
| `temperature` | `number` | no | |
| `thinking` | `boolean` | no | |
| `tools.adaptive` | `boolean` | no | |
| `tools.allowedTools` | `array` | no | |
| `tools.builtins` | `unknown` | no | |
| `tools.focusedTools` | `array` | no | |
| `tools.terminal` | `boolean` | no | |
| `verification.factDecomposition` | `boolean` | no | |
| `verification.hallucinationDetection` | `boolean` | no | |
| `verification.hallucinationThreshold` | `number` | no | |
| `verification.multiSource` | `boolean` | no | |
| `verification.nli` | `boolean` | no | |
| `verification.onReject` | `block` \| `annotate` \| `proceed` | no | |
| `verification.passThreshold` | `number` | no | |
| `verification.riskThreshold` | `number` | no | |
| `verification.selfConsistency` | `boolean` | no | |
| `verification.semanticEntropy` | `boolean` | no | |
| `verification.useLLMTier` | `boolean` | no | |
## 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"` \| `"groq"` \| `"xai"` \| `"ollama"` \| `"litellm"` \| `"test"` |
| `.withModel(model)` | Provider default | Model string or `ModelParams` (`model`, `thinking?`, `temperature?`, `maxTokens?`, `numCtx?`). `numCtx` pins the exact provider context window (Ollama `num_ctx`); also a top-level `AgentConfig` field |
| `.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; retrieval via the unified `find` meta-tool |
| `.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 | Ordered provider cascade — `{ providers }`; falls back to the next provider on any error |
### 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?, telemetry?, tracing? }` — `telemetry: true \| TelemetryConfig` is the entry point for run telemetry (privacy modes, default `{ mode: "isolated" }`) |
| `.withStreaming(options?)` | `"tokens"` | Default `agent.runStream()` density: `{ density?: "tokens" \| "full" }` |
| `.withLogging(config)` | none | Structured logs: level, format, `output` (console / file / stream), rotation |
| `.withAudit()` | disabled | Compliance audit logging |
| `.withEvents()` | — | Wire EventBus for `agent.subscribe()` |
### Metacognition & control
[Section titled “Metacognition & control”](#metacognition--control)
| Method | Default | Description |
| ------------------------------------- | -------- | ------------------------------------------------------------------------------------------ |
| `.withSelfImprovement()` | disabled | Cross-task strategy outcome learning |
| `.withReactiveIntelligence(false)` | on | Pass `false` to disable entropy/controller/telemetry stack |
| `.withReactiveIntelligence(options?)` | defaults | Entropy, controller, hooks — 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 }` — one or more SKILL.md directories (required; a path-less call throws) |
| `.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 |
| `GROQ_API_KEY` | Groq provider | — | Groq API key |
| `XAI_API_KEY` | xAI provider | — | xAI 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.12, 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.12
[Section titled “What’s stable in v0.12”](#whats-stable-in-v012)
The following surfaces are tier-1 stable. We will not break these without a major bump.
* **Entry points** — `createAgent(config)` (declarative front door) and `ReactiveAgents.create()` + the `.with*()` chain syntax. Both are the same API in two syntaxes, generated from and validated against `AgentConfigSchema` (the single source of truth); anything expressible in one is expressible in the other.
* **Provider selection** — `.withProvider("anthropic" | "openai" | "google" | "groq" | "xai" | "ollama" | "litellm" | "local")`, `.withModel(model)` (string) / `.withModel({ provider, model, numCtx? })` (object), and the `LLMProvider` interface
* **Reasoning core** — `.withReasoning()` with the documented `ReasoningOptions` shape; the five canonical strategies — `reactive` (ReAct), `reflexion`, `plan-execute-reflect`, `tree-of-thought`, and `adaptive` (auto-routes among them). `direct` (single-shot) is the no-reasoning fallback.
* **Tool surface** — `.withTools()`, `defineTool()` / `tool()`, MCP attachment via `.withMCP()`, and the `Tool` interface
* **Typed structured output** *(new in 0.12)* — `.withOutputSchema(schema, options?)` and the result fields `result.object` / `result.objectError`; `agent.streamObject(task)` yielding `{ object: DeepPartial }`. Standard Schema (Zod / Valibot / ArkType) and Effect Schema are all accepted.
* **Durable execution** *(new in 0.12)* — `.withDurableRuns()` plus `agent.resumeRun(runId)` and `agent.listRuns({ status? })`
* **Harness composition** *(new in 0.12)* — `HarnessProfile.lean() | balanced() | intelligent()` applied via `.withProfile(...)`. Supersedes `.withLeanHarness()`, which remains functional.
* **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(hook)` accepting a `LifecycleHook` (a plain sync/async function or the Effect form) 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** — `@reactive-agents/replay` package: `loadRecordedRun`, `replay`, `makeReplayController`, `makeReplayToolLayer`, `diffTraces`, `computeArgsHash`. The `ToolCallCompleted` event payload’s `args`, `result`, `error`, `resultTruncated` fields are also stable.
* **AgentResult shape** — `.run()` and `.runStream()` return values
* **Raw provider clients** — `AnthropicProviderLive`, `OpenAIProviderLive`, `LocalProviderLive`, `GeminiProviderLive`, `GroqProviderLive`, `XAIProviderLive`, `LiteLLMProviderLive` exported as standalone Effect Layers (you can skip the harness entirely)
## What’s `@unstable` in v0.12
[Section titled “What’s @unstable in v0.12”](#whats-unstable-in-v012)
These work, but the **shape may change** in a later minor. 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 (no builder for custom stages).
* **Task contracts** — `.withContract(taskContract)` (required/forbidden tools, fixtures, model floor, success oracle) is wired and enforced at `build()`, but the `TaskContract` shape is still growing.
* **Budget killswitch** — `.withBudget({ tokenLimit?, costLimit? })` enforces a cumulative ceiling in-loop; the limits shape may gain fields.
* **Cross-run learning** — `.withLearning({ tier?, dbPath? })` and `.withSkillPersistence(enabled?)` persist experience/skills across runs; the store schema is still settling.
* **Evidence grounding** — `.withGrounding({ mode })` (default off) and the `provenance` / `confidence` / `abstained` result fields.
* **Context curator internals** — `.withContextProfile(...)` is stable; the curator’s compression strategy is not user-replaceable yet.
* **Arbitrator** — `.withCustomTermination(predicate)` is stable for boolean overrides; a full `withArbitrator(impl)` for replacing the termination pipeline is not shipped yet.
* **Verifier strategy** — `.withVerification(options)` accepts options today; a replaceable verifier impl is not shipped yet.
* **Cost router policy** — `.withCostTracking()` records spend (stable); the complexity-routing primitives in `@reactive-agents/cost` (`analyzeComplexity`, `routeToModel`) are exported but the policy is not yet a builder method.
* **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.12
[Section titled “What’s @experimental in v0.12”](#whats-experimental-in-v012)
Use at your own risk. Will change.
* **`code-action` strategy** — the LLM emits a TypeScript IIFE run in a Worker sandbox; sandbox contract and tool-binding shape may change
* **A2A protocol surface** (`packages/a2a`) — wire format and JSON-RPC method names may change as the spec evolves
* **Sub-agent delegation API** — the delegation surface (`.withAgentTool()`, `.withRemoteAgent()`, `.withDynamicSubAgents()`) is functional but its shape is still under iteration
* **Reactive observer / entropy scoring tunables** — thresholds, scoring functions
* **Living Skills runtime in Cortex** — UI and persistence schema not finalized
## 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 naming the replacement
3. Removal happens **no sooner than** one full minor cycle later (e.g., deprecated in 0.12 → removed earliest in 0.13)
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.11.2"`). Consume only `@stable` APIs. |
| Active development, monthly upgrades | Pin minor (`"~0.11.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.
# Telemetry
> Exactly what anonymous data Reactive Intelligence telemetry collects, what it never collects, and every way to turn it off.
When Reactive Intelligence is enabled, the framework sends an **anonymous run report** after each run to help improve model calibration profiles. A dismissible notice is shown the first time this happens in a process, linking here. This page is the complete, honest inventory.
## What is collected
[Section titled “What is collected”](#what-is-collected)
Run-shape metrics only — the report is built in `packages/runtime/src/engine/finalize/telemetry-emit.ts` and its exact type is `RunReport` in `@reactive-agents/reactive-intelligence`:
* A random per-install ID (UUID — no account, no hardware fingerprint)
* Model ID, tier, and provider name (e.g. `qwen3:4b` / `local` / `ollama`)
* Task **category label** (a classifier output like `coding` — see below)
* Tool **names** used and call counts
* Strategy used, termination reason, outcome, iteration/token/duration totals
* Entropy-trace metrics (numeric signals about run stability)
## What is never collected
[Section titled “What is never collected”](#what-is-never-collected)
* **No prompt or task text** — only the classified category label leaves the machine
* **No model outputs, tool arguments, or tool results**
* **No file contents, paths, or environment variables**
* **No API keys**
Reports are signed and sent fire-and-forget to `api.reactiveagents.dev` (override with `REACTIVE_AGENTS_TELEMETRY_REPORTS_URL`); a network failure never affects the run. Runs on the `test` provider never send anything.
## Turning it off
[Section titled “Turning it off”](#turning-it-off)
Any one of these disables telemetry entirely:
```bash
# Environment (no code change) — either variable works
export DO_NOT_TRACK=1 # console DNT convention
export REACTIVE_AGENTS_TELEMETRY=0
```
```typescript
// Per agent, in code
ReactiveAgents.create()
.withReactiveIntelligence({ telemetry: false })
.build();
```
Disabling telemetry does not disable Reactive Intelligence itself — the entropy sensor and controller keep working locally; only the anonymous reporting stops. When the environment opt-out is set, the first-run notice is suppressed too (the framework never claims to send what it doesn’t).