Context API
ActorCtx and PortalCtx — append, state, instances, portals, conversations, schedules, correlation, and llm.
ctx is the runtime handle passed to actor process(), portal start(), route handlers, and lifecycle hooks. The SDK wraps yoke ops into a typed surface — user code never calls Rust directly.
ActorCtx
Full surface for actor handlers:
type ActorCtx<TConfig, TState, TPortals, TEventMap> = {
instanceId: string;
config: TConfig;
sqliteStoreId: string;
state: DurableState<TState>;
sqlite: SQLiteDatabase;
correlation: CorrelationContext;
append(input, options?): Promise<AppendResult>;
instances: InstanceManager;
portals: PortalClients<TPortals>;
conversations: ConversationClient;
schedules: ScheduleManager;
llm: LlmClient; // requires inferenceStack on runtime
};instanceId and config
async process(ctx, event) {
console.log(ctx.instanceId); // UUID
console.log(ctx.config.model); // validated against configSchema
}state and sqlite
See State & SQLite.
await ctx.state.update((s) => ({ ...s, count: s.count + 1 }));
await ctx.sqlite.exec("SELECT 1");append
await ctx.append({
type: "work.assigned",
payload: { role: "coder", lang: "typescript" },
});
await ctx.append(
{ type: "urgent.alert", payload: {} },
{ correlation: "new", priority: 0, groupId: "alerts" },
);Typed for events in defineEvents(). Source is set automatically (kind: "actor", instanceId).
correlation
const id = ctx.correlation.inherit();
await ctx.append({ type: "step.done", payload: {} }); // inherits id| Method | Behavior |
|---|---|
current | Correlation id of event being processed |
inherit() | Reuse current or create new |
create() | Always new id |
instances
const handle = await ctx.instances.create("helper", {
config: { shard: "us-east" },
filter: { type: "work.assigned" },
});
await ctx.instances.destroy(handle.id);Typed when using defineFor — definition names autocomplete.
portals
await ctx.portals.slack.sendMessage("#general", "Done!");Typed from portal interface definitions. Routes through portal_call → portal_invoke.
conversations
Conversation store CRUD — available when tenant has conversation migrations:
const actor = await ctx.conversations.createActor({ name: "alice" });
const room = await ctx.conversations.createConversation({ title: "support" });
const p = await ctx.conversations.addParticipant(room.id, actor.id);
await ctx.conversations.postMessage(room.id, {
parts: [{ type: "text", text: "Hello", format: "plain" }],
}, { authorParticipantId: p.id, type: "message" });Prefer createChatClient(ctx.conversations) for ergonomic wrappers.
schedules
await ctx.schedules.schedule({
key: "retry:42",
delay: { seconds: 30 },
event: { type: "retry.tick", payload: { id: "42" } },
});See Schedules.
llm
Requires .inferenceStack() on the runtime builder:
const outputs = await ctx.llm.infer(messages, {
model: "gpt-4o",
temperature: 0.7,
maxTokens: 1024,
tools: resolvedTools,
});See Inference.
PortalCtx
Portals receive a subset — no state, sqlite, or llm:
type PortalCtx = {
instanceId: string;
config: TConfig;
correlation: CorrelationContext;
append(input, options?): Promise<AppendResult>;
instances: InstanceManager;
conversations: ConversationClient;
schedules: ScheduleManager;
};Route handlers and start() use PortalCtx:
routes: [{
method: "POST",
path: "/webhooks/shopify",
handler: async (req, ctx) => {
await ctx.append({ type: "ingress.shopify.order", payload: parsed });
return { status: 200, body: "ok" };
},
}],Lifecycle hooks
defineActor({
async onCreate(ctx) {
await ctx.sqlite.exec("CREATE TABLE IF NOT EXISTS ...");
},
async onDestroy(ctx) {
// cleanup before teardown
},
async process(ctx, event) { /* ... */ },
});onCreate runs once after SQLite provisioning. onDestroy runs before instance rows are deleted.
Type flow
defineFor(events, portals) threads types through:
defineRuntime().events(events).portals(...).actors(...)
→ ActorCtx.config: CoderConfig
→ ActorCtx.state: CoderState
→ ActorCtx.portals.slack: SlackApi
→ process event payload: narrowed by EventMap