> Documentation index for m4trix: https://m4trix.dev/llms.txt. Every page is available as Markdown by appending `.md` to its URL.

# Channels

> Named streams of events that agents subscribe to and publish into: what they carry, who receives what, and how events leave the network.

Source: https://m4trix.dev/docs/concepts/channels

A channel is a named stream of events inside a network: agents subscribe to channels to receive events and publish to channels to send them.

## Mental model

Topology of this network, generated from the recorded run:

- Channel `main` (the main channel): carries every event.
- Channel `work`: carries only `task`.
- Channel `client`: carries only `answer`; leaves the network through an `sse` proxy.
- Agent `router`: subscribes to `main` → publishes to `work`; listens to `user-message`, emits `task`.
- Agent `worker`: subscribes to `work` → publishes to `client`; listens to `task`, emits `answer`.
- Agent `auditLog`: subscribes to `main` and `work` → publishes nowhere; catch-all (no `listensTo`): receives every event on its channels.
- The run ends on `answer` (`endsOn`).

Each lane is a channel, `main` first. An agent sits where the channel it subscribes to meets the channel it publishes to, and a proxy is a port on the network's border where a channel's events leave it (here `sse` on `client`).

## Wiring

This setup adds a `work` and a `client` channel next to `main` and wires three agents to them:

```ts title="channels.ts"
export const network = AgentNetwork.setup(
  ({ mainChannel, createChannel, proxy, registerAgent, endsOn }) => {
    const work = createChannel('work').events([Task]);
    const client = createChannel('client').events([Answer]).proxy(proxy.sse());

    registerAgent(router).subscribe(mainChannel).publishTo(work);
    registerAgent(worker).subscribe(work).publishTo(client);
    registerAgent(auditLog).subscribe(mainChannel).subscribe(work);

    endsOn(Answer);
  },
);
```

The events and agents it wires live in the same file; the player below shows them next to this code.

## How a run flows

One run of this network, recorded from the real runtime, starting with a `user-message` published on `main`:

```ts title="channels.ts"
export const router = AgentFactory.run()
  .listensTo([UserMessage])
  .emits([Task])
  .logic(async ({ triggerEvent, emit }) => {
    emit({ name: 'task', payload: { topic: triggerEvent.payload.text } });
  })
  .produce({});

export const worker = AgentFactory.run()
  .listensTo([Task])
  .emits([Answer])
  .logic(async ({ triggerEvent, emit }) => {
    emit({ name: 'answer', payload: { text: `Looked into: ${triggerEvent.payload.topic}` } });
  })
  .produce({});

// No listensTo: it receives every event on the channels it subscribes to.
export const auditLog = AgentFactory.run()
  .logic(async ({ triggerEvent }) => {
    console.log('[audit]', triggerEvent);
  })
  .produce({});

export const network = AgentNetwork.setup(
  ({ mainChannel, createChannel, proxy, registerAgent, endsOn }) => {
    const work = createChannel('work').events([Task]);
    const client = createChannel('client').events([Answer]).proxy(proxy.sse());

    registerAgent(router).subscribe(mainChannel).publishTo(work);
    registerAgent(worker).subscribe(work).publishTo(client);
    registerAgent(auditLog).subscribe(mainChannel).subscribe(work);

    endsOn(Answer);
  },
);

/** What the docs recorder publishes to start a run of this snippet. */
export const scenario = {
  start: UserMessage.make({ text: 'Why did checkout get slower?' }),
};
```

1. `user-message` is published on `main` from outside the network; `router` picks it up, and `auditLog`, which has no `listensTo` and so takes every event on `main`, gets a copy too.
2. `router` receives it and emits `task` on `work`, a channel that only carries `task`; `worker` takes it from there, and `auditLog` gets a copy too because it also subscribes to `work`.
3. `worker` receives it and emits `answer` on `client`, a channel that only carries `answer`; no agent subscribes to `client`, so `answer` leaves the network through its `sse` proxy and, as a terminal event, ends the run.
4. The runtime then publishes `m4trix:run.completed` on every channel (`main`, `work` and `client`) to close the run; runtime events never invoke agents, so not even `auditLog` receives it.

## Rules and guarantees

### Defining channels

- **Names are kebab-case:** lowercase letters and digits in hyphen-separated segments, such as `work` or `client-output`. Any other name makes `AgentNetwork.setup()` throw. Names are typed as the branded `ChannelName`.
- **`main` always exists.** The network creates it, and `mainChannel` in the setup context is that channel, not a function: pass it straight to `subscribe` or `publishTo`. `expose()` publishes start events to it.
- **One channel per name.** `createChannel(name)` returns the existing channel when the name is taken, so `createChannel('main')` is `mainChannel`.
- **Agents choose their channels.** Each `subscribe(channel)` or `publishTo(channel)` call on the binding from `registerAgent` adds one channel; an agent can have several of each.

### What a channel carries

