6 Open Responses: Requests, Items, Events, and Conformance
Part II · The portable model runtime
Design pin: Open Responses 2026-04-24 · Official suite pin: 92c12d96 · Current classification: not conformant
A completed string is too small a model for an agent response. One response can contain messages, tool calls, structured data, and failures, each of which changes state while it streams. Items preserve meaning; events preserve change.
An item is an identifiable unit in a response trajectory. An event reports a state transition or incremental update. A snapshot is the materialized state after applying an ordered event prefix.
6.1 From chat turns to trajectories
The familiar chat abstraction is an ordered list of role/content messages. It works for simple text, but an agent run needs more structure. A model can emit a message, request a function, receive its result, continue with another message, and terminate incompletely. Flattening that trajectory into text destroys correlation and lifecycle information.
A response-oriented model separates:
- input items, supplied by the caller;
- output items, produced during the response;
- semantic events, emitted while output changes; and
- a terminal response object, which summarizes the authoritative outcome.
Open Responses specifies typed items and semantic events, including output-item, content-part, and text-delta lifecycles (“Open Responses Specification 2026-04-24,” n.d.). This chapter uses those concepts as a design target, not as a claim that the current reference implementation is conformant.
6.2 Requests carry policy as well as input
A useful request is more than a prompt:
Architecture sketch — not the exact reference API:
interface ModelRequest {
model: string;
input: InputItem[];
tools?: ToolDefinition[];
maxOutputTokens?: number;
signal?: AbortSignal;
}The exact production shape will grow, but each field already has policy consequences. The model identifier selects capability and cost. Tool definitions expose authority. The token limit changes incomplete-response behavior. The signal defines cancellation ownership.
Keep a transcript as typed input items rather than concatenating prose. A message, function call, and function result are different facts even if a provider eventually serializes all three into one request body.
6.3 Items preserve identity
A reducer needs enough identity to correlate updates within the response and replay scope. Distinguish item type, item identifier, call identifier where applicable, and ordered indexes without assuming that every identifier is globally stable. Stream events often carry an output index, item identifier, and content index.
Protocol-derived architecture types — not the exact reference API:
interface MessageItem {
id: string;
type: "message";
role: "assistant";
status: "in_progress" | "completed" | "incomplete";
content: OutputTextPart[];
}
interface FunctionCallItem {
id: string;
type: "function_call";
callId: string;
name: string;
arguments: string;
status: "in_progress" | "completed" | "incomplete";
}These identifiers have different jobs:
ididentifies the output item in the response;callIdcorrelates a function call with its result;outputIndexplaces an item in ordered response output; andcontentIndexplaces a content part inside an item.
Do not substitute array position for identity. Retries, replay, parallel calls, and incremental rendering all need stable references.
6.4 Items are state machines
In the pinned Open Responses specification, generated items move through states such as in_progress, completed, and incomplete (“Open Responses Specification 2026-04-24,” n.d.). A completed item is closed to further mutation. An incomplete item is also terminal, but it tells the application that output ended before normal completion.
A portable reducer should enforce monotonic state:
not present ──item added──► in_progress ──done────────► completed
│
└──interrupted─────► incomplete
Invalid transitions include:
- applying a text delta before its item and content part exist;
- adding content after an item is completed;
- completing the same item twice;
- changing an item identifier at the same output index; and
- accepting a nonterminal update after the response terminates.
Rejecting invalid transitions is not pedantry. It prevents a malformed or duplicated stream from silently corrupting persisted state.
6.5 Semantic events
Transport chunks are not application events. One network chunk can contain several server-sent events, and one JSON event can arrive across several chunks. Decode transport framing first; then process semantic events.
A text item can follow a lifecycle like this:
response.output_item.added
response.content_part.added
response.output_text.delta (zero or more)
response.output_text.done
response.content_part.done
response.output_item.done
response.completed | response.incomplete | response.failed
The specification describes state-transition events and delta events and requires monotonically increasing sequence_number values in the stream (“Open Responses Specification 2026-04-24,” n.d.). Sequence numbers let a consumer establish order and detect gaps. They do not make arbitrary reordering safe: a missing “item added” event cannot be repaired by blindly applying a later delta.
6.6 A reducer, not a callback pile
Model the consumer as a deterministic reducer. The following application-level implementation is executable, not a transcription of the Open Responses schema. Its complete source is code/foundations.ts, and deterministic tests cover normal completion, replay, sequence gaps, premature deltas, text mismatch, and EOF without a terminal event.
export type ResponseEvent =
| { type: "response-start"; sequence: number; responseId: string }
| { type: "text-start"; sequence: number; itemId: string; outputIndex: number }
| { type: "text-delta"; sequence: number; itemId: string; delta: string }
| { type: "text-done"; sequence: number; itemId: string; text: string }
| {
type: "tool-call";
sequence: number;
itemId: string;
outputIndex: number;
callId: string;
name: string;
input: unknown;
}
| {
type: "response-end";
sequence: number;
status: "completed" | "incomplete" | "failed" | "cancelled";
usage?: Usage;
};
export type OutputItem =
| {
type: "message";
id: string;
outputIndex: number;
status: "in_progress" | "completed";
text: string;
}
| {
type: "tool-call";
id: string;
outputIndex: number;
status: "completed";
callId: string;
name: string;
input: unknown;
};
export type ResponseState = {
readonly responseId?: string;
readonly lastSequence: number;
readonly status: "in_progress" | "completed" | "incomplete" | "failed" | "cancelled";
readonly output: readonly OutputItem[];
readonly usage?: Usage;
};
export const initialResponseState = (): ResponseState => ({
lastSequence: 0,
status: "in_progress",
output: [],
});
const invariant = (condition: boolean, message: string): void => {
if (!condition) throw new Error(message);
};
export const applyResponseEvent = (state: ResponseState, event: ResponseEvent): ResponseState => {
invariant(state.status === "in_progress", "event after terminal response");
invariant(event.sequence === state.lastSequence + 1, "non-contiguous sequence number");
switch (event.type) {
case "response-start":
invariant(state.responseId === undefined && state.output.length === 0, "duplicate start");
return { ...state, responseId: event.responseId, lastSequence: event.sequence };
case "text-start":
invariant(state.responseId !== undefined, "response has not started");
invariant(
!state.output.some(
(item) => item.id === event.itemId || item.outputIndex === event.outputIndex,
),
"duplicate item identity",
);
return {
...state,
lastSequence: event.sequence,
output: [
...state.output,
{
type: "message",
id: event.itemId,
outputIndex: event.outputIndex,
status: "in_progress",
text: "",
},
],
};
case "text-delta": {
const item = state.output.find((candidate) => candidate.id === event.itemId);
invariant(item?.type === "message" && item.status === "in_progress", "invalid delta");
const message = item as Extract<OutputItem, { type: "message" }>;
return {
...state,
lastSequence: event.sequence,
output: state.output.map((candidate) =>
candidate.id === message.id
? { ...message, text: message.text + event.delta }
: candidate,
),
};
}
case "text-done": {
const item = state.output.find((candidate) => candidate.id === event.itemId);
invariant(item?.type === "message" && item.status === "in_progress", "invalid completion");
const message = item as Extract<OutputItem, { type: "message" }>;
invariant(message.text === event.text, "final text differs from accepted deltas");
return {
...state,
lastSequence: event.sequence,
output: state.output.map((candidate) =>
candidate.id === message.id ? { ...message, status: "completed" } : candidate,
),
};
}
case "tool-call":
invariant(state.responseId !== undefined, "response has not started");
invariant(
!state.output.some(
(item) =>
item.id === event.itemId ||
item.outputIndex === event.outputIndex ||
(item.type === "tool-call" && item.callId === event.callId),
),
"duplicate tool-call identity",
);
return {
...state,
lastSequence: event.sequence,
output: [
...state.output,
{
type: "tool-call",
id: event.itemId,
outputIndex: event.outputIndex,
status: "completed",
callId: event.callId,
name: event.name,
input: event.input,
},
],
};
case "response-end":
invariant(state.responseId !== undefined, "response has not started");
invariant(
event.status !== "completed" || !state.output.some((item) => item.status === "in_progress"),
"completed response contains an open item",
);
return {
...state,
lastSequence: event.sequence,
status: event.status,
...(event.usage === undefined ? {} : { usage: event.usage }),
};
}
};
export const reduceResponseEvents = (events: Iterable<ResponseEvent>): ResponseState => {
let state = initialResponseState();
for (const event of events) state = applyResponseEvent(state, event);
invariant(state.status !== "in_progress", "stream ended without a terminal event");
return state;
};Pure reduction has practical benefits:
- the same fixture always produces the same snapshot;
- replay can reconstruct state without rerunning tools;
- property tests can generate malformed orders;
- UI projection can be tested separately; and
- persistence can checkpoint an event prefix.
An implementation may mutate an internal builder for performance, but it should preserve reducer semantics at its public boundary.
6.7 Terminal states
A stream ending is not the same as a response completing. EOF can result from success, cancellation, network loss, proxy timeout, or a crashed server. Require an explicit terminal outcome.
A useful portable terminal model distinguishes:
| Terminal state | Meaning | May partial output be shown? | May it be treated as final? |
|---|---|---|---|
completed |
Normal terminal response | Yes | Yes |
incomplete |
Model stopped before full completion | Yes, labeled | No, unless policy accepts it |
failed |
Provider or protocol failure | Possibly, labeled | No |
cancelled |
Caller or system stopped work | Possibly, labeled | No |
cancelled is an application-level normalization in this chapter, not a claim that every provider or Open Responses uses the same terminal representation. Preserve the raw reason in protected diagnostics, not in a public error body.
6.8 Snapshot consistency
A terminal snapshot should agree with the event history. At completion:
- every completed text part equals the concatenation of its accepted deltas or its authoritative done value;
- every item occupies one output index;
- each function result refers to a known call identifier;
- no item remains
in_progressafter a completed response; - terminal status and finish reason agree with policy; and
- unknown usage remains explicitly unknown rather than being converted to zero. Both the tested chapter reducer and the
v0.4.0common loop preserve absent input/output token counts; measured zero remains distinct from absence.
The terminal response object may contain a more authoritative final representation than accumulated deltas. Compare the two. A mismatch is a protocol or adapter defect, not an invitation to silently select whichever value is convenient.
Persisting only the snapshot is compact. Persisting an approved event log supports replay and diagnosis. If events contain sensitive content, retention and redaction rules apply to both forms.
6.9 Unknown and extension events
Evolving protocols add item and event types. A portable consumer needs an explicit compatibility policy:
- reject unknown events in strict conformance mode;
- retain and forward namespaced extensions when the application supports them;
- ignore only events documented as nonessential; and
- emit telemetry when an unknown type is observed.
Never coerce an unknown item into text. That can turn a control or safety signal into user-visible content while losing its semantics.
6.10 SDK events and the HTTP protocol are different contracts
The immutable v0.4.0 model loop consumes normalized AI SDK stream parts rather than raw Open Responses events. It maps text, tool calls, explicit tool-result outcomes, finish reasons, optional usage, response identifiers, and sanitized errors. Provider packages own provider-wire translation.
The release’s application reducer, the shared model stream, and the current Open Responses HTTP bridge are three separate APIs. The reducer checks item identity, contiguous local sequence, closed-item updates, final-text agreement, and terminal presence. The HTTP bridge translates the application’s supported profile into Open Responses-shaped JSON/SSE resources. None is automatically a field-for-field implementation of every specification item and event.
| Concept | Specification target | Local application/runtime | Current HTTP profile |
|---|---|---|---|
| Request input | Typed ordered items/content | Typed messages, history, tools, and ordered admitted content | Strict supported subset; unknown supported-object fields and unknown parts reject |
| Item identity | Output item IDs, call IDs, indexes | Correlated calls/results and checked reducer identity | Stable response/items for implemented text/function paths |
| Ordering | Monotonic stream sequence | SDK order plus local reducer sequence | Contiguous emitted sequence for the implemented SSE profile |
| Terminal outcome | Completed, incomplete, or failed response resources/events | Normalized finish/error/cancellation semantics | Schema-shaped terminal resources for supported paths |
| Usage | Optional measured details | Unknown totals remain absent | null when required totals are unavailable; no invented top-level count |
Correspondence establishes an implementation approach, not conformance.
6.11 Four different status statements
Use four labels precisely:
- API resemblance: a route or JSON object looks like
/v1/responses. - Implemented compatible subset: named clients and fixtures work within a documented local profile.
- Official-suite result: a pinned runner reports exact passes, failures, and skips against an exact target.
- Formal conformance: the implementation satisfies the declared required profile; this status is reserved until the required suite and lifecycle behavior pass.
The current server has a useful HTTP/JSON/SSE subset and a Pi 1.0.4-compatible path. The opt-in wrapper runs the official CLI from immutable revision 92c12d96d7b61d6d15e2214daa5e9c6000ab6e1c and preserves its failing exit status. The latest reviewed complete run reports 8 passed, 9 failed, 0 skipped overall; seven passes and all nine failures contacted the HTTP target.
The remaining failures are three compaction cases and six WebSocket lifecycle/state cases. /v1/responses/compact is absent, and the required WebSocket chains, continuation, reconnect recovery, missing-state behavior, and failed-chain eviction are not implemented. Therefore the classification is compatible subset; not conformant.
Security limits and strict local input rules are documented restrictions. They must not be weakened merely to increase a suite score. Conversely, passing only schema examples would not establish compaction quality, tenant isolation, durable state, concurrency bounds, production WebSocket hardening, or semantic equivalence. Chapter 19 owns the final dated report; this chapter owns the protocol vocabulary used to interpret it.
6.12 Security considerations
Items can contain untrusted text, URLs, citations, tool arguments, and opaque provider metadata. Treat every field as data:
- render text with output encoding appropriate to its destination;
- validate tool arguments before execution;
- constrain extension sizes and nesting;
- do not deserialize provider metadata into executable classes;
- bound retained events and snapshots; and
- avoid exposing raw upstream failures to callers.
Stable item identity also supports auditability: a policy decision can refer to the exact call, result, or message it evaluated.
6.13 How we test the model
A deterministic item/event test suite should cover:
- one message with several text deltas;
- two output items with distinct indexes;
- a function call and correlated result;
- a gap or duplicate sequence number;
- a delta before item creation;
- completion followed by another event;
- incomplete and failed terminal states; and
- an unknown extension under strict and permissive policies.
Protocol conformance tests should remain separate from reducer unit tests. The first proves local invariants; the second proves behavior against a named external specification revision.
6.14 Portability and evidence ledger
| Dimension | Chapter claim | Evidence | Not established |
|---|---|---|---|
| Transport | Semantic reduction is independent of byte framing | Book and v0.4.0 reducers accept application events |
Complete adversarial HTTP/SSE parsing coverage |
| Provider | One application event model preserves selected meanings | v0.4.0 normalized calls, results, finish, and usage |
Complete provider item fidelity |
| Protocol | The target derives from Open Responses 2026-04-24 | Pinned specification and correspondence table | Conformance |
| Feature | Text and function-call identity survive reduction | Book and tagged reducer tests for identity, sequence, and terminal consistency | Citations, reasoning, images, and extensions |
| Semantic | Replaying events preserves state, not answer quality | Replay equality test | Cross-model answer equivalence |
6.15 Pattern summary
Problem: Flattening a response to text loses identity, lifecycle, tool correlation, and terminal meaning.
Pattern: Represent response output as typed stateful items, apply ordered semantic events through a checked reducer, and reconcile the terminal snapshot.
Result: Streaming, replay, persistence, tools, and diagnostics can share one coherent model.
Cost: The implementation must enforce lifecycle invariants and evolve with new item and event types.
6.16 Exercises
- Specify whether duplicate sequence numbers are ignored, rejected, or deduplicated, and justify the replay consequences.
- Add a citation content part without flattening it into text.
- Define how a cancelled response differs from an incomplete model response in your application.
- Write an invariant that detects a function result with no matching call.
- Decide what your strict and permissive modes do with a namespaced extension event.
6.17 Further reading
Read the pinned Open Responses specification’s sections on semantic events, item state machines, streaming, and extensions (“Open Responses Specification 2026-04-24,” n.d.), then compare the generated field definitions in its reference (“Open Responses Reference 2026-04-24,” n.d.). Keep architectural use separate from a formal conformance claim.
6.18 Chapter contract
- Scope: Open Responses request, item, event, lifecycle, and conformance vocabulary.
- Canonical claims: reducers require correlated items and exactly one terminal interpretation.
- Migration inputs: former responses-as-items-and-events chapter.
- Evidence gates: schema fixtures, reducer tests, and official conformance output.
- Demonstration slots: deterministic event trace.
- Source-claim ledger: design uses the 2026-04-24 protocol artifact; official results use suite revision
92c12d96; adapter reduction usesv0.4.0; each remains a separate claim. - Exclusions: provider translation belongs to Chapter 5, transport backpressure to Chapter 7, tool authority to Chapter 8, durable continuation to Chapter 15, and final release classification to Chapter 19.