Body kinds
Schema reference for built-in `body.kind` values. Records you can subscribe to, query against, and project from — emitted by the engine during dispatch, inference, and federation.
Body-kind family renamed: `core.agent.*` → `core.work.*`
The family of body kinds that describes goal-structured work was
renamed from core.agent.* to core.work.*. The same primitive
serves any actor doing goal-structured work — humans, LLM agents,
workflows, automation — so the family name reflects that.
What changed:
core.agent.*(15 kinds:loop,turn,tool_call,tool_result,loop_outcome,loop_cancel,plan,criteria,evaluation,turn_judgment,note,compensation,artifact,coercion) →core.work.*with the same names.core.agent.aitl.v1→core.work.decision_request.v1, with a clearer body shape:requester+decider_didordecider_role+questionoptions+context.
core.scribe.delegation.v1andcore.guide.delegation.v1→ unifiedcore.work.delegation.v1with adelegator_kindfield.- New kinds:
core.work.trust_delta.v1,core.actor.preferences.v1,core.engine.actor_defaults.v1.
What stays the same:
- Records emitted under the old names before v0.52 remain readable — you can still query them, fold them, and project from them.
- The 8-record-field envelope (
parents,thread,actor,act,body,clock,data_type,judged_by) is unchanged.
What to update:
- Clients emitting
core.agent.*records need to switch to the new names — the engine rejects new emits of the old names.
See the unified body-kinds list below for the v0.52 shape.
body.kind is the body-convention discriminant — a string in scope.category.entity[.version] format that names the schema of record.body. Built-in kinds emitted by the engine are documented here; user-extension kinds (anything outside the core.* and syncropel.* reserved scopes) follow the same grammar but live in their own registries.
For the kind grammar itself (3 vs 4 segments, allowed characters, reserved scopes, version semantics), see the body.kind manifest guide.
Kinds emitted by dispatch
Every spl task dispatch (or any other dispatch path) emits a sequence of records that lets operators reconstruct what happened from the log alone — no log scraping, no subprocess inspection. Subscribe to these kinds to build live observability dashboards; query against them to answer "what did this dispatch cost / why did it fail / where in the codepath did it exit."
syncropel.dispatch.stream_event.v1
One record per Assistant event from the dispatched agent. Carries token deltas (this event) and running totals (cumulative for the dispatch). Survives subprocess crashes — if the final result event never arrives, accumulated tokens and partial cost can still be reconstructed.
{
"kind": "syncropel.dispatch.stream_event.v1",
"topic": "stream_event_seen",
"event_type": "assistant",
"tokens_in_delta": 1247,
"tokens_out_delta": 89,
"tokens_in_accum": 8431,
"tokens_out_accum": 1102,
"cost_usd_delta": 0.02145,
"turn_id": "turn_abc123"
}syncropel.dispatch.session.v1
Captures the agent CLI's session UUID from the first system init event. Written once per dispatch. Bridges Syncropel records to the agent's own session log (e.g., ~/.claude/projects/<slug>/<session_id>.jsonl for Claude Code) for raw side-of-agent data — tool uses, tool results, full message history. spl task diagnose correlates via this record.
{
"kind": "syncropel.dispatch.session.v1",
"topic": "agent_session_captured",
"session_id": "abc-123-...",
"project_cwd": "/home/dev/my-project",
"project_slug": "-home-dev-my-project",
"model": "claude-opus-4",
"turn_id": "turn_abc123"
}syncropel.dispatch.subprocess_exit.v1
Emitted after child.wait() regardless of which completion codepath fired. Records the objective facts — PID, exit code, signal, duration, last stream event seen — that previously only existed as log lines.
{
"kind": "syncropel.dispatch.subprocess_exit.v1",
"topic": "subprocess_exited",
"pid": 12345,
"exit_code": 0,
"signal": null,
"duration_ms": 18400,
"last_stream_event_type": "result",
"last_stream_event_clock": 47,
"turn_id": "turn_abc123"
}syncropel.dispatch.complete.v1
The terminal completion record. Carries the same fields as the historical dispatch_complete plus three enums describing how the dispatch ended. Use these to distinguish "succeeded normally" from "subprocess crashed" from "we hit a wall-clock budget" in one query.
{
"kind": "syncropel.dispatch.complete.v1",
"topic": "dispatch_complete",
"success": true,
"cost_usd": 0.34,
"cost_source": "result_event",
"completion_codepath": "result_event",
"failure_reason": null,
"duration_ms": 18400,
"turn_id": "turn_abc123"
}cost_source (where the final cost_usd figure came from)
| Value | Meaning |
|---|---|
result_event | Came from the terminal result stream event (authoritative). |
accumulated_from_events | Computed by summing per-event usage deltas (used when result never arrived). |
unknown | No figure available — stream ended before any cost signal. |
completion_codepath (which exit branch fired)
| Value | Meaning |
|---|---|
result_event | Subprocess emitted a terminal result stream event. The happy path. |
line_timeout | per_line_timeout fired — subprocess went silent for too long. |
budget_deadline | Absolute wall-clock deadline (budget_deadline) was reached. |
stream_eof_fallback | stdout closed without a result event — child exited or stdout broke. |
failure_reason (null on success)
| Value | Meaning |
|---|---|
null | Success. |
result_missing | stdout closed before a result event arrived. |
subprocess_exited_nonzero | Subprocess exited non-zero after closing stdout. |
line_timeout | per_line_timeout fired. |
budget_exceeded | Absolute wall-clock deadline reached. |
read_error | stdout read error mid-dispatch. |
result_reported_error | result event arrived but reported is_error: true. |
See the dispatch observability guide for example queries against these records — including spl task diagnose <id> and spl logs --trace-id.
Kinds emitted by inference
The inference executor commits a KNOW record on the query thread when the pipeline cannot produce an answer. Every non-success path emits one infer.error.v1 record with a stable body.code.
infer.error.v1
{
"kind": "infer.error.v1",
"code": "quorum_not_met",
"message": "Required min_quorum=3 valid responses; received 2.",
"details": {
"min_quorum": 3,
"valid_responses": 2,
"total_dispatched": 5
},
"partial_responses": ["rec_abc...", "rec_def..."]
}details carries code-specific context (projected cost, elapsed seconds, missing field, etc.) — no internal Rust error strings leak through. partial_responses carries the IDs of DO records collected before a post-dispatch failure, so you can inspect what the responders did manage to produce.
code taxonomy (10 values)
Maps the 8-step inference pipeline. Every non-success branch emits exactly one of these.
| Code | Step | Meaning |
|---|---|---|
invalid_query | 1 | Query body failed schema / grammar validation — missing required field, shape mismatch, unknown fold function. |
no_candidates | 2 | Hard-filter produced zero candidates. The responder pool was non-empty but no entry satisfied the predicate set. |
no_relevant_candidates | 3 | Candidates existed but the relevance scorer dropped every one below the configured threshold (top_k empty). |
cost_budget_exceeded | 4 (pre-dispatch) | Projected total cost exceeded side_effects.max_cost_usd (or the namespace default). |
responder_unavailable | 4–5 | One or more selected responders returned dispatch errors before any response could be collected. |
latency_timeout | 5 | The side_effects.max_latency_secs deadline elapsed before min_quorum valid responses arrived. |
quorum_not_met | 6 | Valid (non-error) responses were fewer than fold.min_quorum after filtering error-shaped DOs. |
fold_failed | 7 | The fold function returned an error (missing evaluator, expression failed, etc.). |
answer_shape_mismatch | 8 | The folded answer did not satisfy answer_shape.kind / required_fields / schema_ref. |
obligation_conflict | 8 | Obligation resolution raised a ValidationError conflict that the envelope's strategy could not merge. |
Wire-form is snake_case as shown. Match on body.code to route or filter.
Kinds emitted by the work loop
Every run of a work loop (spl work-loop, POST /v1/work/loop, or a chat-actor delegating to one via @agent <goal>) emits a sequence of records that lets you reconstruct what the loop saw, what it tried, what tools it called, and how it terminated — entirely from the records. Subscribe to these on the loop's thread (or query against them) to build live progress UIs and post-hoc audits.
Loop lifecycle
core.work.loop.v1— the loop record itself. Carries goal, tier, ceilings (max_turns,wall_clock_secs,token_budget), and any per-looptask_budget.core.work.turn.v1— one turn of the loop. Sequenced bybody.turn_index; carries the prompt fold, model call summary, and per-turn cost.core.work.tool_call.v1+core.work.tool_result.v1— every tool invocation and its result. The tool-call body is permission-checked through the engine; the result body carries success or a structured failure code. Failure feedback is always returned to the agent as a tool result so the next turn can read it and self-correct.core.work.loop_outcome.v1— the terminal record.body.outcomeis one ofcompleted,failed,cancelled,blocked,max_turns,budget_exhausted,guardrail_halt, orawaiting_actor(paused). Non-terminal outcomes carry structured payloads —budget_exhaustedincludesbudget_kind;guardrail_haltincludestool,args_hash, andcount.
Halts and signals
core.work.coercion.v1— paired with ablockedoutcome. Names the structuredreason(tool_unavailable/ambiguous_goal/precondition_failed/decision_required/input_invalid/internal_error), anarrativefield, and where relevant amissing_toolsarray ordecision_request_ref. This is how the loop reports an honest "I can't do this" instead of producing noise.core.work.guardrail.v1— emitted when a repeated-failure pattern is detected.verdict: "block"fires mid-loop when the next attempt at a thrashing signature gets a synthetic failure result returned to the agent (the loop continues — the agent gets to change strategy);verdict: "halt"fires when the loop terminates with aguardrail_haltoutcome. Body carriestool,args_hash,count, andthreshold.core.work.compensation.v1— emitted when a loop violates a harness assumption (today: context-reset firings, where the model wraps up prematurely as it nears its context limit). The compensation record names the seam + the violated assumption + context. Operators can review per-actor anomalies viaGET /v1/work/compensation/review.core.work.decision_request.v1— pause for human approval. When a loop needs a decision, it raises one of these and pauses with anawaiting_actoroutcome until you respond.core.work.loop_cancel.v1— built-in cancel. ADOof this kind on the loop thread propagates through the reconciler to the in-process loop, which exits with acancelledoutcome.
Chat-actor delegation (v0.49)
When a Scribe or Guide chat-actor receives a message that opens with @agent <goal>, it delegates to a real loop running under the user's identity (not the chat-actor's), and pair-completes when the loop terminates.
core.work.delegation.v1— Scribe's record that it has handed a goal off to a work loop.body.parent_loop_idreferences the spawned loop.core.work.delegation.v1— same shape, for the Guide actor.core.actor.usage.v1— daily per-actor aggregate (loop count, input/output tokens, cost). Emitted by the usage aggregator; consumed by the cost-preview endpoint to surface "you've used $X.YZ of your $Z.WW daily cap."
For the complete work-loop family (29 kinds total — including artifact, note, coercion, and the v0.39 scribe/guide call/response/error family) see the body-kinds manifest — the manifest is the canonical source, and stays byte-identical with the engine's registry.
Other built-in kinds
Pointers to the rest of the kind surface — full schemas live in their own reference pages.
core.workspace.v1+core.workspace_view.v1— a workspace (an interactive surface carried as a record) + per-thread-per-actor projection memory. See Workspaces.core.instance.shell.v1— an instance's Studio chrome (the navigation rail, side panels, and footer that frame every screen) carried as a record. Resolved atGET /v1/instance/shelland in theGET /v1/instance/bootstrapaggregate. See Instance chrome.core.credential.v1— secrets and external credentials. See Secrets.core.consent.v1— cross-namespace consent grants for federation. See Consent.core.routing_rule.v1/core.fold_rule.v1/core.health_rule.v1/core.aitl_rule.v1/core.permission_rule.v1/core.trigger.v1— CEL-driven engine config records. See CEL expressions and Routing rules.core.subscription.v1— Server-Sent-Events subscription manifests.core.invite.v1— pair-share-invite envelope onth_invites. Fields:invite_id,issuer_did,issuer_key_fp,scope_target(instance/thread:<id>/namespace:<ns>),scopes[],ttl_seconds,max_uses,accept_holder_types[](guest/federated/*),device_label?,notes?,signature. See Pair, share, invite and the API reference — pair, share, invite.core.invite.event.v1— invite lifecycle events onth_audit_invitesmirror. Discriminated byevent_kind:redeemed(carriesholder_type,holder_did?,holder_pubkey?,bearer_token_id?,bearer_sa_id?),revoked(carriesrevoker_actor,revoker_reason?),exhausted(auto-emitted whenuses_remainingreaches 0),refused(carriesrefusal_reason:expired/revoked/holder_type_mismatch/bad_proof/freshness_violation/key_unknown/consent_denied).core.invite_template.v1— operator's saved invite shapes onth_invite_templates. Fields:template_id,label,ttl_seconds,max_uses,scopes[],accept_holder_types[],notes?. Surfaces in/settings/devicestemplate picker.core.api_token.activity.v1— bearer last-use telemetry (v0.64 B2). Emitted by the token-indexmark_usedpath on every request authenticated by the token, joined into invite list rows viabearer_token_id. Used by the Last seen column on the operator panel.core.runtime.v1— pluggable runtime descriptor for extensions. See Extensions.core.artifact.v1— published artifact records.infer.query.v1— inference query records. Documented in detail at infer.query.v1 schema reference.
What's next
- CLI reference — commands that emit and query against these records.
- Dispatch observability guide — example queries +
spl task diagnosewalkthrough. infer.query.v1schema — the inference query body shape that pairs withinfer.error.v1.- body.kind manifest guide — the grammar all kinds (built-in or extension) follow.