Skip to content

Events API

Agent.run() yields event objects. Use isinstance(event, TextChunkEvent) or compare event.type to an EventType member; EventType is not a string enum. Usage is attached to events, not emitted as a separate "usage" event. See Events for consumption patterns.

TextDoneEvent completes a model response; DoneEvent completes the agent interaction. Do not display both text chunks and final text as if they were different answers. Tool failures use ToolResultEvent.error; generation failures use ErrorEvent.message. Initialization and extension failures can also raise exceptions instead of emitting an error event.

Streaming tool generation emits ToolCallProgressEvent before an executable ToolCallEvent is available. The preview contains a cumulative arguments_text snapshot (at most 16,384 characters), arguments_truncated, a unique generation_id, and a stable output index. Call IDs and names may arrive after the first snapshot. Use the generation/index pair to update one preview rather than appending every snapshot. The final call carries the same identifiers in extra and retains its complete, validated arguments.

Progress status is streaming, ready after protocol validation, or abandoned after a failed attempt. A preview is never executed or stored as conversation history. A cancelled consumer may close before an abandonment event can be sent; clients must stop loading indicators when their enclosing run ends. A ready call can still be queued behind another call or require approval.

ToolExecutionStartedEvent identifies the next tool the agent invokes. The tool may request approval as part of that invocation; this event is not proof that side effects have occurred. ToolResultEvent remains the execution outcome.

When the agent exhausts its configured model/tool rounds, it emits ErrorEvent(code="MAX_TOOL_ROUNDS", recoverable=False) followed by DoneEvent(finish_reason=FinishReason.UNKNOWN). The existing error message and usage totals are retained. For a positive plain integer limit, the error includes extra={"max_tool_rounds": 30} (using the actual configured value); booleans and other values are never coerced into this metadata. This is a round budget, not an aggregate tool-call count: one round can contain several calls. No extra model turn or automatic retry is added when the budget is exhausted.

The web UI describes this known error as a round limit using fixed safe text. A native child that reaches its limit remains failed, retains its history, and reports a fixed budget explanation to its parent. These diagnostics do not change the library, Harness, or child round limits or their cleanup behavior.

If a descendant finishes while its immediate parent is stopping or cancelled, the Harness keeps the descendant's TaskCompleted outcome and withholds delivery. It emits the host-owned TaskDeliveryWarning subtype of ErrorEvent, with code TASK_DELIVERY_SKIPPED and recoverable=True. Recoverable refers to the root run: it may continue or delegate replacement work, but descendant data is never routed directly to Main and the failed/cancelled child outcomes are not changed.

This diagnostic carries task_id, task_name, parent_task_id, parent_session_id, child_session_id, depth, activation, and followup. The web client retains only these owned scope fields and fixed safe text, so a child warning does not clear Main's tool preview or mark its run failed. Ordinary provider errors cannot gain this scope by copying the code or adding metadata. Missing or mismatched parent identities remain fatal errors.

Built-in text providers attach safe diagnostics to caught generation failures in ErrorEvent.extra["transport"]. Existing code, message, and recoverable values keep their meaning; diagnostics do not enable retries or change deadlines. Authentication, model-discovery, and other failures may omit this metadata. Native Codex also omits it for early request validation and non-200 HTTP replies, which keep their existing specific error codes.

Field Meaning
category A fixed classification: dns, connect, tls, connect_timeout, read_timeout, timeout, connection_lost, connection, response_payload, decode, invalid_data, protocol, http, or unknown.
phase Native Codex reports request before response headers and response after headers arrive. The shared provider path reports unknown because its outer error handler does not observe that boundary.
generation_elapsed_ms Elapsed time for the generation invocation through failure, including retries and backoff; this is not an individual HTTP request duration.
model_call_id The current observation scope's existing call ID, when available and valid. It is not a newly minted HTTP request ID; direct provider calls can inherit the current context or omit it.

A generic timeout remains timeout; it does not prove a read, connect, or total request deadline expired. Generic ValueError remains invalid_data, rather than being misreported as decoding. Diagnostics contain no exception messages, URLs, headers, response/request bodies, or authentication values. Raw HTTP logging and the opt-in payload observer remain separate facilities. Library consumers and ngn --json retain the nested metadata. The normal web error view and other human-readable clients continue to show their existing safe messages.

