Host Primitives Reference
The host primitives expose the lower-level request orchestration layer, available from @agentrail/app/advanced.
When To Read This Page
Read this page when:
createAgentAppis no longer enough- you need route-level or lifecycle-level control
- you want to understand the primitives that
createAgentAppwraps
Use these when your application needs a custom request lifecycle or is integrating into an existing server architecture that cannot adopt createAgentApp directly.
When To Use Host Primitives
Use the primitives directly when:
- your request lifecycle differs from the standard flow
- you need custom profile resolution rules not covered by
resolveProfile - you want to mix your own context pipeline with only part of the standard setup
- you need custom streaming or orchestration behavior
- you are integrating Agentrail into an existing server architecture
If you do not already know that you need one of those, start with createAgentApp from @agentrail/app first.
Import Path
import { createChatRoute, createStreamRoute } from "@agentrail/app/advanced";Main APIs
createChatRoutecreateStreamRoutecreateOrchestrationRegistrycomposeTransformContextscreateTransformContextcreateContextProviderFromTransformrunPluginLifecycle
Route Factories
createChatRoute
Defined in:
This is the non-streaming host entry point.
It handles:
- request parsing and validation
- session resolution
- profile resolution
- context pipeline resolution
- plugin chat interception
- agent invocation
- turn persistence
Use it when your app needs request/response semantics without SSE.
AgentrailChatRouteOptions
interface AgentrailChatRouteOptions {
/** Profile ID used when the request omits agentId */
defaultAgentId: string;
/** Session persistence implementation */
sessionStore: AgentrailSessionStore;
/** LLM call used to summarize old messages during compaction */
summarize: (
messages: Message[],
ctx?: { reason: "session_compaction" | "reactive_micro" | "reactive_full" },
) => Promise<string>;
/** Compaction trigger configuration */
compaction: {
triggerTokens: number;
minMessages: number;
reactive?: {
enabled?: boolean;
microTriggerPct?: number;
fullTriggerPct?: number;
preserveRecentApiRounds?: number;
microBatchGroups?: number;
maxReactiveCompactionsPerRequest?: number;
};
};
/** Resolve a profile by agentId for the current request */
resolveProfile(
agentId: string,
context: {
tenantId: string;
userId: string;
sessionId: string;
sessionRef: SessionRef;
sessionStore: AgentrailSessionStore;
},
onSubAgentEvent?: (event: object) => void,
): Promise<AgentrailProfile | null>;
/** Registered plugins */
plugins?: AgentrailPlugin[];
/** Static context providers applied to every request */
contextProviders?: ContextProvider[];
/** Dynamic context providers built per request */
getContextProviders?: (context: {
tenantId: string;
userId: string;
sessionId: string;
}) => Promise<ContextProvider[]> | ContextProvider[];
/** Request-time message rewrite hook */
getTransformContext?: (context: {
tenantId: string;
userId: string;
sessionId: string;
}) => Promise<TransformContextFn> | TransformContextFn;
/** Intercept a resolved request before agent execution */
handleResolvedRequest?: (
context: AgentrailResolvedChatContext,
) => Promise<AgentrailChatHandledResponse | null> | AgentrailChatHandledResponse | null;
onRequestStart?: (ctx: AgentrailRequestLifecycleContext) => void | Promise<void>;
onRequestEnd?: (ctx: AgentrailRequestLifecycleContext) => void | Promise<void>;
onTurnPersisted?: (ctx: AgentrailRequestLifecycleContext) => void | Promise<void>;
}Minimal example:
import { createChatRoute } from "@agentrail/app/advanced";
import { SessionManager } from "@agentrail/app";
import { createStaticProfileResolver } from "@agentrail/app";
app.route(
"/api/chat",
createChatRoute({
defaultAgentId: "default",
sessionStore: new SessionManager(dataDir),
summarize: async (messages) => {
// call your LLM summarizer here
return messages.map((m) => String("content" in m ? m.content : "")).join("\n");
},
compaction: { triggerTokens: 80_000, minMessages: 20 },
resolveProfile: createStaticProfileResolver([defaultProfile]),
}),
);createStreamRoute
Defined in:
This is the streaming host entry point.
It adds stream-specific responsibilities on top of the basic host lifecycle:
- attachment persistence
- SSE event forwarding
- proactive compaction signaling
- optional orchestration event forwarding
- workspace-aware streaming behavior
Use it when you need:
- incremental runtime events
- compaction visibility
- orchestration visibility
- long-running tool feedback
AgentrailStreamRouteOptions
interface AgentrailStreamRouteOptions {
/** Root data directory — used for attachment persistence */
dataDir: string;
defaultAgentId: string;
sessionStore: AgentrailSessionStore;
/** Required for sandbox-backed file attachment persistence */
sandboxManager: SandboxManager;
resolveProfile(
agentId: string,
context: {
tenantId: string;
userId: string;
sessionId: string;
sessionRef: SessionRef;
sessionStore: AgentrailSessionStore;
},
onSubAgentEvent?: (event: object) => void,
): Promise<AgentrailProfile | null>;
summarize(
messages: Message[],
ctx?: { reason: "session_compaction" | "reactive_micro" | "reactive_full" },
): Promise<string>;
compaction: {
triggerTokens: number;
minMessages: number;
reactive?: {
enabled?: boolean;
microTriggerPct?: number;
fullTriggerPct?: number;
preserveRecentApiRounds?: number;
microBatchGroups?: number;
maxReactiveCompactionsPerRequest?: number;
};
};
plugins?: AgentrailPlugin[];
contextProviders?: ContextProvider[];
getContextProviders?: (context: {
tenantId: string;
userId: string;
sessionId: string;
}) => Promise<ContextProvider[]> | ContextProvider[];
/** Returns an OrchestrationManager for the session — enables orchestration events */
getOrchestrationManager?: (context: {
tenantId: string;
userId: string;
sessionId: string;
sessionRef: SessionRef;
}) => Promise<OrchestrationManager>;
/** Intercept a resolved request before streaming begins */
handleResolvedRequest?: (context: AgentrailResolvedStreamContext) => Promise<boolean> | boolean;
/** Called for each trace-eligible SSE event — use for trace persistence */
onTraceEvent?: (
context: { tenantId: string; sessionId: string; sessionRef: SessionRef },
envelope: WorkflowTraceEventEnvelope,
) => void;
}Minimal example:
import { createStreamRoute } from "@agentrail/app/advanced";
import { SessionManager, createStaticProfileResolver } from "@agentrail/app";
import { SandboxManager } from "@agentrail/capabilities";
app.route(
"/api/stream",
createStreamRoute({
dataDir,
defaultAgentId: "default",
sessionStore: new SessionManager(dataDir),
sandboxManager: new SandboxManager(dataDir),
resolveProfile: createStaticProfileResolver([defaultProfile]),
summarize,
compaction: { triggerTokens: 80_000, minMessages: 20 },
plugins,
}),
);Profile Resolution
createStaticProfileResolver
Available from the main @agentrail/app entry point. Builds a simple resolver from a fixed list of profiles. Use this with createChatRoute / createStreamRoute when createAgentApp is not suitable.
import { createStaticProfileResolver } from "@agentrail/app";
const resolveProfile = createStaticProfileResolver([supportProfile, researchProfile]);For policy-driven or tenant-driven routing, implement ProfileResolver directly:
import type { ProfileResolver } from "@agentrail/app";
const resolveProfile: ProfileResolver = async ({ agentId, tenantId }) => {
return await loadProfileForTenant(agentId, tenantId);
};Pass the result to createChatRoute / createStreamRoute as resolveProfile.
Context Pipeline Helpers
composeTransformContexts
Composes multiple rewrite transforms left-to-right before provider injection. Use this when different capabilities or host layers each need to rewrite history.
createTransformContext
Defined in:
Converts an ordered list of ContextProviders into the runtime transformContext function shape. It can also accept a base rewrite transform; in that case, providers run against the rewritten history and their messages are prepended afterward.
createContextProviderFromTransform
Also defined in:
Adapts legacy or runtime-style transform logic back into provider form.
This helper is now legacy-only. It should be used only for prepend-only adapters. If your logic rewrites or removes existing messages, implement getTransformContext or use capability buildTransformContext instead.
Orchestration Integration
createOrchestrationRegistry
Defined in:
Manages per-session orchestration managers and lazy run initialization.
import { createOrchestrationRegistry } from "@agentrail/app/advanced";
const orchestrationRegistry = createOrchestrationRegistry({ dataDir });Session Store Boundary
All host primitives depend on the AgentrailSessionStore contract rather than a concrete session manager implementation.
That means you can plug in:
- the default file-backed
SessionManager - a compatible custom store
- a future non-filesystem-backed store, as long as it satisfies the contract
See Session Store Reference for the required shape.
Typical Custom Host Flow
A custom host usually looks like this:
- build or inject a session store
- build a profile resolver
- assemble plugins
- assemble context providers or a transform function
- mount
createChatRouteand/orcreateStreamRoute
The primitives are designed so you can replace any one of those steps without rewriting the whole stack.
Recommendation
Prefer createAgentApp from @agentrail/app for most applications. Drop to primitives from @agentrail/app/advanced only for the part that truly needs custom control.