Yoke

Events & the event stream

The central nervous system of a Yoke runtime.

The event stream (event bus) is the central nervous system of Yoke. Everything communicates through it. One interface, many backends — the code that talks to the bus never changes, only the delivery guarantees and durability do.

Events

An event is an envelope:

{
  id: string;              // unique id
  topic: string;           // e.g. "messages.incoming"
  type: string;            // e.g. "user.message"
  source: string;          // who published it
  timestamp: string;       // ISO 8601
  payload: unknown;        // your data
  metadata?: Record<string, unknown>;
}

Every runtime can define its own events, each with a type, a description, and an optional payload schema (zod) for validation and tooling:

runtime.event({
  type: "order.placed",
  description: "A customer placed an order.",
  schema: z.object({ orderId: z.string(), total: z.number() }),
});

Publishing

await ctx.emit({
  topic: "orders.created",
  type: "order.placed",
  payload: { orderId: "ord_123", total: 19.99 },
});

When a schema is provided, the payload is validated at publish time.

Subscribing

Actors subscribe on your behalf, but you can subscribe directly too:

const subscription = await bus.subscribe(
  ["orders.created", "invoices.*"],
  (envelope) => {
    console.log(envelope.payload);
  },
);
// later:
await subscription.unsubscribe();

Topic conventions

  • Topics are dot-separated namespaces: messages.incoming, yoke.runtime.*.
  • A trailing * component is a wildcard that matches any one segment.
  • ** matches any suffix.

The interface

interface EventBus {
  readonly name: string;
  publish<P>(event: PublishEvent<P>): Promise<EventEnvelope<P>>;
  subscribe<P>(
    topics: string | string[],
    handler: (env: EventEnvelope<P>) => void | Promise<void>,
    options?: SubscribeOptions,
  ): Promise<EventSubscription>;
}

SubscribeOptions supports resuming from a cursor (startAt) and stream consumer/group names (consumer) for durable backends.

Adapters

AdapterModuleDelivery modelDurable
MemoryMemoryEventBusin-process pub/sub, async FIFO per subscriberno
Redis StreamsRedisEventBusstream groups + consumers (ioredis)yes
NATS JetStreamJetStreamEventBuscore NATS + JetStream consumers (nats)yes
NanoNanoEventBusprovider contract neededyes
import { RedisEventBus } from "yoke";

const bus = new RedisEventBus({
  redis: { host: "localhost", port: 6379 },
  stream: "yoke-events",
});

New providers implement the EventBus interface — never the other way around.

Standard events

The runtime emits these on its own stream (yoke.<scope>.<action>):

TypeEmitted whenPayload
runtime.startedruntime boots{ runtimeId }
runtime.stoppingshutdown begins{ runtimeId }
runtime.stoppedshutdown finished{ runtimeId }
agent.spawnedan agent instance is created{ agentName, instanceId }
agent.message.receiveda message is dispatched to an agent{ agentName, instanceId, message }
agent.repliedan agent produces a reply{ agentName, instanceId, reply }
agent.failedan agent invocation throws{ agentName, instanceId, error }
actor.startedan actor begins listening{ actorName }
actor.stoppedan actor stops listening{ actorName }
actor.failedan actor handler throws{ actorName, eventId, error }
tool.calleda tool is invoked{ toolName, agentName? }
tool.faileda tool throws{ toolName, error }
event.defineda custom event is registered{ type, description? }

On this page