defineRuntime
Runtime builder — defineEvents, defineFor, inferenceStack, and the user package contract.
defineRuntime() returns a fluent runtime builder that registers events, portals, actors, and an optional inference stack. Call .build() to produce a RuntimeDefinition the worker loads at boot.
Basic shape
import {
defineEvents,
defineFor,
defineRuntime,
z,
} from "@yoke/sdk";
const events = defineEvents({
"slack.message.received": z.object({
channel: z.string(),
text: z.string(),
}),
"work.assigned": z.object({
role: z.string(),
lang: z.string().optional(),
}),
});
export default defineRuntime()
.events(events)
.portals(({ events }) => {
const { portal } = defineFor(events);
return {
slack: portal({
interface: {
async sendMessage(channel: string, text: string) {
// ...
},
},
}),
};
})
.actors(({ events, portals }) => {
const { actor } = defineFor(events, portals);
return {
router: actor({
configSchema: z.object({ channels: z.array(z.string()) }),
stateSchema: z.object({ lastSeq: z.number() }),
async process(ctx, event) {
// ctx.portals.slack is fully typed
},
}),
};
})
.build();RuntimeBuilder methods
.events(map)
Register event type → Zod payload schemas. Merges with any previously registered events.
const events = defineEvents({
"order.created": z.object({ orderId: z.string() }),
});
defineRuntime()
.events(events)
.events({
"order.shipped": z.object({ orderId: z.string(), tracking: z.string() }),
});Returns a builder with an expanded EventMap type used for typed ctx.append() and process() event narrowing.
.inferenceStack(stack)
Attach an LLM routing configuration used by ctx.llm.infer() and agent processors. Without this, agent inference throws at runtime.
import { defineInferenceStack } from "@yoke/sdk";
const stack = defineInferenceStack({
models: {
"claude-sonnet": { id: "claude-sonnet-4-20250514", contextWindow: 200_000 },
},
providers: {
anthropic: anthropicProvider({ apiKey: process.env.ANTHROPIC_API_KEY! }),
},
routing: {
"claude-sonnet": ["anthropic"],
},
defaultModel: "claude-sonnet",
});
export default defineRuntime()
.events(events)
.inferenceStack(stack)
// ...
.build();InferenceStackConfig fields:
| Field | Type | Purpose |
|---|---|---|
models | Record<string, Model> | Logical model ids → provider model metadata |
providers | Record<string, LlmProvider> | Named provider implementations |
routing | Record<string, string[]> | Model id → ordered provider ids (round-robin failover) |
defaultModel | string? | Default when callers omit runConfig.model |
.agents(fn)
Register high-level agent definitions. The SDK expands each into the standard actor stack (ingestion, egress, processor, optional follow-up and guardian actors).
.agents(() => ({
support: defineAgent({
name: "support-bot",
model: "local-tool-loop",
tools: [echoToolchain],
followUp: false,
guardianAngel: false,
}),
}))The callback receives { events, inferenceStack }. Returns a map of agent keys → defineAgent() inputs. Agent refs (agents.support) are available to .chatPlatforms().
.chatPlatforms(fn)
Register chat platform integrations. Each expands to a portal (webhook ingress + outbound API) and a bridge actor that wires the platform adapter to an agent via createAgent / joinConversation.
.chatPlatforms(({ agents }) => ({
slack: defineChatPlatform({
adapter: createStubChatAdapter("slack"),
agent: agents.support,
webhookPath: "/webhooks/slack",
}),
}))Requires .agents() (or manual agent refs) when binding agent. See Agents for chat platform integration patterns.
.bootstrap(fn)
One-shot boot hook — provision instances with stable ids. Must complete quickly; no long-running loops.
.bootstrap(async (ctx) => {
await ctx.instances.create("router", {
id: "router",
config: { channels: ["#general"] },
filter: { type: "slack.message.received" },
});
})BootstrapCtx exposes ctx.instances and ctx.append(). Idempotent by instance id. Runs after portal singleton rows exist, before HTTP routes mount.
.portals(fn)
Register portal definitions. The callback receives { events } — the accumulated event map.
.portals(({ events }) => {
const { portal } = defineFor(events);
return {
slack: portal({ /* PortalDefinition */ }),
webhook: portal({ /* ... */ }),
};
})Updates builder types:
InferPortalRegistry— registered portal namesInferPortalApis— portal interface types available in.actors()asportals
.actors(fn)
Register actor definitions. The callback receives { events, portals }.
.actors(({ events, portals }) => {
const { actor } = defineFor(events, portals);
return {
coder: actor({ /* ActorDefinition */ }),
router: actor({ /* ... */ }),
};
})Updates InferActorRegistry with typed config/state per actor name.
.portal(name, definition) / .actor(name, definition)
Incremental registration without callbacks. Equivalent to merging one definition at a time:
defineRuntime()
.events(events)
.portal("slack", slackDefinition)
.actor("coder", coderDefinition)
.build();Prefer .portals() / .actors() for type inference across the registry.
.build()
Returns RuntimeDefinition:
type RuntimeDefinition = {
actors: ActorRegistry;
portals: PortalRegistry;
events: EventMap;
inferenceStack?: InferenceStackConfig;
agents?: Record<string, AgentDefinition>;
chatPlatforms?: Record<string, ChatPlatformDefinitionInput>;
bootstrap?: (ctx: BootstrapCtx) => Promise<void>;
};The worker reads this object (or calls .build() on a builder export) to register specs, event schemas, and portal routes.
defineEvents
defineEvents() preserves literal event type keys for TypeScript inference:
const events = defineEvents({
"work.assigned": z.object({ lang: z.string() }),
});
// events is typed as { "work.assigned": ZodObject<...> }Event types not in the map still append at runtime (EventInput fallback), but known types get payload checking in the editor and Zod validation in the worker.
defineFor
Scoped factories that omit the redundant events field inside runtime callbacks:
const { actor, portal } = defineFor(events, portals);
// ^ optional — pass portals in .actors() for typed ctx.portals.*| Factory | Wraps | Injects |
|---|---|---|
actor(input) | defineActor | events from closure |
portal(input) | definePortal | events from closure |
Standalone definitions outside the builder still use defineActor({ events, ... }) / definePortal({ events, ... }) directly.
Type parameters
The builder carries generic state through the chain:
RuntimeBuilder<
TActors, // registered actor registrations
TPortals, // registered portal registrations
TEventMap, // merged event schemas
TPortalApis, // portal interface map for actors
TInferenceStack
>Utility types:
| Type | Purpose |
|---|---|
Registry<TActors, TPortals> | { actors, portals } pair |
RuntimeHandle | Live runtime with .portals client map and .stop() |
EmptyRegistry | Initial builder state |
InferPortalClients<TPortals> | Portal API types from registrations |
User package contract
- File:
yoke.tsat package root. - Export: default export must be a
defineRuntime()builder or its.build()result. - Validation: actor
configSchema/stateSchemaand portalconfigSchemaare validated when instances are created. - Boot: Deno worker imports the module, extracts actor specs (
get_actor_specs), portal routes (list_portal_routes), and event schemas for Zod parsing.
// Valid — builder (worker calls .build())
export default defineRuntime().events(events).actors(...);
// Valid — definition object
export default defineRuntime().events(events).actors(...).build();inferenceStack in actor code
Once configured, actors and portals call:
async process(ctx, event) {
const messages = await ctx.llm.infer(
[{ type: "message", role: "user", body: { text: "Hello" } }],
{ model: "claude-sonnet", temperature: 0.2 },
);
}See Context API — LLM client for InferRunConfig fields.