How the Events Package Handles Agent-Related Events in Prime Agent
The events system in prime-agent implements a typed publish-subscribe architecture centered on the AgentEvent discriminated union, where the Agent class manages listener subscriptions and the AgentLoop drives lifecycle event emission through an EventStream async iterator.
The PrimeIntellect-ai/prime-agent repository provides a lightweight event handling mechanism to track every phase of agent execution. Rather than existing as a standalone module, the events architecture lives within the packages/agent directory and enables deterministic observability into the agent's runtime through typed events like agent_start, turn_start, and tool_execution_end.
Core Event Architecture
The system consists of five primary components working together to provide type-safe event delivery:
-
AgentEvent Type: Defined in
packages/agent/src/types.ts, this discriminated union declares every possible event shape and payload, including metadata for agent lifecycle, message streaming, and tool execution states. -
Agent.subscribe(): Located in
packages/agent/src/agent.ts, this method registers listeners in an internalSet<(event, signal) => void>. It returns an unsubscribe function and, as verified by tests at lines 108-109 ofpackages/agent/test/agent.test.ts, does not emit any initial event upon subscription. -
Agent.processEvents(): The internal dispatcher in
packages/agent/src/agent.tsthat iterates over all registered listeners and awaits each callback, ensuring ordered delivery even when listeners return promises. -
EventStream: A generic async iterator implemented in
packages/agent/src/agent-loop.tsthat wraps the event flow. The stream created bycreateAgentStream()is typed asEventStream<AgentEvent, AgentMessage[]>and terminates when anagent_endevent is detected. -
AgentLoop: Orchestrates the full agent run in
packages/agent/src/agent-loop.ts, instantiating the stream and invoking theemitcallback for every state transition.
Event Lifecycle and Flow
The AgentLoop emits events in a strict chronological sequence that mirrors the agent's execution phases. The ordering is enforced by the implementation and verified by end-to-end tests at lines 143-152 of packages/agent/test/e2e.test.ts.
- agent_start: Emitted when the
run()method begins execution. - turn_start: Signals the beginning of a new interaction cycle.
- message_start: Fires when the assistant begins generating a response, followed by optional
message_updateevents for streaming content, and concluding withmessage_end. - tool_execution_start: Emitted when the model returns a tool call, followed by
tool_execution_endafter the tool completes. - toolResult message: Tool outputs are wrapped in synthetic messages emitting
message_startandmessage_endwithrole: "toolResult". - turn_end: Marks completion of a full response cycle, including any tool results.
- agent_end: Final event indicating conversation termination, carrying the complete message history.
Subscribing to Agent Events
External code can monitor execution by subscribing to the Agent instance. The listener receives both the event payload and an AbortSignal for cancellation:
import { Agent } from "prime-agent";
const agent = new Agent({ /* configuration */ });
// Register a listener – it receives every event and an AbortSignal
const unsubscribe = agent.subscribe((event, signal) => {
console.log(`[${event.type}]`, event);
// Abort the entire run if specific conditions are met
if (event.type === "tool_execution_start" && event.toolName === "risky_operation") {
signal.abort();
}
});
// Later, remove the listener to prevent memory leaks
unsubscribe();
The listener sees complete payloads—for example, a message_end event contains the full AssistantMessage object with content and toolCalls arrays.
Internal Event Dispatch Mechanisms
All event emission funnels through a single AgentEventSink callback passed through the execution stack. In packages/agent/src/agent-loop.ts, helper functions construct specific event objects and pass them to this sink:
- emitToolExecutionEnd (lines 961-983): Constructs the
tool_execution_endevent after tool completion. - emitToolResultMessage (lines 983-995): Wraps tool outputs into message events.
The EventStream constructor accepts two critical predicates: a stop condition checking for event.type === "agent_end" and a result extractor that returns event.messages when the stream terminates. This design allows consumers to use for await...of loops that automatically resolve when the agent finishes:
const stream = agent.run(userQuery);
for await (const event of stream) {
// Process events until agent_end automatically terminates the loop
updateUI(event);
}
Summary
- The events system resides in
packages/agentand centers on theAgentEventdiscriminated union defined insrc/types.ts. - Agent.subscribe() manages listeners in a
Set, returning an unsubscribe function; no events fire during subscription itself. - Agent.processEvents() dispatches events to all listeners sequentially, awaiting async callbacks.
- The EventStream async iterator in
agent-loop.tsdrives the lifecycle, terminating onagent_end. - Event ordering follows a strict sequence from
agent_startthroughturn_start, message events, tool execution events, and finallyagent_end.
Frequently Asked Questions
What is the AgentEvent type in prime-agent?
AgentEvent is a TypeScript discriminated union defined in packages/agent/src/types.ts that describes every possible event in the agent lifecycle. Each event variant includes a type discriminator (such as agent_start, message_end, or tool_execution_start) and a strongly-typed payload containing relevant metadata like message content, tool names, or execution results.
How do I listen to agent events without modifying the core code?
Import the Agent class from the package and call agent.subscribe(), passing a callback function that receives the event and an AbortSignal. This callback fires for every lifecycle event without requiring changes to the internal source. Store the returned unsubscribe function to clean up listeners when your component unmounts or monitoring ends.
What guarantees the order of event delivery?
The Agent.processEvents() method in packages/agent/src/agent.ts iterates over registered listeners in insertion order and awaits each callback before proceeding to the next. Because the AgentLoop emits events synchronously during state transitions and the dispatcher processes them sequentially, the system guarantees that events arrive in strict chronological order as verified by the e2e test suite.
When does the EventStream terminate?
The EventStream created in packages/agent/src/agent-loop.ts terminates when it encounters an event where event.type === "agent_end". This stop predicate is passed to the stream constructor and causes the async iterator to return { done: true }, automatically ending for await...of loops and resolving any pending stream consumers with the final message array.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →