Skip to content
Playground

Production Deployment Checklist

Last updated 9 days ago · 1db78aa

↑ Updated 9 days ago

"docs(guides): dual-syntax tabbed code examples across all guide pages" · 1db78aa · 2026-10-02

  • ### Guardrails
  • ### Behavioral Contracts
  • ### Execution Timeout
  • ### Retry Policy
  • ### Complexity Routing
  • ### Memory Consolidation

This checklist covers the builder methods and configuration options you should evaluate before deploying a Reactive Agents application to production. Each section is independent — enable the layers that match your threat model and reliability requirements.

Guardrails screen every prompt and response for prompt injection, PII leakage, and toxic content. Enable with guardrails: {} (declarative) or .withGuardrails() (builder). Each detector can be toggled independently; pass a customBlocklist to reject additional phrases.

import { createAgent } from "reactive-agents";
const agent = await createAgent({
name: "prod-agent",
provider: "anthropic",
guardrails: {
injection: true,
pii: true,
toxicity: true,
},
});

A GuardrailViolationDetected event is emitted on the EventBus whenever a check fires, so violations surface in your observability pipeline automatically.

Behavioral contracts constrain what the agent is allowed to do at runtime. Use a tool deny list to block dangerous tools, cap iterations, and restrict output patterns. This one is builder-only — withBehavioralContracts has no declarative AgentConfig key:

const agent = await ReactiveAgents.create()
.withProvider("anthropic")
.withBehavioralContracts({
deniedTools: ["shell-execute"],
maxIterations: 20,
maxToolCalls: 40,
})
.build();

Gate high-risk tool calls behind human approval. Name the tools in .withApprovalPolicy() (with .withDurableRuns()): a gated call pauses the run, persists awaiting-approval, and run() returns status: "awaiting-approval" + pendingApproval. A human approves or denies — from this process or any other — and the run resumes from its checkpoint.

const agent = await ReactiveAgents.create()
.withProvider("anthropic")
.withTools({ tools: [databaseWriteTool] })
.withDurableRuns()
.withApprovalPolicy({ tools: ["database-write"], mode: "detach" })
.build();
const result = await agent.run("purge stale records");
if (result.status === "awaiting-approval") {
await agent.approveRun(result.pendingApproval.runId); // or denyRun(runId, reason)
}
// Or, in one call: agent.run(task, { onApproval: ({ toolName }) => confirm(toolName) })

See Durable Human-in-the-Loop for the full flow.

The per-tool requiresApproval: true flag is metadata only — it does not by itself pause a run. Use .withApprovalPolicy({ tools: [...] }) to gate.

Restrict the agent to a fixed set of tools. Any tool not in the list is invisible to the LLM and cannot be called, regardless of what the model requests.

import { createAgent } from "reactive-agents";
const agent = await createAgent({
name: "prod-agent",
provider: "anthropic",
tools: { allowedTools: ["web-search", "file-read"] },
});

The kill switch enables programmatic lifecycle control. Call agent.stop() for a graceful exit that completes the current step, or agent.terminate() for an immediate halt.

import { createAgent } from "reactive-agents";
const agent = await createAgent({
name: "prod-agent",
provider: "anthropic",
features: { killSwitch: true },
});

Then wire graceful stop from a signal handler or timeout:

process.on("SIGTERM", () => agent.stop());

The default iteration cap is 10. Increase it for complex multi-step tasks, or lower it for latency-sensitive paths.

import { createAgent } from "reactive-agents";
const agent = await createAgent({
name: "prod-agent",
provider: "anthropic",
execution: { maxIterations: 20 },
});

Set a wall-clock timeout in milliseconds. The agent throws a TimeoutError if the run does not complete within the limit.

import { createAgent } from "reactive-agents";
const agent = await createAgent({
name: "prod-agent",
provider: "anthropic",
execution: { timeoutMs: 60_000 }, // 60 seconds
});

Configure automatic retries on transient failures (network errors, rate limits). Exponential backoff is applied between attempts.

import { createAgent } from "reactive-agents";
const agent = await createAgent({
name: "prod-agent",
provider: "anthropic",
execution: { retryPolicy: { maxRetries: 3, backoffMs: 1000 } },
});

Set per-request and daily token budgets. The agent performs a pre-flight budget check before each run and a per-iteration check during the ReAct loop. A BudgetExceededError is thrown on overspend.

import { createAgent } from "reactive-agents";
const agent = await createAgent({
name: "prod-agent",
provider: "anthropic",
costTracking: {
perRequest: 0.10, // USD
daily: 5.00, // USD
},
});
import { BudgetExceededError } from "@reactive-agents/runtime";
try {
const result = await agent.run("Analyze the Q4 sales report");
} catch (e) {
if (e instanceof BudgetExceededError) {
console.error("Budget exceeded:", e.message);
// escalate, alert, or degrade gracefully
}
}

