Skip to content

ReactiveAgentBuilder

Last updated 1 day ago · 6379e94

Updated yesterday

"docs: sync all documentation to v0.14.0" · 6379e94 · 2026-07-21

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

CategoryMethods
ReactiveAgents factorycreate, quick, fromConfig, fromJSON
Core IdentitywithName, withAgentId, withPersona, withSystemPrompt, withEnvironment
Model & ProviderwithModel, withProvider
Reasoning & ContextwithReasoning, withMemory, withContextProfile, withMaxIterations, withMinIterations
Tools & MCPwithTools, withRequiredTools, withMCP, withMetaTools, withSkills, withAgentTool, withDynamicSubAgents, withRemoteAgent
Observability & TelemetrywithObservability, withCortex, withStreaming, withLogging, withEvents
Safety & ResiliencewithGuardrails, withKillSwitch, withBehavioralContracts, withVerification, withGrounding, withReceiptSigning, withCircuitBreaker, withRateLimiting
Cost & PerformancewithCostTracking, withModelRouting, withBudget, withModelPricing, withDynamicPricing, withRetryPolicy, withTimeout, withLlmTimeout
Lifecycle & HookswithHook, withHealthCheck, withErrorHandler, withFallbacks, withAudit
AdvancedwithA2A, withGateway, withReactiveIntelligence, withPrompts, withUserInteraction, withLazyValidation, withDocuments, withTaskContext, withLayers
Building & Runningbuild, buildEffect, runOnce
Agent Methodsrun, runStream, chat, session, health, cancel, pause, resume, dispose
Result ReferenceAgentResult, AgentDebrief, stream event types

Every builder method and the AgentConfig key(s) it sets. config methods are expressible declaratively via createAgent(config); overlay methods are code-only (functions/secrets/registries) with the reason recorded. This table is generated from the single source (AgentConfigSchema + the builder prototype) — see the Configuration reference for the declarative field list.

