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:

  • id identifies the output item in the response;
  • callId correlates a function call with its result;
  • outputIndex places an item in ordered response output; and
  • contentIndex places 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_progress after 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.0 common 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.

NoteEvents are evidence; snapshots are state

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:

  1. API resemblance: a route or JSON object looks like /v1/responses.
  2. Implemented compatible subset: named clients and fixtures work within a documented local profile.
  3. Official-suite result: a pinned runner reports exact passes, failures, and skips against an exact target.
  4. 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:

  1. one message with several text deltas;
  2. two output items with distinct indexes;
  3. a function call and correlated result;
  4. a gap or duplicate sequence number;
  5. a delta before item creation;
  6. completion followed by another event;
  7. incomplete and failed terminal states; and
  8. 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

  1. Specify whether duplicate sequence numbers are ignored, rejected, or deduplicated, and justify the replay consequences.
  2. Add a citation content part without flattening it into text.
  3. Define how a cancelled response differs from an incomplete model response in your application.
  4. Write an invariant that detects a function result with no matching call.
  5. 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 uses v0.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.
“Open Responses Reference 2026-04-24.” n.d. Accessed October 7, 2026. https://www.openresponses.org/reference/2026-04-24.
“Open Responses Specification 2026-04-24.” n.d. Accessed October 7, 2026. https://www.openresponses.org/specification/2026-04-24.