AgentChannel
AgentChannel is the typed event bus that every agent exposes. It is the only way plugins and user code communicate with the agent and with each other.
Interface
ts
interface AgentChannel {
emit: <K extends string>(
channel: K,
event: K extends keyof AgentCustomEvent ? AgentCustomEvent[K] : unknown,
options?: { save?: boolean },
) => MaybePromise<void>
subscribe: <K extends string>(
channel: K,
listener: K extends keyof AgentCustomEvent
? AgentEventListener<AgentCustomEvent[K]>
: AgentEventListener,
) => () => void
}emit(channel, event, options?)— emits an event on a named channel.subscribe(channel, listener)— registers a listener. Returns an unsubscribe function.
The 'apeira' channel
The built-in channel 'apeira' carries all core lifecycle and runner forwarding events. Every event includes a turnId.
ts
import { createAgent } from '@apeira/core'
import { responses } from '@apeira/core/responses'
const agent = createAgent({
instructions: 'You are a helpful assistant.',
runner: responses({
apiKey: process.env.OPENAI_API_KEY,
baseURL: 'https://api.openai.com/v1/',
model: 'gpt-5.5',
}),
})
agent.subscribe('apeira', (event) => {
console.log(event.turnId, event.type)
})Persisting events
Pass { save: true } to persist the event to storage as an event entry. This is how the core turn lifecycle events are recorded.
ts
import { createAgent } from '@apeira/core'
import { responses } from '@apeira/core/responses'
const agent = createAgent({
instructions: 'You are a helpful assistant.',
runner: responses({
apiKey: process.env.OPENAI_API_KEY,
baseURL: 'https://api.openai.com/v1/',
model: 'gpt-5.5',
}),
})
await agent.emit('apeira', {
turnId: crypto.randomUUID(),
type: 'agent.reset',
}, { save: true })Listener errors are silently ignored so that one broken subscriber cannot break event delivery to others.
Typed custom channels
Plugins can declare custom channels by extending AgentCustomEvent. Once declared, subscribe() infers the event type automatically.
ts
import type { AGUIEvent } from '@ag-ui/core'
declare module '@apeira/core' {
interface AgentCustomEvent {
'ag-ui': AGUIEvent
}
}Custom channels are also how plugins talk to each other during init():
ts
const pluginA = {
init: (agent) => {
agent.subscribe('custom-channel', (event) => {
// handle event from plugin-b
})
},
name: 'plugin-a',
}
const pluginB = {
init: (agent) => {
agent.emit('custom-channel', { ok: true })
},
name: 'plugin-b',
}Design notes
- Channels are stored in a
Map<string, Set<AgentEventListener>>. emit()resolves listeners and optional persistence in parallel withPromise.all.- Subscriptions are removed lazily: when the last listener unsubscribes, the channel entry is deleted.