With complexity routing enabled, simple queries are automatically routed to a cheaper model tier, reserving your primary model for tasks that need it. .withModelRouting() is builder-only — routing has no declarative AgentConfig key:

const agent = await ReactiveAgents.create()
.withProvider("anthropic")
.withModelRouting()
.build();

Enable the metrics dashboard to get a structured execution summary after every run: phase timing, tool call counts, token usage, estimated cost, and smart alerts.

import { createAgent } from "reactive-agents";
const agent = await createAgent({
name: "prod-agent",
provider: "anthropic",
observability: { verbosity: "normal", live: true },
});

The dashboard is driven entirely by the EventBus. No manual instrumentation is required — MetricsCollector auto-subscribes to ToolCallCompleted and phase lifecycle events.

Call agent.exportMetrics() to retrieve metrics programmatically for forwarding to an external monitoring system (Prometheus, Datadog, etc.).

const result = await agent.run("Process batch job");
const metrics = await agent.exportMetrics();
// Forward to your monitoring pipeline
await metricsClient.record({
agentId: "prod-agent",
duration: metrics.totalDurationMs,
tokens: metrics.totalTokens,
cost: metrics.estimatedCostUsd,
steps: metrics.stepCount,
});

Register a handler to capture all agent errors in one place. The handler receives the error and a context object with task metadata for structured logging.

const agent = await ReactiveAgents.create()
.withProvider("anthropic")
.withErrorHandler((error, ctx) => {
logger.error(error.message, {
taskId: ctx.taskId,
agentId: ctx.agentId,
iteration: ctx.iteration,
});
metrics.increment("agent.error", { type: error.constructor.name });
})
.build();

RuntimeErrors is the exhaustive union of all errors the agent can throw. Use it for type-safe catch blocks.

import { RuntimeErrors } from "@reactive-agents/runtime";
try {
const result = await agent.run(prompt);
} catch (e) {
const error = e as RuntimeErrors;
switch (error._tag) {
case "BudgetExceededError":
// degrade gracefully or queue for later
break;
case "GuardrailViolation":
// return a safe fallback response
break;
case "MaxIterationsError":
// return partial result if available
break;
default:
throw e;
}
}

When running Effect-based code directly, use unwrapError() to extract a clean message from an Effect FiberFailure, and errorContext() to retrieve actionable remediation hints.

import { unwrapError, errorContext } from "@reactive-agents/runtime";
try {
const result = await agent.run(prompt);
} catch (raw) {
const error = unwrapError(raw);
const ctx = errorContext(raw);
console.error(error.message);
if (ctx?.suggestion) {
console.info("Suggestion:", ctx.suggestion);
}
}

The "enhanced" memory tier activates semantic search, episodic recall, and procedural memory in addition to working memory. It requires embedding support — set EMBEDDING_PROVIDER and EMBEDDING_MODEL in your environment.

Terminal window
EMBEDDING_PROVIDER=openai
EMBEDDING_MODEL=text-embedding-3-small
import { createAgent } from "reactive-agents";
const agent = await createAgent({
name: "prod-agent",
provider: "anthropic",
memory: { tier: "enhanced" },
});

Background consolidation merges and compacts memory entries over time, preventing unbounded growth and keeping retrieval quality high.

import { createAgent } from "reactive-agents";
const agent = await createAgent({
name: "prod-agent",
provider: "anthropic",
memory: { tier: "enhanced", memoryConsolidation: true },
});

Cross-run experience learning stores task outcomes in the episodic layer and surfaces relevant prior experiences at the start of each new run, improving performance on repeated task types.

import { createAgent } from "reactive-agents";
const agent = await createAgent({
name: "prod-agent",
provider: "anthropic",
memory: { tier: "enhanced", experienceLearning: true },
});

ConcernBuilder MethodDefaultProduction Recommendation
Prompt injection.withGuardrails()offEnable
Cost limits.withCostTracking({ budget })offSet per-request budget
Iteration limit.withMaxIterations(N)1020–50 for complex tasks
Min iterations.withMinIterations(N)none2–3 for research tasks
Output quality.withOutputValidator(fn)noneValidate structure for critical outputs
Answer verification.withVerificationStep()noneEnable for high-stakes decisions — on a REVISE verdict, reflect mode re-runs once with the verification feedback so the verdict shapes the final answer
Timeout.withTimeout(ms)none60_000–300_000
Retry.withRetryPolicy()none{ maxAttempts: 3 }
Observability.withObservability()offEnable with verbosity: "normal"
Error handler.withErrorHandler()noneSet for logging/alerting
Kill switch.withKillSwitch()offEnable for long-running agents