MethodConfig key(s)KindDescription
withA2Aoverlay — multi-agent transport topology primitive (not data)overlayAgent-to-Agent server.
withAdaptiveHarnessadaptiveHarnessconfigAdaptive harness / policy compiler.
withAgentIdagentIdconfigStable agent identifier.
withAgentTooloverlay — code-only sub-agent registryoverlayRegister a sub-agent as a tool.
withApprovalPolicyoverlay — carries an approval predicate (HITL durability rail)overlayHuman-in-the-loop tool approval gate.
withAuditfeatures.auditconfigPer-tool-call rationale auditing.
withBehavioralContractsoverlay — behavioral-contract overlay (folds into withContract)overlayBehavioral contracts.
withBudgetbudgetconfigDeclarative token/cost budget caps.
withCalibrationoverlay — runtime-probed calibration (not static data)overlayModel calibration mode.
withChannelsoverlay — messaging transport wiring (not data)overlayMessaging channels.
withCircuitBreakercircuitBreakerconfigCircuit-breaker thresholds (false disables).
withContextProfileoverlay — cross-field side-effect profile (not orthogonal data)overlayContext window profile.
withContractoverlay — behavioral-contract overlay (not JSON data)overlayBehavioral contract.
withCortexoverlay — Cortex desk integration — observability alias (see withObservability({cortex}))overlayEmit events to a Cortex desk.
withCostTrackingcostTracking, features.costTrackingconfigCost budget caps.
withCustomTerminationoverlay — carries a termination predicate (folds into withReasoning)overlayCustom termination predicate.
withDocumentsoverlay — ingestion side-effect (folds into withTools({documents}))overlayRAG document ingestion.
withDurableRunsdurableRunsconfigCrash-resume durable execution.
withDynamicPricingoverlay — pricing overlay (folds into withCostTracking)overlayDynamic pricing overlay.
withDynamicSubAgentsoverlay — code-only dynamic sub-agent registryoverlayDynamic sub-agent spawning.
withEnvironmentoverlay — carries secrets/env (never serialized)overlayEnvironment secrets.
withErrorHandleroverlay — carries an error-handler function (not JSON)overlayCustom error handler.
withEventsoverlay — carries an event stream/callback (folds into withObservability)overlayEvent stream sink.
withExperienceLearningmemory.experienceLearningconfigLearn from prior-run experience summaries.
withFabricationGuardfabricationGuardconfigFabrication-guard mode (off/warn/block).
withFallbacksfallbacksconfigProvider/model fallbacks.
withGatewaygatewayconfigGateway (cron/webhook/access-control) config.
withGroundinggroundingconfigOpt-in numeric evidence grounding.
withGuardrailsguardrails, features.guardrailsconfigInjection/PII/toxicity guardrails.
withHarnessoverlay — compose-power-tier harness injection (not data)overlayInject a composed harness.
withHealthCheckfeatures.healthCheckconfigEnable agent.health() probes.
withHookoverlay — carries a lifecycle callback function (not JSON)overlayLifecycle hook.
withKillSwitchfeatures.killSwitchconfigEmergency stop / terminate control.
withLayersoverlay — Effect Layer DI escape hatch (not data)overlayProvide custom Effect layers.
withLazyValidationoverlay — no schema field (folds into withVerification timing)overlayLazy output validation.
withLeanHarnessoverlay — cross-field profile patch — use withProfile(lean())overlayLean-harness mode.
withLearningmemory, skillPersistenceconfigCompounding-intelligence bundle (memory + skill persistence).
withLlmTimeout_overlay — sets ollamaTimeoutMs; no schema field (G3, folds into withBudget)overlayLLM request timeout (ms).
withLoggingloggingconfigStructured logging config.
withLongHorizonhorizonProfileconfigLong-horizon guard profile.
withMCPmcpServersconfigConnect MCP servers.
withMaxIterationsexecution.maxIterationsconfigIteration cap.
withMemorymemory, features.memoryconfigEnable memory layers + tier/dbPath/capacity/experienceLearning/consolidation.
withMemoryConsolidationmemory.memoryConsolidationconfigBackground memory consolidation/decay/prune.
withMetaToolsoverlay — code-only meta-tool registryoverlayConductor’s-suite meta-tools.
withMinIterationsexecution.minIterationsconfigMinimum iterations before termination.
withModelmodel, thinking, temperature, maxTokens, numCtxconfigModel id + params (thinking/temperature/maxTokens/numCtx).
withModelPricingpricingRegistryconfigCustom model pricing registry.
withModelRoutingoverlay — cost-aware routing capability with no config representation (G4)overlayCost-aware model routing.
withNamenameconfigAgent name.
withObservabilityobservability, features.observabilityconfigObservability umbrella (verbosity/live/cortex/tracing/logging/costs/…).
withOutputSchemaoutputSchemaOptionsconfigTyped structured output (schema object is code-only; options serialize).
withOutputValidatoroverlay — carries a validator function (folds into withVerification)overlayCustom output validator.
withPersonapersonaconfigRole/tone/instructions persona.
withProfileprofileconfigPreset baseline capability profile.
withPromptsfeatures.promptsconfigRegister custom prompt templates.
withProviderproviderconfigLLM provider.
withRateLimitingrateLimitingconfigOutbound LLM rate limiting.
withReactiveIntelligencereactiveIntelligence, features.reactiveIntelligenceconfigReactive intelligence posture.
withReasoningreasoning, features.reasoningconfigReasoning strategy + options.
withReceiptSigningoverlay — carries a private signing key (secret, never serialized)overlayEd25519 receipt signing.
withRemoteAgentoverlay — code-only remote-agent registryoverlayRegister a remote agent.
withReplayLLMoverlay — deterministic replay test rig (not data)overlayReplay recorded LLM responses.
withRequiredToolsrequiredToolsconfigTools that must be called before success.
withRetryPolicyexecution.retryPolicyconfigLLM retry policy (maxRetries/backoff).
withSelfImprovementfeatures.selfImprovementconfigEnable self-improvement loop.
withSkillPersistenceskillPersistenceconfigPersist evolved skills across runs.
withSkillsoverlay — code-only SKILL.md directory registryoverlayLiving SKILL.md directories.
withStallPolicystallPolicyconfigStall/no-progress escalation policy.
withStreamingfeatures.streamingconfigEnable event streaming.
withStrictValidationexecution.strictValidationconfigStrict output validation.
withSystemPromptsystemPromptconfigSystem prompt.
withTaskContexttaskContextconfigBackground key/value facts for reasoning.
withTestScenariooverlay — test-scenario rig (not data)overlayLoad a test scenario.
withThinkingthinkingconfigExtended thinking / reasoning effort.
withTimeoutexecution.timeoutMsconfigRun timeout (ms).
withToolstools, features.toolsconfigTools layer + allowed/focused/adaptive/terminal/required options.
withTracingoverlay — trace persistence — observability alias (see withObservability({tracing}))overlayJSONL trace persistence.
withUserInteractionoverlay — durable ask overlay (channel adapters are code-only)overlayDurable user interaction.
withVerificationverification, features.verificationconfigVerification package (entropy/nli/thresholds/useLLMTier/onReject).
withVerificationStepoverlay — carries a verification-step function (folds into withVerification)overlaySingle post-answer reflect pass.
APIDescription
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.
import { ReactiveAgents } from 'reactive-agents'
// or: import { ReactiveAgents } from "@reactive-agents/runtime";
const builder = ReactiveAgents.create()

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

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:

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.

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.

