AgentQueue
AgentQueue is the turn scheduler inside every agent. It serializes top-level turns, drains queued input into the active turn, and handles aborts and resets.
Interface
ts
interface AgentQueue {
abort: (reason?: unknown) => void
clear: () => Promise<void>
getActiveTurnId: () => string | undefined
interrupt: (reason?: unknown) => MaybePromise<string | undefined>
isIdle: () => boolean
send: (item: AgentInput, options?: AgentSignalOptions) => string
wait: (options?: AgentSignalOptions) => Promise<void>
}Turn lifecycle
When you call send(), the queue either:
- Enqueues a new top-level turn if no turn is active.
- Appends the input to the active turn's pending input buffer and emits
turn.input_queued.
The active turn loop:
- Emits
turn.start. - Calls the runner for one model step with the current input and cumulative
stepNumber. - Executes the returned
toolCallsand appends their outputs. - If the model needs a tool follow-up or new input was queued while the runner was running, drains that input (
turn.input_drained) and starts the next step. - Emits
turn.doneon success,turn.failedon error, orturn.abortedif aborted.
The queue owns this loop so tool follow-ups and input submitted with send() share the same boundary. Built-in runners only execute one model request; the Agent layer handles their returned toolCalls uniformly.
Queueing semantics
Top-level turns on the same agent run one at a time. Different agents run concurrently.
ts
import { createAgent, run } 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',
}),
})
const input = { content: 'Hello.', role: 'user', type: 'message' } as const
const first = run(agent, input)
const second = run(agent, input) // waits for firstrun() builds a ReadableStream around send() and subscribe(), filtering events to the submitted turn.
Abort and interrupt
abort(reason)aborts the active turn without recording a boundary.interrupt(reason)aborts the active turn and lets the agent record a<turn_aborted>boundary in storage.clear()aborts the active turn and drops all queued turns and pending input. It is used byreset().wait()returns a promise that resolves when the queue becomes idle.
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.wait() // resolves when no turn is runningDesign notes
- Pending top-level turns are stored in a
yocto-queue; each turn also owns its pending input buffer. - If the active turn has already been aborted,
send()targets the next pending turn when one exists. send()is synchronous and returns immediately with acrypto.randomUUID()turn id.- The queue uses an internal
pumpingflag so concurrentsend()calls share the same pump loop.