nagents.EventType

Bases: Enum

Types of events that can be emitted during generation.

Source code in src/nagents/events/types.py
class EventType(Enum):
    """Types of events that can be emitted during generation."""

    # Streaming events
    TEXT_CHUNK = "text_chunk"  # Partial text during streaming
    REASONING_CHUNK = "reasoning_chunk"  # Partial reasoning/thinking during streaming

    # Audio events (Realtime speech-to-speech)
    AUDIO_CHUNK = "audio_chunk"  # Base64-encoded output audio delta
    AUDIO_TRANSCRIPT_DELTA = "audio_transcript_delta"  # Partial output audio transcript
    INPUT_TRANSCRIPT_DELTA = "input_transcript_delta"  # Partial input audio transcript
    INPUT_TRANSCRIPT_COMPLETED = "input_transcript_completed"  # Final input transcript
    SPEECH_STARTED = "speech_started"  # User started speaking (VAD)
    SPEECH_STOPPED = "speech_stopped"  # User stopped speaking (VAD)

    # Realtime session lifecycle events
    REALTIME_SESSION_CREATED = "realtime_session_created"  # Server session ready
    REALTIME_SESSION_UPDATED = "realtime_session_updated"  # Session config updated
    RESPONSE_CREATED = "response_created"  # Model started a new response
    RESPONSE_CANCELLED = "response_cancelled"  # In-progress response was cancelled
    REALTIME_RATE_LIMITS = "realtime_rate_limits"  # Rate limit / usage counters
    REALTIME_RAW = "realtime_raw"  # Unmapped server event (raw passthrough)
    LIVE = "live"  # GPT-Live lifecycle/control event, not a Realtime event.

    # Completion events
    TEXT_DONE = "text_done"  # Final complete text

    # Tool events
    TOOL_CALL = "tool_call"  # Model wants to call a tool
    TOOL_CALL_PROGRESS = "tool_call_progress"  # Non-executable streamed preview
    TOOL_EXECUTION_STARTED = "tool_execution_started"  # Invocation, including any approval gate
    TOOL_RESULT = "tool_result"  # Tool execution completed

    # Compaction events
    COMPACTION_STARTED = "compaction_started"  # Context compaction started
    COMPACTION_DONE = "compaction_done"  # Context compaction completed

    # Meta events
    ERROR = "error"  # Error occurred
    RATE_LIMIT = "rate_limit"  # Rate limit hit, retrying
    DONE = "done"  # Generation complete

nagents.Event dataclass

Base event with common fields.

Source code in src/nagents/events/types.py
@dataclass
class Event:
    """Base event with common fields."""

    type: EventType
    timestamp: datetime = field(default_factory=datetime.now)
    usage: Usage = field(default_factory=Usage)
    extra: dict[str, Any] = field(default_factory=dict)

nagents.TextChunkEvent dataclass

Bases: Event

Streaming text chunk.

Source code in src/nagents/events/types.py
@dataclass
class TextChunkEvent(Event):
    """Streaming text chunk."""

    type: EventType = field(default=EventType.TEXT_CHUNK)
    chunk: str = ""

nagents.ReasoningChunkEvent dataclass

Bases: Event

Streaming reasoning/thinking chunk (e.g., from models with chain-of-thought).

Source code in src/nagents/events/types.py
@dataclass
class ReasoningChunkEvent(Event):
    """Streaming reasoning/thinking chunk (e.g., from models with chain-of-thought)."""

    type: EventType = field(default=EventType.REASONING_CHUNK)
    chunk: str = ""

nagents.TextDoneEvent dataclass

Bases: Event

Complete text response.

Source code in src/nagents/events/types.py
@dataclass
class TextDoneEvent(Event):
    """Complete text response."""

    type: EventType = field(default=EventType.TEXT_DONE)
    text: str = ""
    finish_reason: FinishReason = FinishReason.STOP

nagents.ToolCallEvent dataclass

Bases: Event

Model requesting a tool call.

