TaskIO
A task's conversation is a sequence of inputs and outputs. Each entry is a TaskIO — the human-facing shape of one conversational message: the content exchanged, plus the structured values that accompanied it.
Shape
A TaskIO serializes as a flat object with well-known keys plus dynamic keys:
message: list[ContentPart] # the conversational content — see Content
received_at: str | None # UTC ISO 8601 — inputs only; when the input arrived
committed_at: str # UTC ISO 8601 — when this turn entered the conversation
{dynamic_key}: any # inputs: agent parameters · outputs: agent exposes
messageMUST be present on every TaskIO, carrying the conversational content parts.committed_atMUST be present on every TaskIO and MUST be the time the turn entered the conversation the model reads from.received_atMUST be present on every input and MUST be the time the runtime took delivery of it. It does not apply to an output, which the runtime produces rather than receives. The two instants are recorded separately because they are not the same moment — see Messages below.message,received_atandcommitted_atare well-known keys: an agent'sparametersandexposesschemas MUST NOT supply them.- An input TaskIO additionally carries the parameter keys from the agent's
parametersschema. Required parameters are always present; optional parameters are present if provided. - An output TaskIO carries the keys from the agent's
exposesschema. All declaredexposeskeys are always present.
Where TaskIO Appears
- In the task context, as
context.inputandcontext.output— CEL expressions read entries positionally (context.input[0].ticket_id). - In the messages projection below — the conversation as a task's consumers read it, polled alongside the task status.
Messages
The conversation is exposed as a single list of the task's TaskIO entries in commit order — the order in which the task actually took them — exactly as a transcript of what happened reads.
An input has two distinct instants, and a runtime MUST track both. It arrives when the runtime takes delivery of it, and it is committed when the runtime places it in front of the model. For an input sent while a turn is in flight these are not the same moment and may be a whole turn apart: a runtime is not obliged to interrupt a running turn (see Task Status, Interaction), so a queued input is committed whenever that runtime next calls the model — which, for a turn that answers without invoking a capability, is after that turn's output.
Commit order is therefore not send order, and a message can be listed after an output it was sent before. The list reports the difference rather than resolving it: every input carries received_at, so a consumer that wants to present the order the sender experienced has what it needs. Which order to present is the consumer's decision.
A message is the TaskIO exactly as the task holds it, plus one presentation field:
type: input | output
message: list[ContentPart]
received_at: str # inputs only — from the TaskIO
committed_at: str | None # from the TaskIO; absent on an input not yet committed
{dynamic_key}: any
typedistinguishes input entries from output entries. It is the only field this projection adds — the instants are the turn's own, so the conversation is readable from the task object and a runtime MUST NOT require its event history to serve this list.committed_atis absent exactly when the input has arrived but has not been committed. Its absence is the read receipt: while it is absent the model has not been shown the message, and a consumer MUST NOT infer that it has been from the message's position.- The list MUST be ordered by
committed_at, with the inputs that have arrived but not yet been committed after it — they have no commit instant, and will commit later than everything that has one. A runtime MUST setcommitted_aton every turn it commits, which is what makes ordering by instant total. - The ordering preserves the relative order of inputs and of outputs, but there is no index correspondence between the two. A turn commits every input queued at the moment it next calls the model, so one output MAY answer several inputs; and a turn whose execution is cancelled commits its input and produces no output at all. A consumer that needs to correlate a response with what prompted it MUST read
committed_at— an output answers every input committed after the preceding output and no later than itself — and MUST NOT count bytypeor infer correlation from a message's position.