Skip to main content

EngineEvent

Enum EngineEvent 

Source
pub enum EngineEvent {
Show 46 variants Log(String), LogLevel { level: String, message: String, }, StateUpdate(String, Value), WorkflowStart(usize), TaskStart(String, Option<String>), TaskPrompt(String, String), TaskEnd { task: String, on_error_label: Option<String>, value: Value, value_type: Option<TypeRef>, duration: Duration, attempt: u8, usage: Option<TokenUsage>, variant: TaskEndVariant, }, AgentOutput { task_name: String, agent_name: Option<String>, task_id: String, schema_type: Option<String>, chunk: String, }, AgentReasoning { task_name: String, agent_name: Option<String>, task_id: String, schema_type: Option<String>, chunk: String, }, CachePlanned { agent: String, n_segments: usize, n_stable: usize, longest_stable_prefix_len_chars: usize, markers_placed: usize, markers_placed_at: Vec<usize>, provider: String, tools_marker_placed: bool, system_marker_placed: bool, }, Suspended { checkpoint_name: String, token: String, prompt: String, schema: Value, actor_hint: ActorHint, timeout_secs: Option<u64>, trigger: SuspendTrigger, loop_context: Option<LoopSuspendContext>, }, Resumed { checkpoint_name: String, token: String, }, WorkflowEnd(WorkflowEndPayload), Error { message: String, kind: ErrorKind, code: ErrorCode, user_message: String, retry_after_ms: Option<u64>, source: ErrorSource, }, NodeStart(usize, Span), NodeEnd { node_id: usize, span: Span, target_var: Option<String>, value: Option<Value>, duration: Duration, }, Breakpoint { node_id: usize, span: Span, token: String, env_snapshot: HashMap<String, Value>, }, BreakpointResumed { node_id: usize, token: String, }, ToolCallStart { task_name: String, tool_name: String, server_name: String, input: Value, tool_use_id: String, }, ToolCallEnd { task_name: String, tool_name: String, tool_use_id: String, output: Value, duration: Duration, }, McpServerDegraded { alias: String, reason: String, }, McpServerRecovered { alias: String, }, ToolApprovalPending { execution_id: Option<String>, node_id: Option<u64>, token: String, tool_ref: String, args: Value, }, ToolApprovalResolved { token: String, approved: bool, args_override: Option<Value>, reason: Option<String>, }, ToolApprovalSkipped { execution_id: Option<String>, node_id: Option<u64>, tool_ref: String, reason: String, }, ToolReplayUncertain { execution_id: Option<String>, tool_use_id: String, tool_name: String, args: Value, }, VerificationStart { workflow_name: String, }, VerificationResult { workflow_name: String, results: Value, duration: Duration, }, ValidationFailure { task_name: String, attempt: u32, model_response: String, truncated: bool, total_length: u64, missing_fields: Vec<String>, extra_fields: Vec<String>, type_errors: Vec<String>, stop_reason: Option<String>, }, SubScript { script_name: String, parent_task: String, parent_node_id: Option<u64>, attempt: Option<u8>, parent_path: Vec<SubScriptFrame>, child: Box<EngineEvent>, }, LoopStart { name: String, max_turns: u32, }, LoopTurn { name: String, turn: u32, tool_calls: Vec<String>, usage: Option<TokenUsage>, }, LoopEnd { name: String, turn_count: u32, value: Value, }, ContextCompacted { agent: String, loop_id: Option<String>, turn: Option<u32>, threshold_pct: Option<u8>, threshold_abs: Option<u32>, strategy: String, before_tokens: u32, after_tokens: u32, provider_native: bool, cache_ttl: Option<String>, }, ContextOverflow { agent: String, attempted_strategies: Vec<String>, configured_cap_tokens: u32, model_context_window: u32, terminated_by_hard_error: bool, }, TaskCacheHit { agent: String, key_prefix: String, }, LLMResponse { node_id: String, call_index: u32, text: String, tool_calls: Vec<CachedToolCall>, usage: Option<TokenUsage>, }, LLMReplayCacheHit { node_id: String, call_index: u32, }, SubScriptSpawned { child_execution_id: String, parent_node_id: String, args: Value, }, SubScriptResult { parent_node_id: String, child_execution_id: String, outcome: ChildOutcome, }, CheckpointResolution { checkpoint_id: String, payload: Value, }, RuntimeStart { task_name: String, runtime_name: String, language: String, }, RuntimeStdout { task_name: String, chunk: String, }, RuntimeStderr { task_name: String, chunk: String, }, RuntimeEnd { task_name: String, exit_code: i32, duration_ms: u64, }, RuntimeError { task_name: String, kind: String, message: String, },
}