Source code in src/nagents/events/types.py
@dataclass
class ToolCallEvent(Event):
    """Model requesting a tool call."""

    type: EventType = field(default=EventType.TOOL_CALL)
    id: str = ""
    name: str = ""
    arguments: dict[str, Any] = field(default_factory=dict)
    finish_reason: FinishReason = FinishReason.TOOL_CALLS
    # Provider-specific metadata (e.g., Gemini thought signatures)
    metadata: dict[str, str] = field(default_factory=dict)

nagents.ToolCallProgressEvent dataclass

Bases: Event

Bounded cumulative preview; never an instruction to execute a tool.

A generation ID belongs to one provider attempt. Index remains stable even when the provider supplies the call ID or function name in later chunks. Ready means protocol validation passed, not that the tool has executed.

Source code in src/nagents/events/types.py
@dataclass
class ToolCallProgressEvent(Event):
    """Bounded cumulative preview; never an instruction to execute a tool.

    A generation ID belongs to one provider attempt. Index remains stable even
    when the provider supplies the call ID or function name in later chunks.
    Ready means protocol validation passed, not that the tool has executed.
    """

    type: EventType = field(default=EventType.TOOL_CALL_PROGRESS)
    generation_id: str = ""
    index: int = 0
    id: str = ""
    name: str = ""
    arguments_text: str = ""
    arguments_truncated: bool = False
    status: Literal["streaming", "ready", "abandoned"] = "streaming"

nagents.ToolExecutionStartedEvent dataclass

Bases: Event

The agent is invoking a tool; its callable may still request approval.

Source code in src/nagents/events/types.py
@dataclass
class ToolExecutionStartedEvent(Event):
    """The agent is invoking a tool; its callable may still request approval."""

    type: EventType = field(default=EventType.TOOL_EXECUTION_STARTED)
    id: str = ""
    name: str = ""

nagents.ToolResultEvent dataclass

Bases: Event

Result from tool execution.

Source code in src/nagents/events/types.py
@dataclass
class ToolResultEvent(Event):
    """Result from tool execution."""

    type: EventType = field(default=EventType.TOOL_RESULT)
    id: str = ""
    name: str = ""
    result: ToolResultType = None
    error: str | None = None
    duration_ms: float = 0.0

nagents.ErrorEvent dataclass

Bases: Event

Error during generation.

Source code in src/nagents/events/types.py
@dataclass
class ErrorEvent(Event):
    """Error during generation."""

    type: EventType = field(default=EventType.ERROR)
    message: str = ""
    code: str | None = None
    recoverable: bool = False

nagents.RateLimitEvent dataclass

Bases: Event

Rate limit hit during generation, automatic retry in progress.

Emitted each time a retryable HTTP error (429 or 5xx) is encountered and the provider will retry after a delay.

Source code in src/nagents/events/types.py
@dataclass
class RateLimitEvent(Event):
    """Rate limit hit during generation, automatic retry in progress.

    Emitted each time a retryable HTTP error (429 or 5xx) is encountered
    and the provider will retry after a delay.
    """

    type: EventType = field(default=EventType.RATE_LIMIT)
    attempt: int = 0  # Current retry attempt (1-based)
    max_retries: int = 0  # Maximum retry attempts configured
    retry_after: float = 0.0  # Delay in seconds before retry
    status_code: int = 0  # HTTP status code (429, 500, etc.)
    message: str = ""  # Human-readable message
    rate_limit_info: dict[str, str] = field(default_factory=dict)  # x-ratelimit-* headers

nagents.DoneEvent dataclass

Bases: Event

Generation complete.

Source code in src/nagents/events/types.py
@dataclass
class DoneEvent(Event):
    """Generation complete."""

    type: EventType = field(default=EventType.DONE)
    final_text: str = ""
    session_id: str | None = None
    finish_reason: FinishReason = FinishReason.STOP

nagents.FinishReason

Bases: Enum

Reason why the model stopped generating.

Source code in src/nagents/events/types.py
class FinishReason(Enum):
    """Reason why the model stopped generating."""

    STOP = "stop"  # Natural stop or stop sequence
    TOOL_CALLS = "tool_calls"  # Model called tools
    LENGTH = "length"  # Max tokens reached
    CONTENT_FILTER = "content_filter"  # Content policy violation (Azure)
    NULL = "null"  # Still generating (streaming)
    UNKNOWN = "unknown"  # Fallback/unknown reason

nagents.Usage dataclass

Token usage statistics with current generation and session totals.