- **Everything, unless declared.** `.events([...])` limits a channel to those events. Calling it again replaces the list, and an empty list carries everything again. See [`.events()`](https://m4trix.dev/docs/api-reference/channel-api.md#events).
- **An agent's emit goes where it is accepted.** The event is published to each of the agent's `publishTo` channels that accepts it and skips the others. If the agent has `publishTo` channels and none of them accepts it, `emit` throws `EmitValidationError` (`reason: 'channel'`): the invocation fails even if the agent catches the error, and the network's [`onAgentError`](https://m4trix.dev/docs/api-reference/agent-network.md#setup-options) policy applies (run failure kind `invalid-emit`). An agent without any `publishTo` channel publishes nowhere instead (see [Common mistakes](#common-mistakes)).
- **Outside publishes are dropped, not thrown.** `plane.publish`, or a proxy's `publish` / `publishInbound`, of an event the channel does not accept resolves `false`, delivers nothing and logs a warning through the network's `logger`. The run keeps going.
- **Start events must fit `main`.** `expose()` throws at setup when `main` declares events that exclude one of its `triggerEvents`.

### Delivery

- **Broadcast, not a work queue.** Every agent subscribed to the channel whose `listensTo` names the event gets it, and so does every reader of the channel (`plane.subscribe`, SSE streams).
- **Catch-all agents.** An agent without `.listensTo()`, like `auditLog`, receives every event on the channels it subscribes to, except the runtime's `m4trix:*` events.
- **Once per agent.** An event published to several channels at once reaches each agent once, even when the agent subscribes to more than one of them.
- **One at a time, in order, per run.** Each agent has one inbox per run, merged across its channels, and handles that run's events one at a time in publish order. Different agents handle the same event concurrently, runs proceed in parallel, and nothing orders one agent's work against another's. See [Concurrency model](https://m4trix.dev/docs/api-reference/agent-network.md#concurrency-model).
- **Emits keep their order.** Everything one invocation emits is published in the order it called `emit`.

### Runtime events

- **Every channel gets them.** When a run ends, the runtime publishes `m4trix:run.completed`, `m4trix:run.failed` or `m4trix:run.cancelled` to every channel of the network (and `m4trix:agent.error` under `onAgentError: 'continue'`). They pass any `.events()` declaration and never trigger agents. The `m4trix:` prefix is reserved: `AgentNetworkEvent.of()` rejects it. See [Run Lifecycle](https://m4trix.dev/docs/api-reference/io-adapters.md#run-lifecycle).

### Proxies

- **A proxy is a declaration.** `.proxy(...)` marks a channel whose events cross the network's border; `network.expose(...)` creates the stream that reads it. A channel can declare several proxies.
- **`proxy.sse()` is built in.** `registerSSEStream()` streams one channel: the one passed as `channel` (it must declare `sse`, or `expose()` throws), else the only channel with an `sse` proxy, else `client`, else `main`. With several `sse` channels and no `channel`, `expose()` throws. See [registerSSEStream() options](https://m4trix.dev/docs/api-reference/io-adapters.md#registerssestream-options).
- **Other kinds are your own.** Declare one with `proxy.custom(kind, config?, direction?)` or `defineProxyKind(kind)` and expose it with `registerCustomProxy` or the kind's `.register()`. The direction defaults to `'outbound'`. Kafka and Socket.IO bridges are not built in. See [Custom Proxies](https://m4trix.dev/docs/api-reference/io-adapters.md#custom-proxies).

### Setup-time check

- **Contradictions throw.** When the setup callback returns, `AgentNetwork.setup()` compares what each agent (and its tools) emits and listens to with what its channels accept. It throws a `NetworkWiringError` listing every `emit-not-accepted` (no `publishTo` channel accepts an event from `.emits()`) and `listen-not-accepted` (no subscribed channel accepts an event from `.listensTo()`).
- **Smells are logged.** `unconsumed-event`, `agent-publishes-nowhere`, `agent-not-subscribed`, `agent-unreachable` and `terminal-event-not-emitted` go to the network's `logger`, and the network runs.
- **What counts as the border.** The check treats `main` and channels with an `'inbound'` or `'bidirectional'` proxy as fed from outside, and channels with a proxy that is not `'inbound'`, or named `client`, as streamed out.
- `network.validate()` runs the same check without throwing. See [Wiring check](https://m4trix.dev/docs/api-reference/agent-network.md#wiring-check).

## Common mistakes

> **Warning:** - **Calling `mainChannel('main')`.** `mainChannel` is already the `main` channel: write `subscribe(mainChannel)`.
> - **Wiring an event to a channel that does not declare it.** With `work` declared as `.events([Task])`, an agent that emits `answer` and publishes only to `work` makes setup throw `emit-not-accepted`; one that listens to `answer` and subscribes only to `work` gets `listen-not-accepted`. Add the event to `.events([...])` or wire the agent to a channel that carries it.
> - **Forgetting `publishTo`.** `registerAgent(worker).subscribe(work)` only logs `agent-publishes-nowhere`. The run still ends on `answer` (`endsOn`), but `answer` lands on no channel, so the stream on `client` only receives `m4trix:run.completed`.
> - **Reading a quiet check as a correct one.** Subscribe `worker` to `mainChannel` instead of `work` and it never runs, yet setup reports nothing: `auditLog` listens to everything on `work`, so `task` counts as consumed, and `main` counts as fed from outside. Without `auditLog`, setup warns `unconsumed-event`.

## What next

| Goal | Page |
| --- | --- |
| Define the events channels carry | [Events](https://m4trix.dev/docs/concepts/events.md) |
| Declare what agents listen to and emit | [Agents](https://m4trix.dev/docs/concepts/agents.md) |
| Wire agents and channels into a network | [Networks](https://m4trix.dev/docs/concepts/networks.md) |
| Fan out, chain agents, wait for replies | [Patterns](https://m4trix.dev/docs/guides/patterns.md) |
| Stream a channel to the browser | [Streaming](https://m4trix.dev/docs/guides/streaming.md) |
| End runs and handle `m4trix:*` events | [Run Lifecycle](https://m4trix.dev/docs/api-reference/io-adapters.md#run-lifecycle) |
| Look up `.events()` and `.proxy()` | [Channel API](https://m4trix.dev/docs/api-reference/channel-api.md) |
| Read every wiring-check code | [Wiring check](https://m4trix.dev/docs/api-reference/agent-network.md#wiring-check) |
