Message envelope¶
The shape of a Task envelope as it is persisted in SQLite, returned by the broker layer, and rendered by the CLI.
This document covers the canonical envelope shape: the broker constructs it, the CLI renders it, and the WebUI consumes it.
Persisted shape (typed columns)¶
Every message lives in tasks as a flat row of typed columns. There is no JSON blob — tasks.text carries the message body and the remaining typed columns carry the routing and lifecycle fields.
| Column | Type | Meaning |
|---|---|---|
task_id |
INTEGER |
Primary key (DB-assigned, never reused). Identifies a single delivery (unicast row, broadcast delivery row, or broadcast summary row). |
context_id |
INTEGER (agents.agent_id FK) |
The recipient agent for unicast/broadcast deliveries; the broadcaster for broadcast_summary; the preserved original context_id for ACK/cancel. The inbox-listing index idx_tasks_context_status_ts keys on (context_id, status_timestamp DESC). |
from_agent_id |
INTEGER (agent_id) |
Sender. Not a foreign key — historical tasks may outlive their sender. |
to_agent_id |
INTEGER (agent_id) |
Recipient. 0 for broadcast_summary rows (real ids are >= 1, so 0 never collides). |
type |
TEXT |
"unicast" or "broadcast_summary". Drives render-time decisions (e.g. summary tasks always emit in full; unicast tasks honor CAFLEET_MAX_TEXT_LEN truncation). |
created_at |
TEXT (ISO-8601, microsecond precision) |
Set at insert time, never updated. |
status_state |
TEXT |
input_required (queued), completed (acked), canceled (retracted). |
status_timestamp |
TEXT (ISO-8601, microsecond precision) |
Updated on every state change. Used for ORDER BY DESC and for member list --activity aggregation. |
origin_task_id |
INTEGER (nullable) |
Broadcast grouping link. NULL on unicast deliveries; on broadcast delivery rows holds the summary task's task_id; on the broadcast summary row itself self-references its own task_id. |
text |
TEXT |
Message body. For broadcast_summary rows, the broker writes the human-readable summary "Broadcast sent to N recipients" (computed at insert time). |
The persisted shape is the canonical source of truth. Every render the broker produces is a projection of these columns.
See data-model.md for the full SQL schema (including indexes and foreign-key declarations).
Rendered shape¶
The broker's read paths return the persisted columns as a flat dict (the typed-column dict), and the CLI projects that dict into a compact rendered envelope — by default the rendered envelope omits the columns whose values are constant or recoverable from context. The --full flag returns the typed-column dict unmodified.
Compact rendered envelope (default)¶
Field decisions:
| Field | Default | --full |
|---|---|---|
task_id |
rendered as id (full integer) |
rendered as task_id |
from_agent_id |
rendered as from (full integer) |
rendered as from_agent_id |
to_agent_id |
omitted (the recipient's own poll already establishes to == self) |
included |
context_id |
omitted (always equals to_agent_id for delivery rows; equals broadcaster for summary rows) |
included |
status_timestamp |
rendered as ts |
rendered as status_timestamp |
text |
included, truncated to CAFLEET_MAX_TEXT_LEN codepoints + … suffix |
included, untruncated |
type |
omitted when "unicast" (the default); rendered as kind when "broadcast_summary" |
rendered as type |
created_at |
omitted | included |
status_state |
omitted when "input_required" (the default for fresh deliveries) |
included |
origin_task_id |
rendered as origin (full integer) when non-NULL; omitted on unicast deliveries |
included |
JSON output¶
CLI JSON output is governed by the --json flag:
| Mode | Output |
|---|---|
--json |
Compact single-line JSON — no whitespace; non-ASCII (e.g. the … suffix) is emitted as UTF-8, not escaped. |
| (text mode) | Two lines per task in the compact rendered shape; a variable-length labeled block per task in --full. |
Examples¶
A poll result with one unicast delivery (id 42, from 7, body "build OK").
Default (cafleet --json message poll --agent-id <my-agent-id>):
--full (cafleet --json message poll --agent-id <my-agent-id> --full):
[
{
"task_id": 42,
"context_id": 3,
"from_agent_id": 7,
"to_agent_id": 3,
"type": "unicast",
"created_at": "2026-05-05T05:42:11.123456+00:00",
"status_state": "input_required",
"status_timestamp": "2026-05-05T05:42:11.123456+00:00",
"origin_task_id": null,
"text": "build OK"
}
]
Indented here for readability; the actual
--jsonoutput is a single compact line with no whitespace.--fullonly changes which fields are emitted, never the encoding.
A broadcast summary row carries kind: "broadcast_summary" (or type in --full) and origin: <id> (self-referencing); the text body is the broker-computed summary string "Broadcast sent to N recipients". The message broadcast response always contains exactly this single summary task plus the wrapper-level notifications_sent_count field — there is no per-recipient envelope list. --full renders that single summary task in full (verbose envelope / typed-column dict) instead of the one-line summary, but never adds per-recipient envelopes (see --full semantics for the cross-subcommand summary).
Text mode¶
Text mode renders each task as two lines (line 1 is the bracketed envelope, line 2 is the body):
Optional segments | kind:<kind> and | origin:<id> are appended to line 1 when the task is a broadcast summary (type != "unicast") or has a non-NULL origin_task_id, respectively. The body line is omitted entirely when the resulting body is the empty string.
--full switches to a variable-length labeled block — one field per line (id, state, from, to, type, text), with the to: line omitted for broadcast-summary rows (to_agent_id == 0) and the text: line omitted when the body is empty. So a fresh unicast delivery prints six lines, while a broadcast-summary row with no recipient prints fewer. Text mode omits the text: line entirely only when the resulting body is the empty string (deliveries explicitly sent with an empty body). Broadcast summary rows are NOT empty — the broker writes the human-readable summary "Broadcast sent to N recipients" at insert time, so summary rows always render their text: line. Body truncation (the … suffix at CAFLEET_MAX_TEXT_LEN codepoints) is documented in cli-options.md.
Flag cross-reference¶
The flags that govern envelope rendering are documented in cli-options.md:
--json— emit JSON output (compact).--full— return the full typed-column envelope and untruncated body.--quiet— onmessage send/message ack/member ping, emit only the new task id.
CAFLEET_MAX_TEXT_LEN (default 200) controls body truncation in the rendered envelope; it is documented under Message Body Truncation.