Attributes:

Name Type Description
prompt_tokens int

Input tokens for current generation

completion_tokens int

Output tokens for current generation

total_tokens int

Total tokens for current generation

cached_tokens int

Cached prompt tokens (OpenAI)

audio_tokens int

Audio input/output tokens

reasoning_tokens int

Tokens used for reasoning/thinking (chain-of-thought models)

session TokenUsage | None

Cumulative token usage across the entire session/run

Source code in src/nagents/events/types.py
@dataclass
class Usage:
    """
    Token usage statistics with current generation and session totals.

    Attributes:
        prompt_tokens: Input tokens for current generation
        completion_tokens: Output tokens for current generation
        total_tokens: Total tokens for current generation
        cached_tokens: Cached prompt tokens (OpenAI)
        audio_tokens: Audio input/output tokens
        reasoning_tokens: Tokens used for reasoning/thinking (chain-of-thought models)
        session: Cumulative token usage across the entire session/run
    """

    prompt_tokens: int = 0
    completion_tokens: int = 0
    total_tokens: int = 0
    cached_tokens: int = 0
    audio_tokens: int = 0
    reasoning_tokens: int = 0
    session: TokenUsage | None = None

    def has_usage(self) -> bool:
        """Check if this usage has any actual token counts."""
        return self.prompt_tokens > 0 or self.completion_tokens > 0 or self.total_tokens > 0

has_usage()

Check if this usage has any actual token counts.

Source code in src/nagents/events/types.py
def has_usage(self) -> bool:
    """Check if this usage has any actual token counts."""
    return self.prompt_tokens > 0 or self.completion_tokens > 0 or self.total_tokens > 0

nagents.TokenUsage dataclass

Token counts for a generation or session.

Source code in src/nagents/events/types.py
@dataclass
class TokenUsage:
    """Token counts for a generation or session."""

    prompt_tokens: int = 0
    completion_tokens: int = 0
    total_tokens: int = 0

nagents.CompactionStartedEvent dataclass

Bases: Event

Event emitted when compaction begins.

Attributes:

Name Type Description
trigger str | None

Optional strategy trigger description

message_count int

Number of messages before compaction

estimated_tokens int

Estimated token count before compaction

session_id str

Original session ID

compaction_session_id str

Session ID used for compaction

Source code in src/nagents/events/types.py
@dataclass
class CompactionStartedEvent(Event):
    """Event emitted when compaction begins.

    Attributes:
        trigger: Optional strategy trigger description
        message_count: Number of messages before compaction
        estimated_tokens: Estimated token count before compaction
        session_id: Original session ID
        compaction_session_id: Session ID used for compaction
    """

    type: EventType = field(default=EventType.COMPACTION_STARTED)
    trigger: str | None = None
    message_count: int = 0
    estimated_tokens: int = 0
    session_id: str = ""
    compaction_session_id: str = ""

nagents.CompactionDoneEvent dataclass

Bases: Event

Event emitted when compaction completes.

Attributes:

Name Type Description
original_message_count int

Number of messages before compaction

original_token_count int

Estimated tokens before compaction

new_message_count int

Number of messages after compaction

summary_tokens int

Estimated tokens in the summary

summary_text str

Full summary text

trigger str | None

The trigger that caused compaction

compactor_used str

Strategy name used for compaction

session_id str

Original session ID

compaction_session_id str

Session ID used for compaction

Source code in src/nagents/events/types.py
@dataclass
class CompactionDoneEvent(Event):
    """Event emitted when compaction completes.

    Attributes:
        original_message_count: Number of messages before compaction
        original_token_count: Estimated tokens before compaction
        new_message_count: Number of messages after compaction
        summary_tokens: Estimated tokens in the summary
        summary_text: Full summary text
        trigger: The trigger that caused compaction
        compactor_used: Strategy name used for compaction
        session_id: Original session ID
        compaction_session_id: Session ID used for compaction
    """

    type: EventType = field(default=EventType.COMPACTION_DONE)
    original_message_count: int = 0
    original_token_count: int = 0
    new_message_count: int = 0
    summary_tokens: int = 0
    summary_text: str = ""
    trigger: str | None = None
    compactor_used: str = ""
    session_id: str = ""
    compaction_session_id: str = ""