run.started | threadId, model, nodeId (model:{modelId}), skills (ids actually loaded) | start |
step.started | step (1-based) | step-start |
reasoning.delta | text | reasoning-delta (glm only; DeepSeek sends none) |
text.delta | text | text-delta Em and en dashes are replaced by a plain hyphen before the event is sent (house style, #109, #124); the TEE verification covers the model’s own bytes |
tool.called | toolCallId, tool, nodeId, args; approvedBy: "rule" and rule: "tool" | "payment" when an owner’s auto-approve rule (#167) lets the call run without a tool.approval_required | tool-call: payload { toolCallId, toolName, args } |
tool.result | toolCallId, tool, nodeId, result, status: "ok" | tool-result: payload { toolCallId, toolName, args, result } |
tool.error | toolCallId, tool, nodeId, status: "error" | "policy_denied" | "wallet_frozen" | "rejected" | "rate_limited", error, result? | tool-error; for send_payment, a tool-result whose status is policy_denied / wallet_frozen (the wallet refused before signing, #42): error is OutLayer’s message, result the payment receipt without txHash; for mail_send, a tool-result whose status is rejected / rate_limited (#96): error is the refusal’s message, result the send record without messageId |
tool.approval_required | toolCallId, tool, nodeId, args | tool-call-approval |
step.finished | step, usage | step-finish |
run.finished | finishReason, text, usage, costUsd, verification: { status: "pending" } | finish |
run.finished (canceled) | status: "canceled", finishReason: "canceled"; no text, usage or costUsd, and no run.verified after it | backend, not the engine: the last event of a run stopped with POST /v1/runs/{runId}/cancel (#121); a builder run gets workflow.finished with status: "canceled" instead |
attack.verdict | attackId, attacker, paymentTool (the Mind’s skills grant send_payment), stoppedBy: model (refused, asked for no payment); no_tool (no payment tool); approval (a payment card waits for the owner); policy (approved, then refused by the wallet in OutLayer’s TEE before signing: policy_denied / wallet_frozen, no tx); none (a payment was sent), detail, optional to, status, attackerTargeted (the payment went to the scenario’s attacker), txHash (only for none) | engine (src/attacks.ts, #16), on a run that carries attackId, after run.finished and before run.verified |
thread.titled | threadId, title (3 to 6 words summing up the user’s first message, in the user’s language, no quotes or final period) | engine (src/titles.ts, #86, #93), after the run.finished of a thread’s first run and before run.verified; skipped if making the title fails or takes too long, and then the backend keeps the first-message cut |
run.verified | model, nodeId, verification (see below). Always the run’s last event | engine, after the stream is read |
run.error | error | error chunk or a thrown error. The backend adds one itself when the engine stream breaks (engine_stream_interrupted), and when a run stayed running after a backend restart (run_interrupted, #218) |
workflow.started | workflowId, nodeId (workflow:{workflowId}), model, skills | workflow-start; first event of a builder run |
node.started | node (canvas node id), nodeType (agent | tool | http), nodeId (Mind Graph) | workflow-step-start |
node.finished | node, nodeType, nodeId, status: "ok" + output, or status: "error" + error | workflow-step-result |
workflow.finished | workflowId, status (success, failed, …) | workflow-finish; followed by run.verified as the last event |