Logging
Structured log/slog through ctxscope, pulled from the context. The library never takes a logger parameter and never installs a global. Whatever you configured on slog.Default() at startup is where it writes, and any scope attributes you set (request_id, user_id) ride along on every line the engine emits.
Levels
It shuts up on purpose.
| Level | What it carries |
|---|---|
| DEBUG | The per-round and per-tool detail. |
| INFO | Exactly two events: a transcript compacted to fit the budget, and a stream that only survived because a retry saved it. |
| WARN | Recoverable weirdness: a malformed tool call dropped at ingest, a decision matching no pending call, an unknown tool requested, the retry loop giving up. |
| ERROR | Things that actually broke. |
Both INFO events are INFO because you want them in a production log without turning DEBUG on, and neither is an error. Compaction is INFO rather than WARN deliberately. It happens routinely on any long conversation, and a WARN that fires on the normal path is exactly how a log turns into noise nobody reads.
Reasons
Every decision the engine makes carries a reason field with a stable, greppable value: token_budget_exceeded, tool_call_denied, max_attempts_exhausted, finish_reason_unmapped, and so on. They’re exported as elelem.LogReason* constants in log_reason.go, so your alerting matches the same symbol the engine emits instead of a string copied out of a log line.
| Area | Values |
|---|---|
| Tool lifecycle | tool_call_denied, tool_call_not_pending, tool_not_in_toolset, tool_arguments_invalid_json, tool_has_no_handler, tool_execution_panicked, tool_handler_panicked, tool_result_removed_by_hook, injection_role_invalid, empty_assistant_turn, tool_arguments_size_capped, tool_call_index_reused, tool_call_id_missing, tool_call_id_duplicate, on_tool_result_hook_failed, tool_calls_per_round_capped |
| Transcript and budget | token_budget_exceeded, budget_unreachable_after_compaction, context_ceiling_near, unpaired_tool_calls, on_error_hook_failed, no_droppable_unit |
| Retry decorator, why it stopped | error_not_retryable, already_streamed, max_attempts_exhausted |
| Drivers | stream_read_failed, finish_reason_unmapped, provider_reasoning_undecodable, provider_reasoning_mismatch |