Variants§

§

Log(String)

§

LogLevel

Structured log line. Parallel to EngineEvent::Log but carries a severity so consumers (Studio’s trace panel, the bench why_failed runner, the future eval-failure dashboard) can highlight WARNs and ERRORs without string-sniffing.

Added in the “why-did-it-fail” infra round so that tracing::warn! calls in providers.rs / engine.rs ALSO show up in the execution event stream — previously they only went to the akribes-server stdout that nobody actively watches, which is how the max_tokens=4096 truncation hid for a week. Pre-existing EngineEvent::Log is retained verbatim for wire compat with older SDKs that match on the bare-string variant.

level is a free-form short string ("WARN", "ERROR", "INFO", …) — kept as String rather than an enum so a newer engine adding "DEBUG" doesn’t crash an older SDK.

Fields

§level: String
§message: String
§

StateUpdate(String, Value)

§

WorkflowStart(usize)

§

TaskStart(String, Option<String>)

§

TaskPrompt(String, String)

§

TaskEnd

Fields

§task: String
§on_error_label: Option<String>
§value: Value
§value_type: Option<TypeRef>

The declared return type of the task, if any. None when the task has no -> Type annotation (e.g. plain str tasks or untyped tasks).

§duration: Duration
§attempt: u8

1-indexed attempt count: 1 = first call succeeded, 2 = first validation retry succeeded, etc. Resets on task-level on_error retries (which are orthogonal to validation retries).

§variant: TaskEndVariant

How the task finished. Explicit discriminator so consumers don’t have to inspect value to distinguish Success from Unable. #[serde(default)] keeps pre-#206 wire payloads deserialising cleanly (they become TaskEndVariant::Success). Forward-compat for later expansion (e.g. Partial in #205) is provided by TaskEndVariant::Unknown.

§

AgentOutput

Fields

§task_name: String
§agent_name: Option<String>
§task_id: String
§schema_type: Option<String>
§chunk: String
§

AgentReasoning

Streaming extended-thinking / reasoning fragment from the LLM, emitted alongside AgentOutput for providers that interleave reasoning blocks into a streamed response (Anthropic extended thinking, OpenAI o-series + GPT-5 reasoning, Gemini 2.5 thinking).

Issue #1176. Pre-fix the streaming path collapsed every non-text rig variant into an empty AgentOutput chunk; reasoning content was simply lost, so Studio’s “thinking” inline panel stayed dark for streamed runs even when reasoning_tokens showed up on the trailing usage. This variant surfaces the same content non-streaming consumers see via the provider’s message-level reasoning block, but as it arrives.

Fields mirror Self::AgentOutput so a consumer that wants to render reasoning identically to text can switch on the variant name only. The chunk is plain text — encrypted / redacted reasoning blocks (Anthropic’s opaque thinking signatures) are dropped at the provider boundary rather than surfaced here, as they have no plain-text equivalent.

Fields

§task_name: String
§agent_name: Option<String>
§task_id: String
§schema_type: Option<String>

Mirrors Self::AgentOutput::schema_type — the declared return type, when present. Reasoning is associated with the task the engine is currently dispatching.

§chunk: String
§

CachePlanned

Auto cache-breakpoint engine emitted a placement decision for the upcoming dispatch. One event per Anthropic structured-output call; absent for non-Anthropic providers and for any dispatch where EngineOptions::auto_cache_enabled is false.

Fields mirror engine_cache::CachePlan plus a human-readable agent label. Captured by the rig path (and any in-process observer) to track cache-hit rates across iterations.

§All four marker slots are now reported

Anthropic’s 4-marker per-request budget covers four canonical slots: tools, system, user-msg #1, user-msg #2. Issue #472 item 1 brought the tools and system slots under engine ownership; the two *_marker_placed boolean fields below report the engine’s decision for each slot. Pre-#472 payloads reported only the user-message slots (markers_placed / markers_placed_at); the new booleans default to false on older wire payloads via #[serde(default)], which preserves the historical “engine reports user-message markers only” view for legacy consumers reading the JSON.

