# How the Events Package Handles Agent-Related Events in Prime Agent

> Explore how the prime-agent events package uses a typed publish-subscribe system and AgentEvent discriminated unions to manage agent lifecycle events effectively. Learn about listener subscriptions and event stream emission.

- Repository: [Prime Intellect/prime-agent](https://github.com/PrimeIntellect-ai/prime-agent)
- Tags: internals
- Published: 2026-08-09

---

**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`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/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`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/agent/src/agent.ts), this method registers listeners in an internal `Set<(event, signal) => void>`. It returns an unsubscribe function and, as verified by tests at lines 108-109 of [`packages/agent/test/agent.test.ts`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/agent/test/agent.test.ts), does not emit any initial event upon subscription.

- **Agent.processEvents()**: The internal dispatcher in [`packages/agent/src/agent.ts`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/agent/src/agent.ts) that 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.ts`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/agent/src/agent-loop.ts) that wraps the event flow. The stream created by `createAgentStream()` is typed as `EventStream<AgentEvent, AgentMessage[]>` and terminates when an `agent_end` event is detected.

- **AgentLoop**: Orchestrates the full agent run in [`packages/agent/src/agent-loop.ts`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/agent/src/agent-loop.ts), instantiating the stream and invoking the `emit` callback 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`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/agent/test/e2e.test.ts).

1. **agent_start**: Emitted when the `run()` method begins execution.
2. **turn_start**: Signals the beginning of a new interaction cycle.
3. **message_start**: Fires when the assistant begins generating a response, followed by optional `message_update` events for streaming content, and concluding with `message_end`.
4. **tool_execution_start**: Emitted when the model returns a tool call, followed by `tool_execution_end` after the tool completes.
5. **toolResult message**: Tool outputs are wrapped in synthetic messages emitting `message_start` and `message_end` with `role: "toolResult"`.
6. **turn_end**: Marks completion of a full response cycle, including any tool results.
7. **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:

```typescript
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`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/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_end` event 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:

```typescript
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/agent` and centers on the `AgentEvent` discriminated union defined in [`src/types.ts`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/src/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.ts`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/agent-loop.ts) drives the lifecycle, terminating on `agent_end`.
- Event ordering follows a strict sequence from `agent_start` through `turn_start`, message events, tool execution events, and finally `agent_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`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/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`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/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`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/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.