All chain methods return this unless noted.

MethodSignatureDescription
withName(name: string) => thisDisplay name / agentId basis
withAgentId(id: string) => thisPin a stable agentId instead of the generated ${name}-${Date.now()}. All memory and run data keyed on agentId accumulates across builds that share the ID (e.g. a UUID or Cortex session ID)
withPersona(persona: AgentPersona) => thisStructured steering: { name?, role?, background?, instructions?, tone? }
withSystemPrompt(prompt: string) => thisCustom system prompt; if persona is set, persona text is prepended
withEnvironment(context: Record<string, string>) => thisExtra key/value context merged into the system prompt (framework already injects date/time/tz/platform)
MethodSignatureDescription
withModel(model: string) => thisSet the LLM model by name (e.g., "claude-sonnet-4-6")
withModel(params: ModelParams) => thisSet model with advanced parameters: thinking, temperature, maxTokens, numCtx
withThinking(options?: boolean | ThinkingOptions) => thisEnable 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") => thisSet the LLM provider
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
}
// 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 field, so it round-trips through toConfig() / fromJSON() and the config-driven path.

.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 enabledundefined never auto-enables.

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)
}
// Boolean form — enable / disable
.withThinking() // enable
.withThinking(false) // disable
// Rich form — effort + budget
.withThinking({ effort: "high", budgetTokens: 4096 })
MethodSignatureDescription
withMemory(options?: MemoryOptions | "1" | "2") => thisEnable 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().
FieldTypeDefault / notes
tier"standard" | "enhanced""standard" — enhanced = 4-layer memory + embeddings
dbPathstringSQLite path (default under .reactive-agents/memory/{agentId}/)
maxEntriesnumberCompaction cap
capacitynumberWorking memory slots (default 7)
evictionPolicy"fifo" | "lru" | "importance"Working set eviction
retainDaysnumberEpisodic retention
importanceThresholdnumberSemantic inclusion threshold
MethodSignatureDescription
withMaxIterations(n: number) => thisMax agent loop iterations (default: 10)
withMinIterations(n: number) => thisMinimum iterations before final-answer is permitted — prevents fast-path exit on complex tasks
withContextProfile(profile: Partial<ContextProfile>) => thisModel-adaptive context overrides: tool result size/preview limits, tool schema verbosity, iterations, temperature, context-window tokens
withStrictValidation() => thisThrow at build time if required config is missing (provider, model, etc.)
withLazyValidation() => thisKeep the missing-API-key and unknown-for-provider-model checks as warnings even under withStrictValidationbuild() succeeds and the clean typed failure surfaces at run() time. Useful when keys are injected after construction, or in tooling that eagerly constructs many configs. Keyless providers (ollama, test) are exempt from the key gate anyway. Env equivalent: REACTIVE_AGENTS_LAZY_VALIDATION=1
withTimeout(ms: number) => thisExecution timeout in milliseconds for the whole agent run (all iterations combined). Throws TimeoutError if exceeded
withLlmTimeout(ms: number) => thisPer-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) => thisRetry on transient LLM failures. { maxRetries: number, backoffMs: number }
FieldTypeDescription
tier"local" | "mid" | "large" | "frontier"Model tier — controls which defaults are applied
toolResultMaxCharsnumberMax characters per compressed tool result before overflow compression
toolResultPreviewItemsnumberArray items shown in a compressed tool result preview
toolSchemaDetail"names-only" | "names-and-types" | "full"Tool schema verbosity in the system prompt
maxIterationsnumber (optional)Max kernel iterations before failing
temperaturenumber (optional)LLM sampling temperature
maxTokensnumber (optional)Context-window token cap used by pressure gates and message compaction
recentObservationsLimitnumber (optional)When > 0, append the last N tool observations to the system prompt (default: 0)
// Lean context for local small models
.withContextProfile({ tier: "local" })
// Manual overrides for a specific task
.withContextProfile({
maxTokens: 4000,
toolResultMaxChars: 800,
toolResultPreviewItems: 3,
toolSchemaDetail: "names-and-types",
})

See Context Engineering for full tier defaults.

MethodDescription
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 and enforced at the shared tool-execution gate on every strategy (plan-execute/blueprint planned steps and the code-action sandbox included) — a violating call is blocked and recorded, never executed. Validated at build(). The declared contract is now load-bearing: it is compiled into the run’s typed goal, the terminal gate checks requirement satisfaction against the evidence ledger, and result.receipt.deliverables[] reports each declared output as produced or missing.
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? }.
withReceiptSigning(options)Opt in to an Ed25519 provenance signature on every trust receipt (off by default — receipts are unsigned). { privateKeyJwk }; also settable via the RA_RECEIPT_KEY env var (this option wins when both are present). The signature certifies this receipt, this run, untampered — it never certifies the answer’s correctness. Generate a keypair with generateReceiptKeyPair() and verify with verifyReceipt(result.receipt!). See The Process Model.
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? }.
withLongHorizon()Opt-in, off by default. Mode toggle (no arguments). Scales the reasoning kernel’s guard thresholds (stall, consecutive-thoughts, redirect/nudge budgets) proportionally to maxIterations instead of using absolute counts, so a run configured for 40+ iterations of tool work isn’t tripped by guards tuned for short runs. Verified to let a long-horizon task run to completion; not yet lift-gated for default-on. When not called, horizonProfile stays unset and behavior is byte-identical to the default.
withAdaptiveHarness()Opt-in and experimental. Mode toggle (no arguments). A policy compiler derives the run’s harness (strategy, budget class, guard/horizon profile, tool surface, verifier tier, memory posture) at run-start from the model’s capability tier + calibration, the compiled contract’s horizon, and the task classification; the plan supplies DEFAULTS while any explicit .withX() you set OVERRIDES the corresponding field. Mid-run it recompiles on live progress evidence — deepening scaffolding when the run struggles, leaning when it flows. Under active validation: the cross-tier ablation was inconclusive (n=1 dev-hardware noise), so it is not default-on and sits under the project lift-gate veto. Zero cost when not called.
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
withTools(options?)Tool layer — see ToolsOptions below
withDocuments(docs)Chunk + index DocumentSpec[] for RAG; retrieval is served through the unified find meta-tool. 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.
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. Also the single entry point for run telemetry (telemetry: true | TelemetryConfig — privacy modes, default isolated) and trace-file control (tracing: false disables; tracing: { dir } sets the directory).
withCortex(url?)Enable best-effort Cortex reporting. Streams all EventBus events to the Cortex local studio 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 for the full feature reference.
withStreaming(options?)Default density for agent.runStream(): { density?: "tokens" | "full" }
withPrompts(options?){ templates?: PromptTemplate[] }
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 SkillRecords 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 } — an ordered provider cascade. The primary provider runs first; on any error the next provider in the list is tried, in order (no error threshold, no 429/cost-specific logic).
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 runs one LLM review; on a REVISE verdict it re-runs the answer once with the verification feedback so the verdict shapes the final answer. 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
withReactiveIntelligence(false)Disable the Reactive Intelligence layer (enabled by default).
withReactiveIntelligence(options?)Entropy, controller, telemetry, hooks (onEntropyScored, onControllerDecision, …). See Reactive Intelligence
withSkills(config){ paths } — one or more SKILL.md directories (required). A path-less call, or the removed packages / evolution / overrides keys, now throws.
withMetaTools(config?)Conductor meta-tools; pass false to turn off defaults when using .withTools(). See MetaToolsConfig
withHarness(fn)Alias for .compose(fn) — attach a harness transform over tagged chokepoints. See 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" | "skip" | …). When unset, calibration auto-enables if reasoning is on; passing "off"/"skip" is an explicit opt-out that is now honored even when reasoning is enabled. See LLM Providers.
withTracing(opts?)Write structured trace files ({ dir? }) for rax diagnose. Governed separately from the metrics dashboard; also toggled by REACTIVE_AGENTS_TRACE. Disable with .withObservability({ tracing: false }).
withChannels(config)Wire @reactive-agents/channels sender-policy access control (ChannelsConfig). See 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.
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.
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.
withUserInteraction()Enable agent-initiated user interaction (Agentic UI). The model may call request_user_input to pause the run durably and ask the human for a form / choice / confirmation; agent.respondToInteraction(...) resumes it. Requires .withDurableRuns() (interaction pauses persist to the durable store). Distinct from withApprovalPolicy(), which gates tool calls the model already chose (human approves/denies) — withUserInteraction() lets the agent proactively request input. Zero cost when not called.
FieldDescription
tools{ definition: ToolDefinition, handler: (args) => Effect.Effect<unknown> }[] — custom tools (handlers return Effect)
resultCompressionResultCompressionConfig — previews, overflow keys, transforms
allowedToolsIf set, only these tool names are exposed to the model (others filtered) — and the allowlist is enforced at execution on every strategy: a call outside it is blocked, never run
adaptiveAdaptive tool listing from task text (heuristic), reduces noise for small models
terminaltrue | ShellExecuteConfig — opt in to the sandboxed shell-execute tool (command allowlist, blocklist, locked cwd)
FieldDescription
brief, find, pulse, recallEnable each Conductor meta-tool
harnessSkillboolean, path string, or { frontier?, local? } for harness skill source
findConfig, pulseConfig, recallConfigFine-tuning (scopes, previews, LLM pulse behavior, …)
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:

