Repository Public
1# Events and items23> For the complete documentation index, see [llms.txt](/llms.txt). Markdown versions of documentation pages are available by appending `.md` to the page URL.45Events report what happens as an agent works. Items are the saved messages and tool calls you can retrieve later. Use events to update your application in real time and items to display its saved history.6789101112Your application sends input events to submit messages, cancel turns, or return tool results. The agent sends events that report output and changes to the session. See [Run and continue sessions](https://developers.openai.com/api/docs/guides/agents-api/sessions) for sending input.1314151617## Consume a stream1819Subscribe before sending work so your application receives the turn's early events. Pass your API client, the conversation's session ID, and an event handler:2021Stream session events2223```javascript24// Pass your saved session ID to this helper.25async function streamSession(client, sessionId, handleEvent) {26 const events = await client.beta.agents.sessions.events.stream(sessionId);27 try {28 for await (const event of events) {29 await handleEvent(event);30 switch (event.type) {31 case "agent.session.idle":32 continue;33 case "error":34 throw new Error(event.error.message);35 case "agent.session.failed":36 case "agent.session.environment.failed":37 throw new Error(`Agent lifecycle failure: ${event.type}`);38 case "agent.session.turn.failed":39 if (event.turn.subagent_id === null) {40 throw new Error(41 `${event.type}: ${event.turn.error?.message ?? ""}`42 );43 }44 break;45 case "agent.session.turn.cancelled":46 if (event.turn.subagent_id === null) {47 throw new Error("The agent turn was cancelled");48 }49 break;50 case "agent.session.turn.completed":51 if (event.turn.subagent_id === null) return;52 break;53 }54 }55 throw new Error(56 "Stream closed before a turn ended. Retrieve the saved state."57 );58 } finally {59 events.controller.abort();60 }61}62```6364```python65# Pass your saved session ID to this helper.66def stream_session(client: OpenAI, session_id: str, handle_event):67 with client.beta.agents.sessions.events.stream(session_id) as events:68 for event in events:69 handle_event(event)70 match event.type:71 case "agent.session.idle":72 continue73 case "error":74 raise RuntimeError(event.error.message)75 case "agent.session.failed" | "agent.session.environment.failed":76 raise RuntimeError(f"Agent lifecycle failure: {event.type}")77 case "agent.session.turn.failed":78 if event.turn.subagent_id is None:79 detail = event.turn.error.message if event.turn.error else ""80 raise RuntimeError(f"{event.type}: {detail}")81 case "agent.session.turn.cancelled":82 if event.turn.subagent_id is None:83 raise RuntimeError("The agent turn was cancelled")84 case "agent.session.turn.completed":85 if event.turn.subagent_id is None:86 return87 raise RuntimeError("Stream closed before a turn ended. Retrieve the saved state.")88```8990```go91// Pass your saved session ID to this helper.92func streamSession(ctx context.Context, client *openai.Client, sessionID string, handleEvent func(openai.AgentSessionEventUnion)) error {93 events := client.Beta.Agents.Sessions.Events.StreamStreaming(ctx, sessionID)94 defer events.Close()95 for events.Next() {96 event := events.Current()97 handleEvent(event)98 switch event.Type {99 case "agent.session.idle":100 continue101 case "error":102 return fmt.Errorf("agent error: %s", event.RawJSON())103 case "agent.session.failed", "agent.session.environment.failed":104 return fmt.Errorf("agent lifecycle failure: %s", event.RawJSON())105 case "agent.session.turn.failed", "agent.session.turn.cancelled":106 if event.Turn.SubagentID == "" {107 return fmt.Errorf("agent turn did not complete: %s", event.RawJSON())108 }109 case "agent.session.turn.completed":110 if event.Turn.SubagentID == "" {111 return nil112 }113 }114 }115 if err := events.Err(); err != nil {116 return err117 }118 return fmt.Errorf("stream closed before a turn ended; retrieve the saved state")119}120```121122```java123// Pass your saved session ID to this helper.124public static void streamSession(125 OpenAIClient client, String sessionId, Consumer<AgentSessionEvent> handleEvent) {126 try (StreamResponse<AgentSessionEvent> events =127 client.beta().agents().sessions().events().streamStreaming(sessionId)) {128 var iterator = events.stream().iterator();129 while (iterator.hasNext()) {130 var event = iterator.next();131 handleEvent.accept(event);132 if (event.idle().isPresent()) {133 continue;134 }135 if (event.error().isPresent()) {136 throw new IllegalStateException("Agent error: " + event);137 }138 if (event.failed().isPresent() || event.environmentFailed().isPresent()) {139 throw new IllegalStateException("Agent lifecycle failure: " + event);140 }141 if (event.turnFailed().filter(e -> e.turn().subagentId().isEmpty()).isPresent()142 || event.turnCancelled().filter(e -> e.turn().subagentId().isEmpty()).isPresent()) {143 throw new IllegalStateException("Agent turn did not complete: " + event);144 }145 if (event.turnCompleted().filter(e -> e.turn().subagentId().isEmpty()).isPresent()) {146 return;147 }148 }149 throw new IllegalStateException(150 "Stream closed before a turn ended. Retrieve the saved state.");151 }152}153```154155```ruby156# Pass your saved session ID to this helper.157def stream_session(client, session_id, &handle_event)158 events = client.beta.agents.sessions.events.stream_streaming(session_id)159 begin160 events.each do |event|161 handle_event.call(event)162 case event.type.to_s163 when "agent.session.idle"164 next165 when "error"166 raise event.error.message167 when "agent.session.failed", "agent.session.environment.failed"168 raise "Agent lifecycle failure: #{event.type}"169 when "agent.session.turn.failed"170 raise "#{event.type}: #{event.turn.error&.message}" if event.turn.subagent_id.nil?171 when "agent.session.turn.cancelled"172 raise "The agent turn was cancelled" if event.turn.subagent_id.nil?173 when "agent.session.turn.completed"174 return nil if event.turn.subagent_id.nil?175 end176 end177 raise "Stream closed before a turn ended. Retrieve the saved state."178 ensure179 events.close180 end181end182```183184```bash185curl -N \186 "https://api.openai.com/v1/agents/sessions/$session_id/events?stream=true" \187 -H "OpenAI-Beta: agents=v1" \188 -H "Authorization: Bearer $OPENAI_API_KEY" \189 -H "Accept: text/event-stream"190```191192193The helper passes each event to your handler, then checks common event types. It continues on `agent.session.idle` and returns when the root turn completes. It raises an error if the root turn fails or is cancelled, the session or environment fails, or an `error` event arrives. Subagent turn events do not end the stream. Your handler decides how to display output; the caller handles errors from the helper. If the stream closes before a turn ends, the helper raises an error. See [Recover a disconnected stream](#how-to-recover-a-disconnected-stream).194195196197198<details>199<summary>Send a message after subscribing</summary>200201This version accepts a message and submits it after opening the stream:202203Send and stream a message204205```javascript206// Pass your saved session ID and message to this helper.207async function sendAndStream(client, sessionId, text, handleEvent) {208 const events = await client.beta.agents.sessions.events.stream(sessionId);209 try {210 await client.beta.agents.sessions.events.create(sessionId, {211 events: [212 {213 type: "agent.session.input.message",214 input: [{ role: "user", content: [{ type: "input_text", text }] }],215 },216 ],217 });218 for await (const event of events) {219 await handleEvent(event);220 switch (event.type) {221 case "agent.session.idle":222 continue;223 case "error":224 throw new Error(event.error.message);225 case "agent.session.failed":226 case "agent.session.environment.failed":227 throw new Error(`Agent lifecycle failure: ${event.type}`);228 case "agent.session.turn.failed":229 if (event.turn.subagent_id === null) {230 throw new Error(231 `${event.type}: ${event.turn.error?.message ?? ""}`232 );233 }234 break;235 case "agent.session.turn.cancelled":236 if (event.turn.subagent_id === null) {237 throw new Error("The agent turn was cancelled");238 }239 break;240 case "agent.session.turn.completed":241 if (event.turn.subagent_id === null) return;242 break;243 }244 }245 throw new Error(246 "Stream closed before a turn ended. Retrieve the saved state."247 );248 } finally {249 events.controller.abort();250 }251}252```253254```python255# Pass your saved session ID and message to this helper.256def send_and_stream(client: OpenAI, session_id: str, text, handle_event):257 with client.beta.agents.sessions.events.stream(session_id) as events:258 client.beta.agents.sessions.events.create(259 session_id,260 events=[261 {262 "type": "agent.session.input.message",263 "input": [264 {265 "role": "user",266 "content": [{"type": "input_text", "text": text}],267 }268 ],269 }270 ],271 )272 for event in events:273 handle_event(event)274 match event.type:275 case "agent.session.idle":276 continue277 case "error":278 raise RuntimeError(event.error.message)279 case "agent.session.failed" | "agent.session.environment.failed":280 raise RuntimeError(f"Agent lifecycle failure: {event.type}")281 case "agent.session.turn.failed":282 if event.turn.subagent_id is None:283 detail = event.turn.error.message if event.turn.error else ""284 raise RuntimeError(f"{event.type}: {detail}")285 case "agent.session.turn.cancelled":286 if event.turn.subagent_id is None:287 raise RuntimeError("The agent turn was cancelled")288 case "agent.session.turn.completed":289 if event.turn.subagent_id is None:290 return291 raise RuntimeError("Stream closed before a turn ended. Retrieve the saved state.")292```293294```go295// Pass your saved session ID and message to this helper.296func sendAndStream(ctx context.Context, client *openai.Client, sessionID string, text string, handleEvent func(openai.AgentSessionEventUnion)) error {297 events := client.Beta.Agents.Sessions.Events.StreamStreaming(ctx, sessionID)298 defer events.Close()299 if err := events.Err(); err != nil {300 return err301 }302 err := client.Beta.Agents.Sessions.Events.New(ctx,303 sessionID,304 openai.BetaAgentSessionEventNewParams{305 Events: []openai.AgentSessionInputParamUnion{306 {307 OfParamAgentSessionInputMessage: &openai.AgentSessionInputParamAgentSessionInputMessage{308 Input: []openai.AgentSessionInputMessageParam{309 {310 Content: []openai.InputContentParamUnion{311 {312 OfParamInputText: &openai.InputContentParamInputText{313 Text: text,314 },315 },316 },317 },318 },319 },320 },321 },322 })323 if err != nil {324 return err325 }326 for events.Next() {327 event := events.Current()328 handleEvent(event)329 switch event.Type {330 case "agent.session.idle":331 continue332 case "error":333 return fmt.Errorf("agent error: %s", event.RawJSON())334 case "agent.session.failed", "agent.session.environment.failed":335 return fmt.Errorf("agent lifecycle failure: %s", event.RawJSON())336 case "agent.session.turn.failed", "agent.session.turn.cancelled":337 if event.Turn.SubagentID == "" {338 return fmt.Errorf("agent turn did not complete: %s", event.RawJSON())339 }340 case "agent.session.turn.completed":341 if event.Turn.SubagentID == "" {342 return nil343 }344 }345 }346 if err := events.Err(); err != nil {347 return err348 }349 return fmt.Errorf("stream closed before a turn ended; retrieve the saved state")350}351```352353```java354// Pass your saved session ID and message to this helper.355public static void sendAndStream(356 OpenAIClient client, String sessionId, String text, Consumer<AgentSessionEvent> handleEvent) {357 try (StreamResponse<AgentSessionEvent> events =358 client.beta().agents().sessions().events().streamStreaming(sessionId)) {359 client360 .beta()361 .agents()362 .sessions()363 .events()364 .create(365 EventCreateParams.builder()366 .sessionId(sessionId)367 .addEvent(368 AgentSessionInputParam.AgentSessionInputMessage.builder()369 .addInput(370 AgentSessionInputMessageParam.builder()371 .addInputTextContent(text)372 .build())373 .build())374 .build());375 var iterator = events.stream().iterator();376 while (iterator.hasNext()) {377 var event = iterator.next();378 handleEvent.accept(event);379 if (event.idle().isPresent()) {380 continue;381 }382 if (event.error().isPresent()) {383 throw new IllegalStateException("Agent error: " + event);384 }385 if (event.failed().isPresent() || event.environmentFailed().isPresent()) {386 throw new IllegalStateException("Agent lifecycle failure: " + event);387 }388 if (event.turnFailed().filter(e -> e.turn().subagentId().isEmpty()).isPresent()389 || event.turnCancelled().filter(e -> e.turn().subagentId().isEmpty()).isPresent()) {390 throw new IllegalStateException("Agent turn did not complete: " + event);391 }392 if (event.turnCompleted().filter(e -> e.turn().subagentId().isEmpty()).isPresent()) {393 return;394 }395 }396 throw new IllegalStateException(397 "Stream closed before a turn ended. Retrieve the saved state.");398 }399}400```401402```ruby403# Pass your saved session ID and message to this helper.404def send_and_stream(client, session_id, text, &handle_event)405 events = client.beta.agents.sessions.events.stream_streaming(session_id)406 begin407 client.beta.agents.sessions.events.create(408 session_id,409 events: [410 {411 type: "agent.session.input.message",412 input: [413 {414 role: "user",415 content: [416 {417 type: "input_text",418 text: text419 }420 ]421 }422 ]423 }424 ]425 )426 events.each do |event|427 handle_event.call(event)428 case event.type.to_s429 when "agent.session.idle"430 next431 when "error"432 raise event.error.message433 when "agent.session.failed", "agent.session.environment.failed"434 raise "Agent lifecycle failure: #{event.type}"435 when "agent.session.turn.failed"436 raise "#{event.type}: #{event.turn.error&.message}" if event.turn.subagent_id.nil?437 when "agent.session.turn.cancelled"438 raise "The agent turn was cancelled" if event.turn.subagent_id.nil?439 when "agent.session.turn.completed"440 return nil if event.turn.subagent_id.nil?441 end442 end443 raise "Stream closed before a turn ended. Retrieve the saved state."444 ensure445 events.close446 end447end448```449450451</details>452453454455456457458459460## Handle updates461462Use the event's `type` to decide what your application should do:463464- **Display text:** Append `agent.session.turn.output_text.delta` to the relevant content part. When `agent.session.turn.output_text.done` arrives, replace that part with its complete text. Deltas may be absent.465- **Track work:** Session, turn, and item events report progress. Check for `agent.session.turn.completed`, `agent.session.turn.failed`, or `agent.session.turn.cancelled` to determine the turn's outcome.466- **Provide required input:** On `agent.session.requires_action`, retrieve the session and inspect `required_actions`. Your code may need to return a function result or connect an environment.467468An idle session or a closed stream alone does not establish success. A completed turn also does not guarantee that every tool succeeded. Inspect the agent's output.469470Use `item_id`, `output_index`, and `content_index` to connect text updates to the same content part. For example, these abbreviated events update one part:471472```json473{474 "type": "agent.session.turn.output_text.delta",475 "item_id": "msg_789",476 "output_index": 0,477 "content_index": 0,478 "delta": "Acme competes"479}480```481482```json483{484 "type": "agent.session.turn.output_text.done",485 "item_id": "msg_789",486 "output_index": 0,487 "content_index": 0,488 "text": "Acme competes on price and distribution."489}490```491492Each event has its own `event_id`. The shared `item_id` identifies the saved item, which includes the message's content, status, and phase. See [Retrieve saved work](https://developers.openai.com/api/docs/guides/agents-api/sessions#retrieve-session-items).493494See the [streaming events reference](https://developers.openai.com/api/reference/resources/beta/subresources/agents/streaming-events) for all event types and fields. These stream events are distinct from [webhooks](https://developers.openai.com/api/docs/guides/agents-api/sessions/webhooks). For subagent activity and command attribution, see [Observe delegation](https://developers.openai.com/api/docs/guides/agents-api/multi-agent#observe-delegation).495496## Fetch items and turns497498Use the session ID from your application's conversation state to retrieve saved work:499500- **Session items:** [List items](https://developers.openai.com/api/reference/resources/beta/subresources/agents/subresources/sessions/subresources/items/methods/list) to retrieve the root agent's messages and tool calls across turns.501- **Turns:** [List turns](https://developers.openai.com/api/reference/resources/beta/subresources/agents/subresources/sessions/subresources/turns/methods/list) to browse the session's work. [Retrieve a turn](https://developers.openai.com/api/reference/resources/beta/subresources/agents/subresources/sessions/subresources/turns/methods/retrieve) by ID to inspect its status, timestamps, usage, and error.502- **Items from one turn:** For a root-agent turn, filter session items by `turn_id`. Each subagent has its own [item history](https://developers.openai.com/api/reference/resources/beta/subresources/agents/subresources/sessions/subresources/subagents/subresources/items/methods/list) and a [per-turn items endpoint](https://developers.openai.com/api/reference/resources/beta/subresources/agents/subresources/sessions/subresources/subagents/subresources/turns/subresources/items/methods/list).503504List endpoints return one page at a time. Use SDK pagination helpers or the `after` cursor to retrieve more results. A single page may not contain every item for a turn. Use `order: "asc"` to read items from oldest to newest.505506## How to recover a disconnected stream507508Streams do not replay missed events. To restore your application's view:5095101. Open a new stream and buffer incoming events.5112. Retrieve the session and its saved items while the stream stays connected.5123. Restore your local state from those items, keyed by item ID.5134. Apply buffered item updates using `item_id`. Discard updates for items that already reached their final state in the retrieved history.5145. Resume handling live events.515516An `output_text.done` event can replace a temporary text buffer with the complete text. Saved items let you recover completed work, but not every intermediate event you missed.