§Engine intent vs. wire-level cache footprint

Every *_marker_placed field describes the engine’s INTENT to stamp cache_control on that block. Anthropic’s response is what determines actual cache_creation_input_tokens / cache_read_input_tokens. Anthropic enforces a per-prefix minimum cacheable size (1024 tokens for sonnet/haiku-class models). When a cache_control marker sits at a prefix BELOW that minimum (e.g. the system block by itself is ~30 tokens), Anthropic extends the cache write forward to the next eligible boundary. So a “system marker only” cold dispatch can still report cache_creation_input_tokens ≈ entire prompt size. This is Anthropic’s documented behavior; the engine’s plan is correct, but the *_marker_placed fields describe INTENT, not the resulting wire-level cache footprint.

Set AKRIBES_DEBUG_CACHE_BODY=1 on the process running the dispatcher to print a per-call summary of which body sections carry markers.

Fields

§agent: String

Agent name as declared in the source (agent classifier).

§n_segments: usize

Number of segments the engine assembled for this dispatch. Includes the static prefix (docstrings/rules/examples), one segment per {placeholder} boundary in the body template, and the trailing structured-output instruction.

§n_stable: usize

How many segments the engine considered “stable” — i.e. either previously cached for this agent or marked stable by the DAG-aware peek (referenced by an upcoming dispatch).

§longest_stable_prefix_len_chars: usize

Total character length of the longest stable run at the HEAD of the segment list. 0 when no leading prefix was stable.

§markers_placed: usize

Number of cache_control markers actually placed on the user message. 0, 1, or 2 (Anthropic’s user-message budget within the 4-marker per-request cap).

Engine-level intent only; see the type-level docstring above for the relationship to wire markers and Anthropic’s observed cache_creation_input_tokens.

§markers_placed_at: Vec<usize>

Segment indices the engine stamped with a cache_control marker, sorted ascending. Length always equals markers_placed. Empty on the cold-cache / no-stable-prefix paths. Captured for the DAG-aware integration tests so they can assert the boundary picked by the placement algorithm without scraping the outbound HTTP body.

#[serde(default)] for backwards compat with payloads emitted before the field existed.

§provider: String

