createAgentApp Reference
createAgentApp is the primary entry point for building an Agentrail-powered HTTP server. It returns a fully configured Hono application with /chat, /stream, and optional observability routes already wired up.
Import
import { createAgentApp } from "@agentrail/app";Minimal example
import { createAgentApp, defineProfile } from "@agentrail/app";
const myProfile = defineProfile({
/* ... */
});
const app = createAgentApp({
dataDir: "./data",
profiles: [myProfile],
summarize: async (messages) => callLLMSummarizer(messages),
});
export default app;Mounted routes
| Method | Path | Description |
|---|---|---|
POST | /chat | Non-streaming agent invocation |
POST | /stream | Streaming agent invocation (SSE) |
GET | /health | Liveness probe (unless health.disableBuiltinHealthRoutes is set) |
GET | /ready | Readiness probe (same condition) |
GET | /__inspector/* | Inspector API (only when inspector: true) |
Options reference
dataDir
dataDir?: stringPath to the directory used for persistent session and trace storage. The filesystem layout under this directory is managed by SessionManager and createFileTelemetrySink.
Required when sessionStore is not provided. Required when inspector: true is set.
sessionStore
sessionStore?: AgentrailSessionStoreCustom session store implementation. When provided, dataDir is ignored for session storage. Use this to plug in a database-backed or in-memory store.
See Session Store Reference for the full interface and a worked example.
profiles
profiles?: ProfileDefinition[]Static list of agent profiles available to the app. The first entry is used as the default when a request omits agentId. At least one of profiles or resolveProfile must be provided.
See Build a Profile for how to define profiles.
resolveProfile
resolveProfile?: ProfileResolverA function that dynamically resolves a profile for each request. Useful for tenant-aware routing, feature flags, or A/B testing. When provided, profiles is only consulted to determine defaultAgentId.
const app = createAgentApp({
dataDir: "./data",
resolveProfile: async ({ agentId, tenantId }) => {
return await loadProfileForTenant(agentId, tenantId);
},
defaultAgentId: "default",
});defaultAgentId
defaultAgentId?: stringProfile ID used when a request body omits agentId. Falls back to the first entry in profiles when not set.
summarize
summarize?: (
messages: Message[],
ctx?: { reason: "session_compaction" | "reactive_micro" | "reactive_full" },
) => Promise<string>Summarizer function called by the compaction system when context grows beyond compaction.triggerTokens. Should call an LLM and return a condensed string.
When omitted, a no-op fallback concatenates message content without summarizing — agents may lose context rather than receive a proper recap. Always provide a real summarizer for production use.
compaction
compaction?: {
triggerTokens: number;
minMessages: number;
reactive?: {
enabled?: boolean;
microTriggerPct?: number;
fullTriggerPct?: number;
preserveRecentApiRounds?: number;
microBatchGroups?: number;
maxReactiveCompactionsPerRequest?: number;
};
}Thresholds for automatic context compaction. Defaults to { triggerTokens: 150_000, minMessages: 20 }.
compaction.reactive controls in-loop reactive compaction for long-running turns. microTriggerPct starts local API-round summarization, fullTriggerPct triggers a more aggressive in-memory summary, and maxReactiveCompactionsPerRequest limits retries after prompt-too-long failures.
See Context & Compaction for how compaction works.
plugins
plugins?: AgentrailPlugin[]App-level plugins for request interception and lifecycle hooks. Plugins run in order for each request.
See Write a Plugin and Plugin Contract.
onPluginError
onPluginError?: PluginErrorHandlerCalled when a plugin hook throws. Defaults to console.warn. Plugin errors are isolated — a failing plugin never aborts the request pipeline.
contextProviders
contextProviders?: ContextProvider[]Static context providers prepended to every request regardless of profile. Use this for app-wide context such as current date or system configuration.
sandboxManager
sandboxManager?: SandboxManagerSandbox manager for upload handling and workspace snapshots in the /stream route. When omitted, the route is still mounted but file-upload and workspace-snapshot features are disabled.
traceStoreFactory
traceStoreFactory?: (sessionRef: SessionRef) => SessionTraceStore<WorkflowTraceEventEnvelope>Factory that creates a session-scoped trace store for each request. Trace envelopes (agent loop events, tool calls, compaction events) are written via store.appendEnvelope(envelope).
When omitted and dataDir is set, createAgentApp falls back to the default filesystem-backed createFileSystemSessionTraceStore. When neither is provided, traces are not persisted (but still forwarded to telemetrySink if one is configured).
import { createAgentApp, createFileSystemSessionTraceStore } from "@agentrail/app";
const app = createAgentApp({
sessionStore: mySessionStore,
profiles: [myProfile],
traceStoreFactory: (sessionRef) => createMySessionTraceStore(sessionRef),
});createOrchestrationPersistence
createOrchestrationPersistence?: (sessionRef: SessionRef) => OrchestrationPersistenceFactory that creates an OrchestrationPersistence for each session. When provided (and orchestrationRegistry is not), createAgentApp automatically builds an AgentrailOrchestrationRegistry backed by this factory. This is the recommended shorthand for hosts that only need to swap the storage backend.
import { createAgentApp } from "@agentrail/app";
const app = createAgentApp({
sessionStore: mySessionStore,
profiles: [myProfile],
createOrchestrationPersistence: (sessionRef) => createMyOrchestrationPersistence(sessionRef),
});When omitted, orchestration SSE events are not forwarded unless an explicit orchestrationRegistry is passed.
orchestrationRegistry
orchestrationRegistry?: AgentrailOrchestrationRegistryExplicit registry instance used by the /stream route to subscribe to sub-agent events for real-time SSE forwarding. Pass the same registry instance you used when wiring up orchestration(registry, factory) inside your profile.
Use orchestrationRegistry when you need direct control over the registry (e.g. to call invalidate() or share it with other parts of the server). For simpler setups, use createOrchestrationPersistence instead and let createAgentApp build the registry.
When neither is provided, orchestration SSE events are not forwarded to the client.
inspector
inspector?: true | InspectorDataSourceMounts the read-only Inspector API at /__inspector, consumed by the Agentrail Inspector Docker image.
Three variants:
true— automatically creates a filesystem data source fromdataDir. RequiresdataDirand is incompatible with a customsessionStore. An error is thrown at startup if either constraint is violated.InspectorDataSource— use a custom data source. Works with anysessionStoreconfiguration.
// Filesystem backend (default)
createAgentApp({ dataDir: "./data", profiles: [...], inspector: true });
// Custom backend
createAgentApp({
sessionStore: mySessionStore,
profiles: [...],
inspector: new MyInspectorDataSource(),
});See Inspector Route Reference.
health
health?: {
readinessChecks?: ReadinessCheck[];
disableBuiltinHealthRoutes?: boolean;
}Health route configuration. By default createAgentApp mounts GET /health and GET /ready. Both endpoints are compatible with Kubernetes liveness and readiness probes.
readinessChecks— additional checks run alongside the built-in session store check. Use this to verify API keys or external services at startup:tsconst app = createAgentApp({ dataDir: "./data", profiles: [myProfile], health: { readinessChecks: [ async () => { const ok = await pingExternalService(); return { name: "external-service", status: ok ? "ok" : "fail" }; }, ], }, });disableBuiltinHealthRoutes— set totrueto skip mounting/healthand/readyentirely. Useful when the host application manages health routes itself.
permissionPolicy
permissionPolicy?: ToolPermissionPolicyOptional permission policy applied to all sessions served by this app.
When set, tools evaluate the policy via their checkPermissions hook before executing. The policy is forwarded through AgentrailProfileContext.permissionPolicy → CapabilityBuildContext.permissionPolicy into each capability's tool set.
import { parseRules } from "@agentrail/capabilities";
const app = createAgentApp({
dataDir: "./data",
profiles: [myProfile],
permissionPolicy: {
mode: "default",
// Bash rules use "verb:args" DSL — "git:*" matches any git command
allow: parseRules(["Bash(git:*)", "Bash(npm:*)"]),
deny: parseRules(["Bash(rm:*)"]),
ask: parseRules(["Write", "Edit"]),
},
});The Bash DSL uses a verb:args form: "git:*" matches any command whose first word is git (e.g. git status, git log --oneline, git add src/main.ts). The tool normalises "git status" → "git:status" before pattern matching. The * wildcard in Bash patterns matches across / so path arguments in commands are matched correctly.
Permission modes:
mode | Default outcome | Description |
|---|---|---|
"default" | allow | Opt-in deny/ask. Only rules explicitly listed in deny/ask block tool calls. |
"strict" | deny | Deny-by-default. Only operations listed in allow are permitted; everything else is blocked. Use for minimal-privilege configurations. |
"acceptEdits" | allow | Like default, but ask decisions for Write/Edit tools are auto-approved. |
"dontAsk" | allow | Like default, but ask decisions are demoted to deny (headless environments). |
"bypassPermissions" | allow | All checks skipped (trusted automation only). |
Strict (deny-by-default) allowlist example:
import { parseRules } from "@agentrail/capabilities";
const app = createAgentApp({
permissionPolicy: {
mode: "strict", // deny anything not explicitly allowed
allow: parseRules([
"Bash(git:*)", // prefix-match: any content starting with "git:"
"Bash(npm:*)", // prefix-match: any content starting with "npm:"
"Read", // permit all file reads
]),
deny: [],
ask: [],
},
});Bash rule caveat: Bash patterns are prefix-anchored (no trailing
$).Bash(git:*)matches any normalised command whose first word isgit, but it also matches shell strings that merely start withgit:— including chained forms likegit:status; curl evil.com. Bash rules are useful for coarse-grained allow/deny (e.g. block allrmcalls), but they cannot provide strict command confinement. For strong shell isolation, run agents in the sandboxed environment.
Loading from agentrail.yaml:
When config.permissions is set, use configPermissionsToPolicy to convert the raw YAML config into a runtime ToolPermissionPolicy:
import { createAgentApp, loadAgentrailConfig, configPermissionsToPolicy } from "@agentrail/app";
const config = loadAgentrailConfig();
const app = createAgentApp({
dataDir: "./data",
profiles: [myProfile],
permissionPolicy: config.permissions ? configPermissionsToPolicy(config.permissions) : undefined,
});See the Tool Permissions Guide for the full DSL reference, mode descriptions, and interactive approval wiring.
telemetrySink
telemetrySink?: TelemetrySinkPluggable sink that receives structured TelemetrySinkEvent objects from both the /chat and /stream routes. Use this to forward events to OpenTelemetry, Datadog, or a custom backend.
Built-in sinks shipped with @agentrail/app:
createConsoleTelemetrySink()— pretty-prints events to stdout; for local development only.createFileTelemetrySink(dataDir)— appends events as JSONL trace files underdataDir; used by the Inspector.
Return value
Returns a Hono instance. Mount it in any Node.js HTTP server:
import { serve } from "@hono/node-server";
import { createAgentApp } from "@agentrail/app";
const app = createAgentApp({
/* ... */
});
serve({ fetch: app.fetch, port: 3000 });Or export it for use with Bun, Cloudflare Workers, or any other runtime that accepts a fetch-compatible handler.