Yoke.sh
Agents

Agent bootstrap

Stand up agents with createAgent and joinConversation — no agents table, no mirror relations, no forum auto-creation.

Agent bootstrap follows a strict convention: forums are created separately, agents are actor + egress, and each forum join creates a monologue stint with ingestion and processor instances.

1. Create a chat room (forum)

Agents do not create forums. Use the Chat SDK or HTTP client:

import { createChatClient } from "@yoke/sdk";

const chat = createChatClient(ctx.conversations);
const room = await chat.createRoom({ title: "support" });

const human = await chat.createActor({ name: "alice" });
const humanP = await chat.addMember(room.id, human.id);
await chat.send(room.id, "Hello!", { authorParticipantId: humanP.id });

Headless HTTP

For apps without a running actor context, use HTTP clients against the control plane:

import { createChatHttpClient, createAgentBootstrapHttpClient } from "@yoke/sdk";

const chat = createChatHttpClient({ baseUrl: controlPlaneUrl, tenantId });
const agents = createAgentBootstrapHttpClient({
  baseUrl: `${controlPlaneUrl}/v1/tenants/${tenantId}/ingress`,
});

const room = await chat.createRoom({ title: "support" });
const agent = await agents.createAgent({ name: "support-bot" });
await agents.joinConversation(agent, room.id, { model: "gpt-4o" });

2. Create an agent

import { createAgent } from "@yoke/sdk";

const agent = await createAgent(ctx, { name: "support-bot" });
// → { actorId, egressInstanceId }

This creates:

  1. One actors row with role: "agent" (the persona)
  2. One egress actor instance with config { agentActorId } — routes replies from any monologue stint back to forums

createAgent does not create a monologue. Monologues are created per forum join.

3. Join a chat room

import { joinConversation } from "@yoke/sdk";

const join = await joinConversation(ctx, agent, room.id, {
  model: "gpt-4o", // optional, default "fixture-echo"
});

Returns a JoinHandle:

type JoinHandle = {
  forumId: string;
  forumParticipantId: string;
  monologueId: string;
  monologueParticipantId: string;
  systemActorId: string;
  systemParticipantId: string;
  ingestionInstanceId: string;
  processorInstanceId?: string;
  followUpInstanceId?: string;
  guardianSubscriptionInstanceId?: string;
};

What joinConversation does

sequenceDiagram
  participant App
  participant Store as Conversation store
  participant RT as Runtime

  App->>Store: createMonologueForStint (system + agent participants)
  App->>Store: addParticipant(forum, agent, metadata.monologueId)
  App->>RT: create ingestion instance
  App->>RT: create processor instance
  App->>RT: create followUp instance (optional)
  App->>RT: create guardianSubscription instance (optional)
  App->>RT: append agent.forum.joined
  Note over RT: Egress updates ctx.state.joins
  1. Creates a monologue for this stint (system + agent participants)
  2. Adds the agent as a forum participant with metadata.monologueId
  3. Spins up ingestion and processor instances for this monologue
  4. Optionally creates follow-up and guardian subscription instances
  5. Emits agent.forum.joined so egress learns forumId → { monologueId, forumParticipantId }

Ingested forum messages are copied into the monologue by external shadow actors — one per forum author. No mirror relations. Ingested copies carry body.meta.sourceConversationId for egress routing.

Join options

await joinConversation(ctx, agent, forumId, {
  metadata: { team: "support" },       // extra forum participant metadata
  model: "claude-sonnet-4",            // processor model id
  createProcessor: true,               // default true
  followUp: false,                     // disable follow-up timer
  followUp: { delayMs: 60_000, text: "Still need help?" },
  guardianAngel: false,                // disable guardian subscription
  guardianAngel: { model: "guardian-steer" },
});

4. Register actor definitions

Register SDK-provided actors in your yoke.ts package:

import {
  defineAgentStack,
  defineEgressActor,
  defineFollowUpActor,
  defineGuardianAngel,
  defineIngestionActor,
  defineProcessorActor,
  agentEvents,
  conversationEvents,
  defineEvents,
  defineFor,
  defineRuntime,
  egressConfigSchema,
  egressStateSchema,
  followUpConfigSchema,
  followUpStateSchema,
  ingestionConfigSchema,
  ingestionStateSchema,
  processorConfigSchema,
  processorStateSchema,
} from "@yoke/sdk";

const events = defineEvents({ ...conversationEvents, ...agentEvents });

const agentStack = defineAgentStack({
  compaction: async (messages) => {
    if (messages.length <= 20) return messages;
    return messages.slice(-10);
  },
});

const guardianAngel = defineGuardianAngel({
  filter: agentSaidV1Filter,
  model: "guardian-steer",
});

export default defineRuntime()
  .events(events)
  .actors(({ events }) => {
    const { actor } = defineFor(events);
    return {
      ingestion: actor(defineIngestionActor({
        configSchema: ingestionConfigSchema,
        stateSchema: ingestionStateSchema,
      })),
      egress: actor(defineEgressActor({
        configSchema: egressConfigSchema,
        stateSchema: egressStateSchema,
      })),
      processor: actor(defineProcessorActor({
        configSchema: processorConfigSchema,
        stateSchema: processorStateSchema,
        compaction: agentStack.compaction,
      })),
      followUp: actor(defineFollowUpActor({
        configSchema: followUpConfigSchema,
        stateSchema: followUpStateSchema,
      })),
      guardianSubscription: actor(guardianAngel.subscription),
      guardianAngel: actor(guardianAngel.runner),
    };
  });

Optional coordinator registry

Store join handles in a coordinator actor's durable SQLite state:

type AgentRegistryState = {
  agents: Record<string, AgentHandle & { joins: Record<string, JoinHandle> }>;
};

Postgres participants.metadata.monologueId is canonical — the registry is optional convenience for portal handlers.

Checklist

  • Chat room created separately (createChatClient / HTTP)
  • Agent created with createAgent (actor + egress only)
  • Each forum joined via joinConversation (monologue + ingestion + processor per stint)
  • monologueId on forum participant metadata (automatic)
  • No mirror relations anywhere
  • Processor posts type: "reply" when ready for forum (egress copies it out)

Expose agent bootstrap via portal routes (POST /agents, POST /agents/:agentId/join) — see Portals.

On this page