Whether the engine asked the provider to stamp cache_control on the tools block (issue #472 item 1). true on every Anthropic structured-output dispatch today — the synthetic return_result tool is stable per task and always benefits from caching. Reserved for future per-call unique-tool flows that may flip it to false.

Provider name the dispatch is going to (anthropic, openai, gemini, …). Lets SDK consumers route each CachePlanned event to a provider-specific renderer without scraping agent / model state. Issue #1019: before this field every event was an Anthropic event, invisible for OpenAI / Gemini caching paths. Carries the lower-cased provider id so renderers match against "anthropic" / "openai" / "gemini" directly. Empty string in old wire payloads (the #[serde(default)] default).

§tools_marker_placed: bool

#[serde(default)] for backwards compat with payloads emitted before the field existed (pre-#472 payloads silently treat this as false, which under-reports the real wire footprint — the field is the canonical truth from this PR onward).

§system_marker_placed: bool

Whether the engine asked the provider to stamp cache_control on the system block (issue #472 item 1). true on any dispatch with a non-empty agent system prompt. Reserved for future one-shot system-prompt flows (e.g. agent definitions that include per-call data) that may flip it to false.

#[serde(default)] for backwards compat — see tools_marker_placed.

§

Suspended

Fields

§checkpoint_name: String
§token: String
§prompt: String
§schema: Value
§actor_hint: ActorHint
§timeout_secs: Option<u64>
§trigger: SuspendTrigger

Why we suspended. Defaults to SuspendTrigger::DagPosition on older wire payloads that omit the field (e.g. an older server serializing against a pre-Stream-6 SDK, or vice versa). The #[serde(default)] here is what guarantees backwards compat.

§loop_context: Option<LoopSuspendContext>

Some(ctx) when the suspension fired inside a loop block’s per-turn dispatch — see LoopSuspendContext for the carried fields. None for the existing top-level / DAG checkpoint path, which is the wire shape every pre-loop-checkpoint SDK emits. #[serde(default, skip_serializing_if = "Option::is_none")] keeps both directions wire-compatible: old servers serializing against new SDKs simply omit the field, and new servers emit it only when there’s a loop context to carry.

§

Resumed

Fields

§checkpoint_name: String
§token: String
§

WorkflowEnd(WorkflowEndPayload)

Terminal event of a workflow run.

§Aggregate rollup (issue #1173)

Pre-#1173 this was the tuple variant WorkflowEnd(Value) whose payload was the bare workflow output. Consumers re-walked every TaskEnd to compute totals; cluster dashboards and the CLI’s trailer line did this on every execution. The variant still has a single payload slot — a WorkflowEndPayload — but the payload itself now carries both the output value AND a WorkflowTotals rollup so consumers can size the run without touching the per-task stream.

§Wire compatibility

New wire shape: {"type": "WorkflowEnd", "payload": {"value": <output>, "total_input_tokens": N, ...}}. Legacy wire shape (pre-#1173): {"type": "WorkflowEnd", "payload": <output>} — payload IS the bare output value.

WorkflowEndPayload implements Serialize/Deserialize by hand so it accepts both shapes: new shape when the payload is a JSON object with a value key plus at least one total_* aggregate key, legacy bare-value otherwise. Aggregate fields default to 0 on legacy reads; serialization always emits the new shape.

§

Error

Structured failure surfaced to subscribers (SSE, WebSocket, OTel). message and kind are kept for back-compat; code, user_message, retry_after_ms, and source carry the richer detail consumers should branch on. New construction goes through [EngineEvent::error_from_detail] so all fields stay in sync.

Fields

§message: String
§user_message: String
§retry_after_ms: Option<u64>
§

NodeStart(usize, Span)

§

NodeEnd

Fields

§node_id: usize
§span: Span
§target_var: Option<String>
§value: Option<Value>
§duration: Duration
§

Breakpoint

Fields

§node_id: usize
§span: Span
§token: String
§env_snapshot: HashMap<String, Value>
§

BreakpointResumed

Fields

§node_id: usize
§token: String
§

ToolCallStart

Fields

§task_name: String
§tool_name: String
§server_name: String
§input: Value
§tool_use_id: String

LLM-issued tool_use_id. Empty string on pre-durable-execution payloads (preserved by #[serde(default)]). Always present on events written by v1+ engines; the cache lookup at the ToolCallEnd site keys on this.

§

ToolCallEnd

Fields

§task_name: String
§tool_name: String
§tool_use_id: String
§output: Value
§duration: Duration
§

McpServerDegraded

An MCP server’s circuit breaker tripped open.

Fields

§alias: String
§reason: String
§

McpServerRecovered

An MCP server recovered (circuit breaker closed again).

Fields

§alias: String
§

ToolApprovalPending

A destructive MCP tool invocation is awaiting operator approval. Mirrors the checkpoint suspension protocol — the execution is resumed by POST /executions/{id}/resume with { approve: bool, args_override?: Value } keyed on token.

Fields

§execution_id: Option<String>
§node_id: Option<u64>
§token: String
§tool_ref: String
§args: Value
§

ToolApprovalResolved

Audit-trail companion to EngineEvent::ToolApprovalPending (issue #857). Emitted on every resume path — both approval and rejection — so trace replay can reconstruct the decision and Studio’s approval inbox can render a checkmark/X next to the resolved row. Without this event the only way to distinguish “approved” from “rejected” after the fact is to observe whether a subsequent ToolCallStart fired against the same token, which is fragile and loses the rejection reason.

Fields

§token: String

Matches the token on the originating EngineEvent::ToolApprovalPending.

§approved: bool

True on approve, false on reject.

§args_override: Option<Value>

Operator-supplied argument override on approval, when the approver chose to edit the tool args before dispatch. None on rejection or on plain approve-as-proposed.

§reason: Option<String>

Optional reason string the approver attached to the decision. Surfaced by Studio’s inbox; persisted for compliance audits.

§

ToolApprovalSkipped

Emitted when a tool that would normally have required approval was dispatched without prompting because a pre-configured policy (allowlist / read-only classification / project-level auto-approval) covered it. Operator-audit gap closer (issue #1110): without this event the only way to spot auto-approval is to compare the policy config against the trace stream out-of-band.

reason is a short policy-driven discriminator (e.g. "policy:read_only", "policy:allowlisted", "policy:internal") so SDK consumers can render a badge without re-classifying the tool themselves.

Fields

§execution_id: Option<String>
§node_id: Option<u64>
§tool_ref: String
§reason: String
§

ToolReplayUncertain

Emitted during durable replay when the replay cache holds a ToolCallStart for a given tool_use_id but no matching ToolCallEnd. The tool MAY have fired once on a previous run before the server crashed mid-call, and we are about to re-fire it — which will cause a duplicate side effect for non-idempotent tools (e.g. send_email, create_pr).

This previously emitted only a tracing::warn!(target = "tool_replay_uncertain", ...), which is invisible to SDK / Studio consumers. The structured event lets the operator surface a “replay-uncertain tool” badge inline in the execution timeline and decide whether to acknowledge before the re-run continues (#872).

args is the raw JSON-encoded tool input from the cached start event so the operator can compare against the impending re-fire’s args. #[serde(default)] on the deserializer keeps wire-compat with pre-#872 events that omit the variant entirely (they decode through the SDK’s Other catch-all).

Fields

§execution_id: Option<String>
§tool_use_id: String
§tool_name: String
§args: Value
§

VerificationStart

Fields

§workflow_name: String
§

VerificationResult

Fields

§workflow_name: String
§results: Value
§duration: Duration
§

ValidationFailure

A structured-output task’s response failed validation. Emitted in addition to the existing Log line so SDK consumers without the new event still render the human-readable summary, but tooling that knows about this variant can render the model’s actual response, the schema-validator’s structured error breakdown, and the provider’s stop_reason (so e.g. a max_tokens truncation isn’t misdiagnosed as “schema overflow” — see issue #320).

Fields:

  • task_name — the task whose validation failed.

  • attempt — 1-indexed attempt number (1 = first call, 2 = first retry, …).

  • model_response — the raw text / JSON-serialized tool input the model emitted, exactly as the validator saw it. May be empty ("") or "{}" when the model truncated mid-output.

    Issue #1139 — bounded. Capped at VALIDATION_FAILURE_RESPONSE_CAP_BYTES bytes on emit. If the raw response was longer, truncated is true and total_length carries the original byte count so consumers can surface “model emitted 4 MB; first 64 KB shown” instead of silently logging a megabyte to execution_events three times in a retry loop.

  • truncatedtrue when model_response was truncated to fit the cap. #[serde(default)] keeps pre-#1139 wire payloads decoding cleanly (they decode as false).

  • total_length — original byte length of the model response before truncation. Equal to model_response.len() when truncated is false. #[serde(default)] for wire-compat.

  • missing_fields — JSON-pointer paths to required fields the schema validator flagged as absent.

  • extra_fields — paths to fields rejected by additionalProperties: false.

  • type_errors — human-readable type / value mismatches (e.g. "expected string, got null at /name"). Includes any non-missing, non-additional-property schema errors plus parse / custom-validator messages.

  • stop_reason — the provider’s stop reason, when known. For Anthropic this is "end_turn" / "max_tokens" / "tool_use" etc. None when the upstream call didn’t surface one.

Fields

§task_name: String
§attempt: u32
§model_response: String
§truncated: bool
§total_length: u64
§missing_fields: Vec<String>
§extra_fields: Vec<String>
§type_errors: Vec<String>
§stop_reason: Option<String>
§

SubScript

Envelope that wraps an event emitted by a sub-script invoked from a parent task via the call("name", inputs={...}) script-composition primitive (roadmap item A).

§Flat wire shape (issue #993)

Before #993 the envelope nested a Box<EngineEvent> per call-stack level, so a depth-N chain produced a single event whose serialized size was O(N) (a 10-level fanout could push one SSE frame past a megabyte and choke reconnect logic in cluster cards). The new shape is flat: child is always the LEAF event the innermost sub-engine emitted (never another SubScript), and parent_path carries the outer ancestor chain by id.

  • script_name — the immediately-running sub-script (innermost frame). Same field semantics as the legacy wire shape.
  • parent_task — the variable name on the immediate-parent side that received the call’s result.
  • parent_node_id / attempt — the immediate parent’s call(…) node id + author-raise attempt counter (#845).
  • parent_path — ordered [outermost, ..., immediate_parent]; empty when the current sub-script is a direct child of the top-level workflow.
  • child — the leaf event the innermost sub-engine emitted. Boxed to keep the variant cheap on the stack. Field name kept for wire-compat with pre-#993 consumers; the meaning narrowed from “wrapped event (potentially another SubScript)” to “leaf event”.

Consumers that want the full call tree walk parent_path once and consume child as a leaf. The top-level reducer in each SDK is responsible for stitching frames back into a tree.

§Back-compat read path

Legacy emissions (pre-#993) put a nested SubScript inside child, with parent_path absent or empty. Self::flatten_subscript_chain is the canonical post-deserialize step that walks any nested SubScripts into parent_path, leaving the resulting envelope in the new flat form. The Rust SDK runs it on every inbound event before projecting to WorkflowEvent; the TS and Python SDKs flatten equivalently inside their reducers.

Fields

§script_name: String

Script name of the innermost (currently emitting) sub-script.

§parent_task: String

Parent-side variable name that received the immediate parent’s call result.

§parent_node_id: Option<u64>

Compiler-stable id of the immediate parent’s call(…) node. Lets SDK consumers correlate retries of the same call site (issue #845). #[serde(default)] keeps pre-#845 wire payloads decoding cleanly.

§attempt: Option<u8>

1-indexed attempt counter for author-raise retries at the same call site. See SubScriptFrame::attempt for the same semantics on ancestor frames.

§parent_path: Vec<SubScriptFrame>

Ancestor chain from outermost to immediate parent. Empty when the emitting sub-script sits directly under the top-level workflow. Skipped from the wire when empty so a depth-1 envelope stays as compact as it was pre-#993, and #[serde(default)] keeps pre-#993 payloads (which omit the field) decoding cleanly.

§child: Box<EngineEvent>

The leaf event the innermost sub-engine emitted. New emissions guarantee this is never another SubScript; legacy payloads may still nest one — call EngineEvent::flatten_subscript_chain to normalize.

§

LoopStart

A loop block began executing. Emitted exactly once per loop call, before any LoopTurn. max_turns is the resolved upper-bound budget (declared max_turns: if present, else the engine’s LOOP_MAX_TURNS_DEFAULT). Additive event — older SDKs treat the JSON payload as an unknown variant and ignore it.

Fields

§name: String
§max_turns: u32
§

LoopTurn

A single turn of a loop block settled (provider call returned and every tool_use block was dispatched). turn is 1-indexed. tool_calls is the names of the tools the model invoked this turn, in dispatch order — including the synthetic state_get, state_update, return and any user skills: entries.

Fields

§name: String
§turn: u32
§tool_calls: Vec<String>
§usage: Option<TokenUsage>

Per-turn token usage reported by the provider for the dispatch this turn produced. None on providers that don’t surface per-call usage (mock provider) and on pre-#829 wire payloads. Lets Studio’s loop card show “turn 3 spent 4500 tokens” without walking the wrapped LLMResponse sub-tree of events.

§

LoopEnd

A loop block exited. value is the agent’s submitted return value (from return(...)), the final state on natural stop_when exit without a return, or a Value::FatalError envelope when the loop exhausted its max_turns budget without ever calling return. turn_count is the number of turns actually executed (1-indexed).

Fields

§name: String
§turn_count: u32
§value: Value
§

ContextCompacted

Emitted when a compaction step runs — once per primitive activation. provider_native: true means Anthropic / OpenAI performed the compaction server-side; the engine surfaces the before/after counts from the response. strategy is the primitive name (drop_thinking_blocks, drop_oldest_tool_results, summarize_to_state, provider_native) or the user task name for a custom compactor task.

cache_ttl is Some("5m") or Some("1h") on the Anthropic provider_native path (the engine pins ttl: "1h" via the extended-cache-ttl-2025-04-11 beta header — see providers.rs:1772), None for every non-native compaction primitive (the engine-driven primitives don’t write any cache block themselves). Downstream cost dashboards multiply cache-write tokens by the 1h-vs-5m rate (issue #1130); without this field the wire envelope leaves the TTL ambiguous. #[serde(default, skip_serializing_if = "Option::is_none")] keeps pre-#1130 wire payloads decodable as cache_ttl = None.

See the compaction design at docs/superpowers/specs/2026-05-12-compaction-design.md for the “Observability + cost” contract; the chain emits one ContextCompacted per primitive activation and ContextOverflow at chain exhaustion.

Fields

§agent: String
§loop_id: Option<String>
§turn: Option<u32>
§threshold_pct: Option<u8>
§threshold_abs: Option<u32>
§strategy: String
§before_tokens: u32
§after_tokens: u32
§provider_native: bool
§cache_ttl: Option<String>
§

ContextOverflow

Emitted when the compaction chain runs to exhaustion (or when compaction: none and the request would exceed the model context). Carries the chain log so users can diagnose which primitives ran before the engine gave up.

terminated_by_hard_error distinguishes the user-authored hard_error() early-exit (issue #1056) from the implicit chain-exhaustion exit. SDKs can render a stronger “author intentionally bailed out at threshold X” message instead of the generic “all arms exhausted” copy.

#[serde(default)] on the new field keeps pre-#1056 wire payloads decodable (they decode as terminated_by_hard_error = false, which matches their original semantics — every old emission was a chain-exhaustion exit).

Fields

§agent: String
§attempted_strategies: Vec<String>
§configured_cap_tokens: u32
§model_context_window: u32
§terminated_by_hard_error: bool

true when the chain hit a user-authored hard_error() step; false when the chain ran past every step without freeing enough budget. See the type-level docs for the distinction and the back-compat default.

§

TaskCacheHit

Emitted by the engine when a task’s result is served from [crate::engine_persistent_cache::PersistentTaskCache] instead of being dispatched to a provider. Lets trace inspectors, the bench UI, and the MCP show “stages 1-3 were free, stage 4 ran” without having to parse prompt-segment internals.

agent is the agent name the task ran under (matches EngineEvent::AgentOutput::agent_name for the same task). key_prefix is the first 6 hex chars of the (u64) cache key so consumers can cluster repeated hits without persisting the full key. The prefix is informational only — collisions are harmless, the key itself is the source of truth.

Emitted on the cache-hit branch of the engine’s task dispatch loop, before the corresponding TaskEnd event for the same task.

Fields

§agent: String
§key_prefix: String
§

LLMResponse

LLM provider response captured for durable replay. Carries the full response (text + tool-use blocks + usage) keyed by (node_id, call_index). See crates/akribes-core/src/replay_cache.rs.

Fields

§node_id: String
§call_index: u32
§text: String
§tool_calls: Vec<CachedToolCall>
§

LLMReplayCacheHit

Emitted on the cache-hit branch of call_provider_* — distinguishes “this task’s underlying LLM call was served from the replay cache” from “the task’s full result was served from the persistent task cache” (the latter already has EngineEvent::TaskCacheHit). Issue #815: a reconnecting SSE client otherwise can’t tell a cache-served task from a slow first-token, because the engine deliberately does not re-emit streaming AgentOutput chunks on replay.

node_id + call_index mirror the keying of the underlying LLMResponse variant so consumers can correlate the hit with the cached response.

Fields

§node_id: String
§call_index: u32
§

SubScriptSpawned

A child execution row was just inserted at the parent’s call(...) node. The parent’s event log carries this intent event; the child’s own log records its lifecycle independently.

Fields

§child_execution_id: String
§parent_node_id: String
§args: Value
§

SubScriptResult

Child execution finished and the parent observed its terminal state. Synthesised by the parent reading the child’s terminal event; never written by the child itself.

Fields

§parent_node_id: String
§child_execution_id: String
§

CheckpointResolution

A Suspended checkpoint resolved. Written when POST /executions/:id/resume lands a payload; replay re-derives the engine state from this event instead of re-suspending.

Fields

§checkpoint_id: String
§payload: Value
§

RuntimeStart

Emitted when the engine starts dispatching a runtime block (the container code-execution construct — see the “AI-driven container code execution” feature). One event per runtime call site, before any RuntimeStdout/RuntimeStderr chunks. task_name is the call-site identifier the engine uses to attribute the call (matches the wrapping TaskStart/TaskEnd pair’s task field so SDK reducers that group by task keep working). runtime_name is the declared runtime NAME(...) identifier. language is the source-form keyword ("python" / "bash" / "node" / "rust" / "java").

Fields

§task_name: String
§runtime_name: String
§language: String
§

RuntimeStdout

A chunk of stdout produced by the running sandbox. chunk is the raw byte slice decoded as UTF-8 (lossy). Multiple events fire in arrival order; SDK reducers concatenate to reconstruct the full stream.

Fields

§task_name: String
§chunk: String
§

RuntimeStderr

A chunk of stderr produced by the running sandbox. Mirrors EngineEvent::RuntimeStdout for the error stream.

Fields

§task_name: String
§chunk: String
§

RuntimeEnd

The runtime call finished successfully. exit_code is the container’s process exit code (0 for clean exit; non-zero on crash / panic / explicit non-zero exit). duration_ms is the wall-clock time the sandbox reported between dispatch and exit — does not include the time the engine spent waiting on its own semaphore.

Fields

§task_name: String
§exit_code: i32
§duration_ms: u64
§

RuntimeError

The runtime call failed before producing an exit code. kind is a stable wire-form discriminator: "NotConfigured" (no sandbox URL set), "Timeout" (execution exceeded the declared timeout), "SandboxUnavailable" (network / connect error to the sandbox), "OomKilled" (the container hit its memory cap), "Internal" (any other sandbox-side failure). message is human-readable detail forwarded from the sandbox’s error SSE event (or synthesised by the engine for client-side errors like NotConfigured).

Fields

§task_name: String
§kind: String
§message: String

Implementations§

Source§

impl EngineEvent

Source

pub fn flatten_subscript_chain(self) -> EngineEvent

Flatten any legacy nested EngineEvent::SubScript chain into the new parent_path + child(leaf) shape (issue #993).

Pre-#993 emissions wrapped each call-stack level in its own SubScript envelope (SubScript { child: Box<SubScript{ child: ... }> }). The new shape carries the chain via parent_path and reserves child for the innermost leaf event. This method walks down child for as long as it is itself a SubScript, accumulating each frame into a growing parent_path (so the resulting list reads [outermost_ancestor, …, immediate_parent_of_leaf]), and finally sets child to the recovered leaf.

Events that are not SubScript envelopes are returned unchanged. SDK consumers (Rust / TS / Python) call this on every inbound event so reducers see a uniform flat shape regardless of which engine version emitted the wire log.

Source

pub fn validation_failure( task_name: impl Into<String>, attempt: u32, model_response: String, missing_fields: Vec<String>, extra_fields: Vec<String>, type_errors: Vec<String>, stop_reason: Option<String>, ) -> EngineEvent

Build a EngineEvent::ValidationFailure with model_response capped to VALIDATION_FAILURE_RESPONSE_CAP_BYTES (issue #1139). The original byte length is preserved on total_length and truncated is set when the cap fired so consumers can surface “first 64 KB shown; original was N bytes” without having to re-derive it. UTF-8 boundary is respected — the truncation slices at the closest char boundary below the cap.

Source

pub fn error(detail: ErrorDetail) -> EngineEvent

Build an EngineEvent::Error from a fully-formed crate::error::ErrorDetail. Use this at every error-emission site so SDK / OTel / DB consumers all see the same structured fields.

Source

pub fn error_kind(kind: ErrorKind, message: impl Into<String>) -> EngineEvent

Quick constructor for sites that don’t yet have a specific ErrorCode. Picks the closest “Other” code for the kind via crate::error::ErrorDetail::from_kind.

Source

pub fn error_code(code: ErrorCode, message: impl Into<String>) -> EngineEvent

Quick constructor from a specific ErrorCode.

Trait Implementations§

Source§

impl Clone for EngineEvent

Source§

fn clone(&self) -> EngineEvent

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for EngineEvent

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl<'de> Deserialize<'de> for EngineEvent

Source§

fn deserialize<__D>( __deserializer: __D, ) -> Result<EngineEvent, <__D as Deserializer<'de>>::Error>
where __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl From<EngineEvent> for WorkflowEvent

Source§

fn from(evt: EngineEvent) -> Self

Converts to this type from the input type.
Source§

impl Serialize for EngineEvent

Source§

fn serialize<__S>( &self, __serializer: __S, ) -> Result<<__S as Serializer>::Ok, <__S as Serializer>::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more