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.
StateUpdate(String, Value)
WorkflowStart(usize)
TaskStart(String, Option<String>)
TaskPrompt(String, String)
TaskEnd
Fields
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).
attempt: u81-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).
usage: Option<TokenUsage>variant: TaskEndVariantHow 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
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
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.
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
n_segments: usizeNumber 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: usizeHow 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: usizeTotal character length of the longest stable run at the
HEAD of the segment list. 0 when no leading prefix was
stable.
markers_placed: usizeNumber 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: StringWhether 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: boolWhether 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
trigger: SuspendTriggerWhy 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
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
source: ErrorSourceNodeStart(usize, Span)
NodeEnd
Breakpoint
BreakpointResumed
ToolCallStart
Fields
ToolCallEnd
McpServerDegraded
An MCP server’s circuit breaker tripped open.
McpServerRecovered
An MCP server recovered (circuit breaker closed again).
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.
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: StringMatches the token on the originating
EngineEvent::ToolApprovalPending.
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.
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).
VerificationStart
VerificationResult
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_BYTESbytes on emit. If the raw response was longer,truncatedistrueandtotal_lengthcarries the original byte count so consumers can surface “model emitted 4 MB; first 64 KB shown” instead of silently logging a megabyte toexecution_eventsthree times in a retry loop. -
truncated—truewhenmodel_responsewas truncated to fit the cap.#[serde(default)]keeps pre-#1139 wire payloads decoding cleanly (they decode asfalse). -
total_length— original byte length of the model response before truncation. Equal tomodel_response.len()whentruncatedisfalse.#[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 byadditionalProperties: 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.Nonewhen the upstream call didn’t surface one.
Fields
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
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.
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
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).
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
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
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.
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.
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.
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.
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.
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.
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").
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.
RuntimeStderr
A chunk of stderr produced by the running sandbox. Mirrors
EngineEvent::RuntimeStdout for the error stream.
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.
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).
Implementations§
Source§impl EngineEvent
impl EngineEvent
Sourcepub fn flatten_subscript_chain(self) -> EngineEvent
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.
Sourcepub 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
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.
Sourcepub fn error(detail: ErrorDetail) -> EngineEvent
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.
Sourcepub fn error_kind(kind: ErrorKind, message: impl Into<String>) -> EngineEvent
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.
Sourcepub fn error_code(code: ErrorCode, message: impl Into<String>) -> EngineEvent
pub fn error_code(code: ErrorCode, message: impl Into<String>) -> EngineEvent
Quick constructor from a specific ErrorCode.
Trait Implementations§
Source§impl Clone for EngineEvent
impl Clone for EngineEvent
Source§fn clone(&self) -> EngineEvent
fn clone(&self) -> EngineEvent
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read more