Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 43 additions & 15 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -973,10 +973,11 @@ lock, and return terminal storage outcomes without rolling their transaction bac

Initial messages for a newly created Environment-bearing Session use that same
reservation in the creation transaction, including its connection-action event.
The creation winner alone inserts it; the original pre-work snapshot and stream
cursor remain unchanged. A durable initial/later flag defaults historical rows to
later input without inferring origin. Initial expiry projects a failed Session and
safe error before any Turn exists; later expiry retains idle semantics. Failure
The creation winner alone inserts it; the stream cursor still precedes that
event, and creation retries never re-insert it. A durable initial/later flag
defaults historical rows to later input without inferring origin. Initial expiry
projects a failed Session and safe error before any Turn exists; later expiry
retains idle semantics. Failure
events capture the settled activity and Usage atomically. Late connections and
creation retries cannot reset or replay expired input, and newer work supersedes
old activity without changing its event snapshots. The Environment itself is not
Expand All @@ -987,8 +988,9 @@ immediate Turn admission. Cancellation/deletion keep their existing semantics.
Ordinary and streamed public self-hosted creation accept initial text through this
transaction after configuration and new-work lease checks. They return the owned
Environment ID and executor URL while offline, without waiting for admission.
The creation stream sends its original pre-work `created` snapshot before the
committed connection action. A disconnected observer leaves committed input intact;
The creation stream's `created` snapshot is the committed JSON 201 projection,
including the connection action; the committed action event then follows from the
creation cursor. A disconnected observer leaves committed input intact;
only the existing Worker prepares, promotes and starts it. Saved-Agent retries with recorded intent
recover before fresh execution admission or source resolution; inline retries keep
their existing resolved-snapshot validation.
Expand Down Expand Up @@ -2073,19 +2075,45 @@ replaced; do not carry obsolete compatibility code forward to satisfy this secti
Missing sequence positions produce a safe stream error and close; recover via
Session/Turn/Items queries. Socket writes have a five-second deadline and hold
no database connection. Client disconnect releases the handler; comments keep
idle connections alive. SSE does not close merely because one Turn finishes.
idle connections alive. GET SSE does not close merely because one Turn finishes;
only creation responses end on settlement (below).

- Session creation with `stream=true` reuses atomic input admission and the live
event loop. The upsert returns its cursor under the Session lock, before initial
inputs; never replace it with a post-commit cursor lookup. A new response emits
one request-local `agent.session.created` with the pre-input resource snapshot,
then committed changes from that cursor. The local creation retry key excludes
response mode: retries observe only later events and admit no work again. Retry
the same request/key with `stream=false` to recover a lost Session ID. GET event
streams retain their current live-only start. Disconnect never cancels admitted
work. Exact upstream created-snapshot timing, POST stream lifetime and creation
retry response semantics remain unverified; the separate SDK one-Turn helper
does not define this endpoint. Do not present local retry behavior as replay.
one request-local `agent.session.created` with the committed Session projection
that the JSON 201 response returns (read after the commit), then committed
changes from that cursor exactly once. A fresh creation stream ends right
after the first `agent.session.idle` recorded when a Turn ends or an input
reservation stops being pending (expired, cancelled or failed), or any
`agent.session.failed`, and never sends the events after it. A self-hosted
connection clearing pending input to idle, `requires_action`, function results
and resumed work keep it open. A creation that admitted nothing (no Turn or
reservation) ends right after `created`. Settlements that record no event use
a fallback: after an empty drain the stream reads the JSON-path projection and
the event cursor in one database snapshot and, if the Session is idle or failed
with no queued, running or waiting Turn and no pending reservation, sends only
events up to that cursor, then ends. Accepted follow-ups: another client's work
drained before that read can still be sent, and idles recorded by an older
binary during a rolling deploy carry no settled marker and rely on the
fallback. An input reservation made while the ending Turn captured Artifacts
can start a later Turn that the stream does not follow. The settled marker and
pending-input flag are Store-internal, never wire fields, and add no events.
Re-read the projection after a sent Session status event and otherwise at most
once a second. The local creation retry key excludes response mode; a same-key
`stream=true` retry of an existing creation returns 201 with only the
connection comment and ends at once, admitting nothing and following no work,
because official same-key requests create distinct Sessions. Retry the same
request/key with `stream=false`, or use the GET events stream, to recover. GET
event streams keep their live-only start and never end on settlement.
Disconnect never cancels admitted work. Official observations cover `none`
creation; self-hosted, hosted and no-input stream lifetimes and the retry
behavior are local choices, and the separate SDK one-Turn helper does not
define this endpoint. Do
not present local retry behavior as replay. A new Turn records `turn.created`,
its user input Items, then Session activity in one transaction. Terminal Turn
events carry top-level `usage` copied from their Turn snapshot, null when
unknown; never derive or sum it.

