Instances
Runtime instances — creation, config, filters, lifecycle, and who can spawn them.
Definitions are fixed at build time. Instances are dynamic runtime objects created from those definitions with per-instance config and filters.
Understanding this distinction is essential for building scalable Yoke systems.
Definition vs instance
defineRuntime()
.actors(() => ({
coder: actor({ configSchema, stateSchema, process }), // DEFINITION
}));
// At runtime:
await ctx.instances.create("coder", { // INSTANCE
config: { model: "gpt-4o", lang: "typescript" },
filter: { type: "work.assigned", where: { "payload.lang": "typescript" } },
});| Definition | Instance | |
|---|---|---|
| Count | One per name in yoke.ts | Unlimited |
| Storage | TypeScript | Postgres actor_instances / portal_instances |
| Identity | "coder" (definition name) | UUID instanceId |
| Filter | N/A (on instance) | Declarative or function |
| SQLite | Schema in stateSchema | Provisioned per instance |
Portal definitions always have one singleton instance per tenant. Actor definitions can have many.
Creating instances
From actor or portal code
const handle = await ctx.instances.create("coder", {
config: { model: "claude-sonnet", lang: "rust" },
filter: { type: "work.assigned", where: { "payload.lang": "rust" } },
});
console.log(handle.id); // instance UUIDconfig is validated against the definition's configSchema. filter determines stream subscription.
From bootstrap()
Packages typically provision fixed topology in defineRuntime().bootstrap() using stable instance ids:
.bootstrap(async (ctx) => {
await ctx.instances.create("router", {
id: "router",
config: { channels: ["#general"] },
filter: { type: "slack.message.received" },
});
})Re-running bootstrap() is safe — creation is idempotent by id.
From HTTP / CLI
Multi-tenant control plane and executor APIs can create instances externally. The same Postgres rows and instance.created events are emitted.
Instance options
type CreateInstanceOpts = {
id: string; // required — idempotent key for this instance
config: TConfig;
filter?: DeclarativeFilter | FunctionFilter;
typeHint?: string; // narrows slow-path function filter evaluation
};Declarative filters
Persisted in Postgres, indexed for fast fan-out:
filter: { type: "conversation.message.posted" }
filter: { type: "work.*" }
filter: {
type: "work.assigned",
where: { "payload.lang": "typescript" },
}Function filters
Require filterFromConfig on the definition:
await ctx.instances.create("coder", {
config: { lang: "go" },
filterKind: "function",
});On boot/restart, the runtime calls filterFromConfig(config) to rebuild the function. Without the factory, persisted function-filter instances exist but receive no events.
Lifecycle
create → active → destroy| Event | Stream type | When |
|---|---|---|
| Created | instance.created | Row inserted, SQLite provisioned, filter registered |
| Destroyed | instance.destroyed | onDestroy called, queue cleared, rows deleted |
await ctx.instances.destroy(instanceId);Destroy is permanent — SQLite blob deleted, queue rows removed, filter deregistered.
Instance record in Postgres
actor_instances stores:
| Column | Purpose |
|---|---|
id | Instance UUID |
definition_name | e.g. "coder" |
config | JSON blob |
filter_kind | declarative or function |
filter | Serialized declarative filter or config for function rebuild |
queue_policy | Serialized mode + concurrency |
worker_id | Horizontal scaling assignment |
tenant_id | Multi-tenant isolation |
status | active, etc. |
On restart, the runtime loads all instance rows, rebuilds filters, provisions SQLite from actor_sqlite or S3, and resumes drain loops.
Worker assignment
In multi-worker deployments, new instances inherit the creating worker's worker_id. Orphan instances (worker_id IS NULL) are claimed on worker startup.
Only the owning worker drains an instance's queue. See Scaling.
Agent instances
Agent bootstrap creates multiple instances per join:
const agent = await createAgent(ctx, { name: "support-bot" });
// → { actorId, egressInstanceId }
const join = await joinConversation(ctx, agent, room.id, { model: "gpt-4o" });
// → { ingestionInstanceId, processorInstanceId, monologueId, ... }Each join stint gets fresh ingestion and processor instances. One egress instance per agent routes replies across all forum joins.
sqliteStoreId
Advanced: actors can resolve a logical SQLite store segment per invocation via sqliteStoreId on the definition. Default is instanceId. Used when one instance manages multiple logical state partitions.