Skip to main content

AgentEvent reference

AgentEvent is the streaming event type yielded by AgentLoop.run(). Every surface that renders a turn — CLI, TUI, web UI, channel adapters — consumes this stream and renders the variants it cares about.

Source

Defined in packages/core/src/agent-loop.ts. Re-exported from @ethosagent/core as AgentEvent.

Signature

import type { AgentEvent } from '@ethosagent/core';

export type AgentEvent =
| { type: 'text_delta'; text: string }
| { type: 'thinking_delta'; thinking: string }
| { type: 'tool_start'; toolCallId: string; toolName: string; args: unknown }
| {
type: 'tool_progress';
toolName: string;
message: string;
percent?: number;
audience: 'internal' | 'user';
}
| {
type: 'tool_end';
toolCallId: string;
toolName: string;
ok: boolean;
durationMs: number;
audience?: 'internal' | 'user';
result?: string;
}
| { type: 'usage'; inputTokens: number; outputTokens: number; estimatedCostUsd: number }
| { type: 'error'; error: string; code: string }
| { type: 'done'; text: string; turnCount: number }
| { type: 'context_meta'; data: Record<string, unknown> }
| {
type: 'run_start';
provider: string;
model: string;
source: 'team-coordinator' | 'team-personality' | 'personality' | 'global';
};

Variants

typeWhen emittedPayload fields
run_startOnce at the start of each turn, before any LLM call.provider, model, source
context_metaOnce after context injectors run.data
text_deltaEach token of assistant text from the LLM stream.text
thinking_deltaEach token of extended-thinking output (when the provider supports it).thinking
tool_startOnce per tool call, immediately before execution.toolCallId, toolName, args
tool_progressZero or more times per tool call while it runs.toolName, message, percent?, audience
tool_endOnce per tool call after execution returns or throws.toolCallId, toolName, ok, durationMs, audience?, result?
usageOnce per LLM call, after the response completes.inputTokens, outputTokens, estimatedCostUsd
errorWhen a turn aborts (uncaught LLM error, hook failure, abort signal).error, code
doneOnce at the end of a successful turn. Always the last event.text, turnCount

run_start

Emitted before any LLM call so consumers can surface the effective provider and model for the turn. source indicates which routing rule selected the model — useful for telemetry that wants to attribute cost to the team or personality layer.

context_meta

Emitted after every ContextInjector writes to PromptContext.meta. The data blob is the merged map; surfaces that show injector provenance (e.g. the web drawer) consume this. Skip if you do not display injector telemetry.

text_delta

Each chunk of assistant text streamed from the LLM. Concatenating every text_delta.text in order rebuilds the assistant response. Whitespace is preserved as-is; do not normalise.

thinking_delta

Extended-thinking tokens. Anthropic models with thinkingBudget set produce these; OpenAI-compatible providers do not. Surface them in a separate visual lane (collapsed by default) or drop them entirely.

tool_start

Fires once per tool call, immediately before Tool.execute runs. toolCallId is stable for the whole call — match it against tool_progress and tool_end events to assemble per-call UI.

tool_progress

Emitted by tools via ToolContext.emit({ type: 'progress', ... }). The audience field is the audience boundary gate.

audienceWho renders it
'internal' (default)Framework only — logs, telemetry, dev TUI. Channel adapters and the CLI chat REPL MUST drop it.
'user'Explicit per-event opt-in by the tool author. Surfaced in user-facing streams (CLI, channel messages).

The framework never opts in for the tool. Budget-warning events emitted by AgentLoop itself use audience: 'user'.

tool_end

Fires once per tool call. ok: true indicates the tool returned a success ToolResult; ok: false indicates an error (including hook-rejected calls). result carries the success value or error message — optional so consumers that only render status chips can ignore it. The audience boundary applies to success cases only; failures always render.

usage

Per-LLM-call token + cost accounting. Emitted after every LLM round-trip — a turn that triggers tool calls emits a usage event for each round. Sum across the turn to get total cost.

error

Terminal error. After this fires the turn is over; no further events are emitted. code is a stable identifier (abort, llm_timeout, provider_unauthorized, tool_unknown, etc.) safe for switch-statement dispatch.

done

Always the last event of a successful turn. text is the full concatenated assistant response (equivalent to joining all text_delta.text values). turnCount is the cumulative count for the session.

Notes

  • The stream always ends with exactly one of done or error. Consumers must handle both.
  • tool_progress events are unordered with respect to other events from the same tool — only tool_start (first) and tool_end (last) are guaranteed bracketing markers per toolCallId.
  • text_delta and thinking_delta may interleave with tool_start / tool_end when the LLM streams tool calls inline with text.
  • audience: 'internal' is the default when absent. Surface code that fans events out to humans must filter explicitly.
  • tool_end.result is best-effort. CLI status chips and telemetry collectors that only need ok/duration may ignore it; expandable UIs (web drawer) use it to avoid a follow-up history fetch.
  • A turn may include multiple LLM round-trips when the LLM calls tools. Each round emits its own usage event; sum across the turn for total cost.
  • error is terminal — no done follows. Consumers MUST close UI affordances (spinners, "agent thinking…") on both terminators.

Event ordering example

A turn where the LLM calls one tool then produces a final answer typically streams events in this order:

run_start (provider, model, source)
context_meta (injector metadata)
text_delta * N (assistant reasoning text)
tool_start (toolCallId=t1, toolName=read_file, args={...})
tool_progress * M (audience='internal' by default)
tool_end (toolCallId=t1, ok=true, durationMs=120)
usage (inputTokens/outputTokens for round 1)
text_delta * K (assistant final answer)
usage (inputTokens/outputTokens for round 2)
done (text, turnCount)

Channel adapters that only render user-visible output filter to text_delta plus tool_end (when ok and audience === 'user') plus error / done.

Used by

ConsumerRole
apps/ethos/src/commands/chat.tsCLI REPL — renders text deltas, tool chips, and the final response.
apps/tui/src/components/App.tsxInteractive TUI — full event-stream rendering.
apps/web/src/lib/chat-reducer.tsWeb UI — reducer that maps events to message state.
apps/web/src/lib/drawer-reducer.tsWeb UI — tool-call drawer that consumes tool_start / tool_end / context_meta.
apps/vscode-extension/src/bridge.tsVS Code bridge — forwards events to the webview.
apps/acp-server/src/index.tsACP server — wire-protocol mapping of events.
extensions/gateway/src/Channel gateway — filters by audience before dispatching to adapters.
extensions/eval-harness/src/runner.tsEval runner — collects events to score turns.

See also