Context
Share state across agents, tools, and hooks with typed context
Context passes shared state -- database connections, API clients, user info -- through the entire agent execution. Tools, hooks, and guardrails all receive the same typed, immutable context object, scoped to a single run.
Basic usage
Define an interface for your context, then pass it to Agent<TContext> and provide the value via run():
import { AzureResponsesModel } from "stratus-sdk";
import { Agent, run } from "stratus-sdk/core";
interface AppContext {
userId: string;
db: Database;
logger: Logger;
}
const model = new AzureResponsesModel({
endpoint: process.env.AZURE_ENDPOINT!,
apiKey: process.env.AZURE_API_KEY!,
deployment: "gpt-5.2",
});
const agent = new Agent<AppContext>({
name: "support",
model,
instructions: "You are a customer support agent.",
tools: [lookupOrder, cancelOrder],
});
const result = await run(agent, "Where is my order #123?", {
context: { userId: "user_abc", db: myDb, logger: myLogger },
});The generic parameter Agent<AppContext> flows through the entire system. TypeScript will enforce that every tool, hook, and guardrail on this agent uses the same context type.
Accessing context in tools
The execute function receives context as its first argument:
import { tool } from "stratus-sdk/core";
import { z } from "zod";
interface AppContext {
userId: string;
db: Database;
}
const lookupOrder = tool({
name: "lookup_order",
description: "Look up an order by ID",
parameters: z.object({ orderId: z.string() }),
execute: async (ctx: AppContext, { orderId }) => {
const order = await ctx.db.orders.find(orderId, ctx.userId);
return JSON.stringify(order);
},
});The ctx parameter is fully typed -- you get autocomplete for ctx.db, ctx.userId, and any other properties on your interface.
Dynamic instructions
Instructions can be a function that receives context, letting you customize the system prompt per-request:
const agent = new Agent<AppContext>({
name: "support",
model,
instructions: (ctx) =>
`You are a support agent for user ${ctx.userId}. ` +
`Their account tier is ${ctx.db.getTier(ctx.userId)}.`,
tools: [lookupOrder],
});Async functions are also supported:
instructions: async (ctx) => {
const rules = await ctx.db.getRules(ctx.userId);
return `Follow these rules: ${rules}`;
},Accessing context in hooks
Every hook receives context in its parameter object. Use this for audit logging, metrics, or permission checks:
const agent = new Agent<AppContext>({
name: "support",
model,
hooks: {
beforeRun: async ({ agent, input, context }) => {
context.logger.info(`[${agent.name}] user=${context.userId} input="${input}"`);
},
afterRun: async ({ result, context }) => {
context.logger.info(`Response: ${result.output}`);
},
beforeToolCall: ({ toolCall, context }) => {
if (toolCall.function.name === "cancel_order" && !context.isAdmin) {
return { decision: "deny", reason: "Admin access required" };
}
},
},
});See Hooks for the full set of lifecycle callbacks.
Accessing context in guardrails
Guardrails receive context as their second argument. Use it for user-specific validation:
import type { InputGuardrail } from "stratus-sdk/core";
interface AppContext {
userId: string;
tenantId: string;
}
const tenantGuardrail: InputGuardrail<AppContext> = {
name: "tenant_check",
execute: async (input, ctx) => {
const isAllowed = await checkTenantPermissions(ctx.tenantId, input);
return { tripwireTriggered: !isAllowed };
},
};
const agent = new Agent<AppContext>({
name: "support",
model,
inputGuardrails: [tenantGuardrail],
});See Guardrails for input and output validation details.
Context with sessions
Pass context via createSession(). It flows to every stream() call for the lifetime of the session:
import { createSession } from "stratus-sdk/core";
const session = createSession<AppContext>({
model,
instructions: "You are a customer support agent.",
tools: [lookupOrder, cancelOrder],
context: { userId: "user_abc", db: myDb, logger: myLogger },
});
session.send("Where is my order?");
for await (const event of session.stream()) {
if (event.type === "content_delta") process.stdout.write(event.content);
}Session context is set once at creation time. To change context between turns, create a new session or use resumeSession() with a new config.
Context with handoffs
Context is shared across all agents in a handoff chain. When Agent A hands off to Agent B, both receive the same context object.
All agents in a handoff chain must share the same TContext type. This is enforced at the type level:
interface AppContext {
userId: string;
db: Database;
}
const refundAgent = new Agent<AppContext>({
name: "refunds",
model,
instructions: "Process refund requests.",
tools: [processRefund], // processRefund receives AppContext too
});
const triageAgent = new Agent<AppContext>({
name: "triage",
model,
instructions: "Route the user to the right specialist.",
handoffs: [refundAgent],
});
await run(triageAgent, "I want a refund", {
context: { userId: "user_abc", db: myDb },
});
// When triage hands off to refunds, the same context is passed throughThe onHandoff callback also receives context:
import { handoff } from "stratus-sdk/core";
handoff({
agent: refundAgent,
onHandoff: async (ctx) => {
await ctx.db.audit.log("handoff_to_refunds", ctx.userId);
},
});Next steps
Last updated on