stratus

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

context.ts
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:

context-tool.ts
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:

dynamic-instructions.ts
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:

context-hooks.ts
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:

context-guardrail.ts
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:

session-context.ts
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:

handoff-context.ts
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 through

The 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

Edit on GitHub

Last updated on

On this page