- Public Turn retrieve/list project persisted execution state and the immutable
Session Agent identity. Scope both resources and pagination cursors to the
Expand Down
24 changes: 15 additions & 9 deletions apps/web/e2e/fixture-core.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,7 @@ function emitTurnLifecycle(status) {
session_id: "session_snapshot",
turn_id: terminal.id,
turn: terminal,
usage: terminal.usage,
})}\n\n`;
for (const stream of streamResponses.keys()) stream.write(event);
return true;
Expand Down Expand Up @@ -1065,8 +1066,9 @@ const server = http.createServer(async (request, response) => {
"cache-control": "no-cache, no-transform",
connection: "keep-alive",
});
response.write(": fixture creation retry observes only future events\n\n");
setTimeout(() => response.end(), state.controls.sessionCreateStreamCloseDelayMs);
// Like Core, a same-key stream retry sends no events and ends at once;
// clients recover the Session with stream=false.
response.end(": connected\n\n");
return;
}
return sendJson(response, receipt.session, 201);
Expand Down Expand Up @@ -1121,7 +1123,6 @@ const server = http.createServer(async (request, response) => {
created_at: baseline + state.sequence,
last_active_at: baseline + state.sequence,
};
const createdSnapshot = structuredClone(created);
let initialTurn = null;
let initialItems = [];
if (hasInitialInput && initialInputMessages) {
Expand Down Expand Up @@ -1153,6 +1154,11 @@ const server = http.createServer(async (request, response) => {
created.status = "in_progress";
created.last_active_at = baseline + state.sequence;
}
// As in Core, the created event repeats this fixture's JSON 201 body. The
// fixture queues a Turn for any initial input, so both show in_progress;
// Core instead shows requires_action for self_hosted and idle while an
// openai_hosted Environment provisions.
const createdSnapshot = structuredClone(created);
state.sessions.unshift(created);
if (typeof idempotencyKey === "string") {
state.sessionCreateReceipts.set(idempotencyKey, { fingerprint, session: created });
Expand Down Expand Up @@ -1188,12 +1194,6 @@ const server = http.createServer(async (request, response) => {
turn_id: turn.id,
turn,
})}\n\n`);
response.write(`event: agent.session.in_progress\nid: progress_${state.sequence}\ndata: ${JSON.stringify({
type: "agent.session.in_progress",
event_id: `progress_${state.sequence}`,
session_id: created.id,
session: created,
})}\n\n`);
for (const [index, item] of items.entries()) {
response.write(`event: agent.session.turn.item.added\nid: item_${state.sequence}_${index + 1}\ndata: ${JSON.stringify({
type: "agent.session.turn.item.added",
Expand All @@ -1203,6 +1203,12 @@ const server = http.createServer(async (request, response) => {
item,
})}\n\n`);
}
response.write(`event: agent.session.in_progress\nid: progress_${state.sequence}\ndata: ${JSON.stringify({
type: "agent.session.in_progress",
event_id: `progress_${state.sequence}`,
session_id: created.id,
session: created,
})}\n\n`);
}
setTimeout(() => response.end(), state.controls.sessionCreateStreamCloseDelayMs);
return;
Expand Down
55 changes: 39 additions & 16 deletions contracts/agents-api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -776,27 +776,50 @@ express the string/array union, so input is unconstrained with a type descriptio
### Session creation streaming

`POST /v1/agents/sessions` also accepts `stream=true` for the supported creation
inputs. Fresh creation sends `agent.session.created` with the pre-input Session,
then its committed activity/Turn/Item/output events. Self-hosted initial creation
first requests the Environment connection, before native readiness and a Turn.
The cursor comes
from the atomic creation upsert, so rapid initial execution cannot move the start
past its own events. The ordinary bounded-buffer/gap policy still applies.
inputs. Fresh creation sends `agent.session.created` with the committed Session,
the same projection as the JSON 201 body (`in_progress` after `none` initial
input), then its committed activity/Turn/Item/output events. A new Turn publishes
`turn.created`, its user input `item.added`, `agent.session.in_progress`, then
`turn.in_progress`. Self-hosted initial creation shows and then emits the
Environment connection request, before native readiness and a Turn. The cursor
comes from the atomic creation upsert, so rapid initial execution cannot move the
start past its own events. The ordinary bounded-buffer/gap policy still applies.