// 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 for full details on loop detection triggers, handoff context, and EventBus subscription examples.

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:

// 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.

MethodSignatureDescription
withA2A(options?: A2AOptions) => thisA2A 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 }) => thisStatic sub-agent as a tool
withDynamicSubAgents(options?: { maxIterations?: number }) => thisspawn-agent for runtime sub-agents
withRemoteAgent(name: string, remoteUrl: string) => thisRemote A2A agent as a tool
MethodSignatureDescription
withMCP(config: MCPServerConfig | MCPServerConfig[]) => thisConnect to MCP servers. Accepts a single config or array. Automatically enables .withTools().
FieldTypeTransportDescription
namestringallUnique name for this server. Tool names are prefixed {name}/
transport"stdio" | "streamable-http" | "sse" | "websocket"allProtocol to use. Use "streamable-http" for modern remote servers, "stdio" for local subprocesses
commandstringstdioExecutable to launch ("bunx", "docker", "python", absolute path, etc.)
argsstring[]stdioArguments passed to command. Includes package names, flags, Docker image, etc.
envRecord<string, string>stdioExtra env vars merged on top of the parent process environment. Use for per-server secrets
cwdstringstdioWorking directory for the subprocess. Defaults to parent process cwd
endpointstringstreamable-http, sse, websocketHTTP/WebSocket URL ("https://mcp.example.com", "ws://localhost:8000/mcp")
headersRecord<string, string>streamable-http, sseHTTP headers sent on every request. Use for Authorization, x-api-key, etc.

Examples:

// 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 ?? "" } }
MethodSignatureDescription
withHook(hook: LifecycleHook) => thisRegister a lifecycle hook

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.

MethodSignatureDescription
withTestScenario(turns: TestTurn[]) => thisDeterministic 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 and Configuration for examples.

MethodSignatureDescription
withLayers(layers: Layer<any, any>) => thisAdd custom Effect Layers to the runtime
async build(): Promise<ReactiveAgent>

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

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

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

runOnce(input: string): Promise<AgentResult>

Section titled “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.

const result = await ReactiveAgents.create()
.withProvider('anthropic')
.withReasoning()
.runOnce('Summarize the README in one paragraph')
console.log(result.output)
// Resources are already cleaned up

The facade returned by build().

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:

Uses the Explicit Resource Management protocol introduced in TypeScript 5.2. The agent is disposed automatically when the enclosing block exits, whether normally or via an exception.

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.

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.

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

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

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()
}
PatternWhen to use
await usingGeneral 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>

Section titled “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.

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

Section titled “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>

Section titled “runEffect(input: string): Effect.Effect<AgentResult, Error>”

Run a task as an Effect for composition (see Effect-TS primer).

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

Section titled “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.

resumeRun(runId: string): Promise<AgentResult>

Section titled “resumeRun(runId: string): Promise<AgentResult>”

