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 extendCognesy\Events\Event.
Listening to Events
Targeted Listeners
UseonEvent() on the StructuredOutputRuntime to listen for a specific event type:
Wiretap (All Events)
Usewiretap() 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, andStructuredOutputResponseGenerated 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 fromCognesy\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.