Skip to main content

Overview

Instructor dispatches events at every significant stage of its execution. You can listen to these events for logging, monitoring, debugging, or custom processing. All event classes extend Cognesy\Events\Event.

Listening to Events

Targeted Listeners

Use onEvent() on the StructuredOutputRuntime to listen for a specific event type:

Wiretap (All Events)

Use wiretap() to receive every event dispatched by Instructor. This is useful for debugging or comprehensive logging:

Practical Example

Event Categories

Events are organized into namespaces that correspond to the processing stage:

High-Level Events (Events\StructuredOutput)

Response-model Events (Events\ResponseModel)

Attempt Events (Events\Attempt)

Response Events (Events\Response)

Extraction Events (Events\Extraction)

Streaming Events (Events\Streaming)

Listener Gating

Some events are not constructed at all when nothing is listening for them. Building the payload is not free: the structured-output lifecycle events each carry a telemetry envelope that serialises the entire conversation, and StructuredOutputResponseGenerated additionally normalizes the result value, runs strlen() over the content and reasoning content, and walks the tool calls. StructuredOutputResponseUpdated pays a smaller version of that once per streamed emission. The rule is Cognesy\Events\Support\ListenerGate, shared with Polyglot — see the “Listener Gating” section of packages/polyglot/docs/internals/events.md for the two properties that matter to anyone writing a dispatcher.

What is gated

Everything else is dispatched unconditionally. Fail-open is contractual. A dispatcher that does not implement Cognesy\Events\Contracts\CanCheckListeners cannot report its listeners, so it is assumed to listen and receives every event. No dispatcher ever loses an event to this optimisation.

When the gate is resolved

StructuredOutputEventProjector resolves its gates once, at construction, and a projector is built per execution — by StructuredOutputExecutionSession for the sync path and inside StructuredOutputStream::__construct() for the streaming path. Both are constructed after any onEvent() or wiretap() call on the runtime, so no listener can be registered and then missed. StructuredOutputRuntime is the exception: it builds its projector per request, inside create(). A runtime is long-lived and onEvent() mutates it, so gates resolved in its constructor would silently drop StructuredOutputRequestReceived for every caller who registers a listener the way the API invites. One hasListenersFor() per request costs nothing next to the envelope it guards. DefaultRetryPolicy also builds a projector, but the retry and recovery events it emits are dispatched unconditionally — only the four events in the table above are gated. Only the payload and the dispatch are conditional. Timing, attempt numbering and execution state are not.

Event Methods

Every event inherits the following convenience methods from Cognesy\Events\Event: Events carry a $logLevel property (PSR log level) and a $data payload. The print() method respects a configurable log-level threshold. Instructor event payloads are normalized arrays. Structured-output lifecycle events also expose correlation fields such as requestId, executionId, attemptId, phase, and phaseId where applicable.