A fresh creation stream ends right after the first `agent.session.idle` recorded
when a Turn ends or an input reservation stops being pending (expired, cancelled
or failed), or any `agent.session.failed`, and never sends the events after it. A
pinned-SDK loop over `sessions.create(..., stream=True)` therefore ends right
after the initial Turn's idle. `requires_action`, function results, resumed work
and a self-hosted connection clearing pending input keep it open, and a
provisioning or offline reservation keeps it open until a Turn settles or the
reservation expires or fails. A creation that admitted nothing ends right after
`created`. A settlement that records no event ends the stream after the events
up to the cursor read with a settled projection in one snapshot; another client's
work drained before that read can still be sent. Observe later Turns with the GET
event stream, which never ends on its own. Terminal Turn events carry the Turn
snapshot's `usage` at the top level, null when unknown.

The local `Idempotency-Key` creation extension shares identity across response
modes. Retrying creation streams only future changes and never resubmits input or
replays old events. Recover a lost Session ID by repeating the same request/key
with `stream=false`, then use Session/Turn/Items reads. Disconnect only stops the
HTTP observer; committed reservations and admitted execution continue. Idle streams remain open for later
Turns. Pinned SDK3.13.0 proves the creation stream and created-event schema; exact
upstream initial snapshot/order, POST stream lifetime and retry behavior have not
been compared with the hosted service. These choices are not full conformance.
modes. A same-key `stream=true` retry of an existing creation returns 201 with
only the connection comment and ends at once: it replays nothing, resubmits no
input and follows no work, since official same-key requests create distinct
Sessions. Recover a lost
Session ID by repeating the same request/key with `stream=false`, then use
Session/Turn/Items reads. Disconnect only stops the HTTP observer; committed
reservations and admitted execution continue. September 23 official `none`
observations match the created snapshot, the end at idle, the start order and the
terminal usage field ([evidence](history-events-usage.md#creation-stream-settlement-2026-09-23)).
Self-hosted, hosted and no-input creation stream lifetimes and the stream retry
behavior are local choices.
These choices are not full conformance.

`official_session_creation_stream.py` covers the pinned client and raw HTTP on
real PostgreSQL: idle/initial text and saved Agents, first snapshots and ordered
Items, retries across response modes, later Turns, disconnect recovery, isolation
real PostgreSQL: initial text and saved Agents, created snapshots equal to the JSON
201 body, Turn start order, terminal usage, the end at idle, GET continuation,
immediately ending stream retries and JSON retries, disconnect recovery, isolation
and errors before stream headers. Store tests cover concurrent upsert ownership,
pre-admission cursors and observers draining after execution has completed.
pre-admission cursors, post-admission projections and observers draining after
execution has completed; API tests cover the stream lifetimes.

## Acceptance evidence and remaining scope

Expand Down
12 changes: 9 additions & 3 deletions contracts/agents-api/environments.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,15 @@ the exact local profile are validated
before persistence. Supported optional functions remain engine-specific.

Initial text commits a reservation and connection action, then returns the Session
and Environment connection target while offline. Streamed creation sends its
original `created` snapshot before the connection action. The existing Worker
prepares and admits the input; closing the stream leaves committed work intact.
and Environment connection target while offline. Streamed creation sends the same
committed projection as its `created` snapshot, already showing `requires_action`
and the connection action, then the committed `requires_action` event. Like
every fresh creation stream it ends right after the idle recorded when the
admitted Turn ends or the reservation stops being pending, or after a failure;
the connection alone clearing the action does not end it. A creation without
input ends right after its created snapshot, and a same-key stream retry ends at
once without events. The existing Worker prepares and admits the input; closing the stream
leaves committed work intact.
Initial expiry leaves a failed Session, safe error and empty actions without a
Turn or an Environment failure. Creation retries preserve the original identity,
deadline and input. Later live observers do not replay creation events.
Expand Down
Loading
Loading