Resume a crashed or paused durable run from its last checkpoint. Requires .withDurableRuns(). See Durable Execution.

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

Section titled “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[]>

Section titled “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.

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

Section titled “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>

Section titled “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.

MethodDescription
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>

Section titled “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.

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"]
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)
}

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. Persistence requires the memory layer (.withMemory()): the session store is wired only when memory is enabled — without it, persist: true silently no-ops and the session stays in-memory only.

// 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() // Flushes history to storage (if persisted) and clears the in-memory copy — the DB record is kept
// session() options
{
persist?: boolean // Persist history to SQLite via SessionStoreService (requires .withMemory())
id?: string // Session ID for persistence (auto-generated if omitted)
}
interface AgentSession {
chat(message: string): Promise<ChatReply>
history(): ChatMessage[]
end(): Promise<void>
}

Requires .withHealthCheck() to be enabled.

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

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}`)
}
interface HealthResult {
status: 'healthy' | 'degraded' | 'unhealthy'
checks: Array<{
name: string
status: 'pass' | 'warn' | 'fail'
message?: string
durationMs?: number
}>
}

Cancel a running task by its ID.

getContext(taskId: string): Promise<unknown>

Section titled “getContext(taskId: string): Promise<unknown>”

Get the execution context of a running or completed task.

Requires .withKillSwitch() to be enabled.

MethodSignatureDescription
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)

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:

// ── 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:

// 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:

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:

TagPayload fields
AgentStartedtaskId, agentId, provider, model, timestamp
AgentCompletedtaskId, agentId, success, totalIterations, totalTokens, durationMs
LLMRequestStartedtaskId, requestId, model, provider, contextSize
LLMRequestCompletedtaskId, requestId, tokensUsed, durationMs
ReasoningStepCompletedtaskId, strategy, step, thought|action|observation
ToolCallStartedtaskId, toolName, callId
ToolCallCompletedtaskId, toolName, callId, success, durationMs
FinalAnswerProducedtaskId, strategy, answer, iteration, totalTokens
GuardrailViolationDetectedtaskId, violations, score, blocked
ExecutionPhaseEnteredtaskId, phase
ExecutionPhaseCompletedtaskId, phase, durationMs
ExecutionHookFiredtaskId, phase, timing
ExecutionCancelledtaskId
MemoryBootstrappedagentId, tier
MemoryFlushedagentId
AgentPausedagentId, taskId
AgentResumedagentId, taskId
AgentStoppedagentId, taskId, reason
TaskCompletedtaskId, success
GatewayStartedagentId, timestamp
GatewayStoppedagentId, reason
GatewayEventReceivedagentId, eventId, source, category
ProactiveActionInitiatedagentId, eventId, action
ProactiveActionCompletedagentId, eventId, success, durationMs
ProactiveActionSuppressedagentId, eventId, reason
PolicyDecisionMadeagentId, eventId, action, policyTag
HeartbeatSkippedagentId, consecutiveSkips, reason
EventsMergedagentId, mergedCount, mergeKey
BudgetExhaustedagentId, tokensUsed, dailyBudget
StrategySwitchEvaluatedtaskId, fromStrategy, recommendedStrategy, rationale, willSwitch
StrategySwitchedtaskId, fromStrategy, toStrategy, switchNumber, stepsCarriedOver
ProviderFallbackActivatedtaskId, fromProvider, toProvider, reason, attemptNumber
DebriefCompletedtaskId, agentId, debrief
ChatTurntaskId, sessionId, role, content, routedVia, tokensUsed?
MemorySnapshottaskId, iteration, working, episodicCount, semanticCount, skillsActive
ContextPressuretaskId, utilizationPct, tokensUsed, tokensAvailable, level
AgentHealthReportagentId, status, checks[], uptimeMs
AgentConnectedagentId, runId, cortexUrl
AgentDisconnectedagentId, runId, reason
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
// Trust receipt — graded evidence about HOW the answer was produced (see The Process Model).
// `receipt.deliverables[]` names each declared deliverable as produced or missing when the run's
// compiled contract declared at least one concrete output (absent for pure Q&A runs).
receipt?: TrustReceipt
}

receipt.deliverables is { spec: string; produced: boolean }[] — a partial multi-file run lists exactly which outputs never landed (produced: false) instead of claiming success. The full receipt shape, verdicts, and optional Ed25519 signing are documented in The Process Model.

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:

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.

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

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:

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)
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()
.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