You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[FEATURE]: Endpoints to list, inspect, attach to, and control sessions and runs #785
Once a run can outlive its request, something has to be able to find it. Today the only management surface is A2A's, shaped for agents: a task id you already have, a subscribe that is the execution stream, a cancel. The epic's "native API intent based layer for kicking off a headless task", and its endpoints for "understanding what is running at any given time", have no home. Neither does the resume endpoint the park work leaves for later.
Give the runtime an HTTP surface of its own. It is the runtime's public form: every route maps onto one runtime call, and the handlers are the only code outside the runtime that hold it.
Goals
Inspect, attach, cancel — needs only the runtime and observer kinds:
GET /v1/sessions — sessions this instance holds, with each one's AgentState ([FEATURE]: A runtime that owns sessions and their runs #780): Preparing, Running, Blocked and on what, Parked, Done. GET /v1/sessions/{id} — one session's summary: its state, agent, live and last run, latest sequence, and this instance's observer counts. This is "what is running right now", answered per agent rather than per request.
GET /v1/sessions/{id}/events — an SSE stream of SessionEvents from a cursor; a collecting attach by default, with claim and presence as query parameters for the other kinds ([FEATURE]: Observers declare whether they collect, claim, or attend #781). This is "zoom in": the projection is the envelope itself, not the chat wire. Attaching to a session with no live run is allowed; the stream delivers the next run from its Started. A second claim is refused with the current claimant named; take-over is not offered over HTTP.
GET /v1/runs — live and recently ended runs, filterable by session, status, and pending approval. GET /v1/runs/{id} — one run's summary: its RunFacts ([FEATURE]: A runtime that owns sessions and their runs #780) plus the session's observer counts. GET /v1/runs/{id}/events — the session stream from the run's Started; the same attach, with the cursor filled in.
POST /v1/runs/{id}/cancel — with a reason; the lifecycle event carries External and the message.
Start and continue — needs the liveness policy:
POST /v1/runs — start a run: agent, the conversation's messages in the chat endpoint's shape, session, liveness policy ([FEATURE]: Liveness policy when a run's last claim detaches #784), and optionally the run it continues. One request serves every case a seam has: a headless start on a new session; the next turn after a run finished with a ClarificationNeeded or an answer, where continues records the lineage and liveness defaults to the prior run's; a start into an existing session. It returns 202 with the run id while the run is Preparing; Started arrives on the events stream. A start into a session with a live run is 409 naming that run ([FEATURE]: A runtime that owns sessions and their runs #780). (background: true belongs to the Responses API, which AURA does not serve; if it ever does, it can front this same start.)
The messages travel with the request because the server keeps no session history today. The session stream ([FEATURE]: A session's event journal and late attach #779) now makes history derivable — Started { prompt } and each run's Completed are the turns in order — but whether the server reconstructs it from the stream or [EPIC] State Management #210 stores messages beside it is [EPIC] State Management #210's decision, and until it is made messages is required. Nothing here injects a prompt into a running turn; HITL decisions and the turn nudge are the only input that enters a run, between its turns.
POST /v1/runs/{id}/resume — rehydrate a parked run through the continuation surfaces in orchestration::park and start it under the runtime with its recorded decisions. Also a new run that continues the parked one; it is a separate route because its seed is the checkpoint, not messages. (It could become POST /v1/runs { continues } with no messages on a Parked run, the runtime dispatching on the prior run's status — left as an option. [FEATURE]: Liveness policy when a run's last claim detaches #784's resume-on-start does the same thing without a request.) PR Park and resume for human-in-the-loop approvals #760 (open) adds a resume endpoint at POST /v1/sessions/{session_id}/runs/{run_id}, guarded by an in-process ResumeClaimTable; this route is that endpoint under the runtime, and the two paths need to agree.
Cross-instance — needs the session claim:
A session this instance does not hold is reachable: the summary comes from the holder, and an events attach relays over EventBus on session:{session_id} with the sequence numbers and fell-behind contract bus_bridge has, generalized from A2A tasks to every session. The relay is a lease in the holder's LeaseStore ([FEATURE]: Atomic Fence for VFS Claims #581), so a relaying instance that dies is detached with Expired. A cancel routes the way a2a:cancel:{task_id} does, and the holder records the outcome. Until [FEATURE]: Atomic Fence for VFS Claims #581 lands, a session elsewhere is reported as not found.
Throughout:
Authorization the same as the existing routes for now; these follow whatever strategy the existing endpoints adopt.
Data structures
Proposed wire types, in aura-web-server:
// POST /v1/runspubstructStartRunRequest{pubagent:Option<String>,// AgentInfo::id; the default agent when absentpubmessages:Vec<ChatMessage>,// the chat endpoint's shape; the last user message is the promptpubsession_id:Option<SessionId>,// a new session when absentpubcontinues:Option<RunId>,// the run this one follows; liveness defaults to itspubliveness:Option<Liveness>,// #784; the `headless` config default when absent}pubstructStartRunResponse{// 202 — the run is Preparing; 409 — RunInProgress { run_id }pubrun_id:RunId,pubsession_id:SessionId,pubcontinues:Option<RunId>,}// GET /v1/sessions, GET /v1/sessions/{id}pubstructListSessionsQuery{pubstate:Option<AgentStateKind>,pubagent:Option<String>,publimit:Option<u32>,pubcursor:Option<String>}pubstructSessionSummary{pubsession_id:SessionId,pubagent:String,#[serde(flatten)]pubstate:AgentState,// #780 — { "state": "blocked", "on": { "kind": "approval", ... } }pubcurrent_run:Option<RunId>,publast_run:Option<RunId>,publatest_seq:Option<SequenceNumber>,pubobservers:ObserverCounts,// #781}// GET /v1/runspubstructListRunsQuery{pubsession_id:Option<SessionId>,pubstatus:Option<RunStatus>,pubpending_approval:Option<bool>,publimit:Option<u32>,pubcursor:Option<String>,}pubstructListRunsResponse{pubruns:Vec<RunSummary>,pubnext_cursor:Option<String>}// GET /v1/runs/{id} — RunFacts (#780) plus what only this instance knowspubstructRunSummary{#[serde(flatten)]pubfacts:RunFacts,// usage snapshotted live while runningpubobservers:ObserverCounts,// the session's, #781}// GET /v1/sessions/{id}/events, GET /v1/runs/{id}/eventspubstructEventsQuery{pubafter:Option<u64>,// replay events with seq > after; absent = from the start// (/runs/{id}/events: absent = from the run's Started)pubclaim:Option<bool>,// attach as the claimant (#781); refused if one existspubpresence:Option<bool>,// a human is at this observer}// body: `text/event-stream` of SessionEvent JSON, one per `data:` line, plus a// `gap` event carrying `resumed_at` when the subscriber fell behind (#779)// POST /v1/runs/{id}/cancelpubstructCancelRunRequest{pubreason:Option<String>}
Additional Context
There is no user or tenant model, so "the same as the existing routes" means any caller with the API key can list and attach to every session on the instance. That is the current exposure of /v1/chat/completions extended to sessions the caller did not start. It is accepted for V1 because the auth work in flight is where the answer belongs, not here.
?after= is the session sequence number the client has already seen, so a reconnecting client asks for after=<last seq> and gets no duplicate and no hole. It matches Cursor::After (#779); a from that was ambiguous about inclusivity was the reconnect off-by-one waiting to happen. On the run-scoped route the default cursor is the run's first_seq; an explicit after is still a session sequence.
The session summary's state is constructed, not stored — the derivation table is in #780 — so an instance can answer it for any session it holds without a store read, and a different implementation could construct it differently without this API noticing.
Presence on ?presence=true is self-declared, as it is for every observer (#781).
Summary
Once a run can outlive its request, something has to be able to find it. Today the only management surface is A2A's, shaped for agents: a task id you already have, a subscribe that is the execution stream, a cancel. The epic's "native API intent based layer for kicking off a headless task", and its endpoints for "understanding what is running at any given time", have no home. Neither does the resume endpoint the park work leaves for later.
Give the runtime an HTTP surface of its own. It is the runtime's public form: every route maps onto one runtime call, and the handlers are the only code outside the runtime that hold it.
Goals
Inspect, attach, cancel — needs only the runtime and observer kinds:
GET /v1/sessions— sessions this instance holds, with each one'sAgentState([FEATURE]: A runtime that owns sessions and their runs #780): Preparing, Running, Blocked and on what, Parked, Done.GET /v1/sessions/{id}— one session's summary: its state, agent, live and last run, latest sequence, and this instance's observer counts. This is "what is running right now", answered per agent rather than per request.GET /v1/sessions/{id}/events— an SSE stream ofSessionEvents from a cursor; a collecting attach by default, withclaimandpresenceas query parameters for the other kinds ([FEATURE]: Observers declare whether they collect, claim, or attend #781). This is "zoom in": the projection is the envelope itself, not the chat wire. Attaching to a session with no live run is allowed; the stream delivers the next run from itsStarted. A second claim is refused with the current claimant named; take-over is not offered over HTTP.GET /v1/runs— live and recently ended runs, filterable by session, status, and pending approval.GET /v1/runs/{id}— one run's summary: itsRunFacts([FEATURE]: A runtime that owns sessions and their runs #780) plus the session's observer counts.GET /v1/runs/{id}/events— the session stream from the run'sStarted; the same attach, with the cursor filled in.POST /v1/runs/{id}/cancel— with a reason; the lifecycle event carriesExternaland the message.Start and continue — needs the liveness policy:
POST /v1/runs— start a run: agent, the conversation's messages in the chat endpoint's shape, session, liveness policy ([FEATURE]: Liveness policy when a run's last claim detaches #784), and optionally the run itcontinues. One request serves every case a seam has: a headless start on a new session; the next turn after a run finished with aClarificationNeededor an answer, wherecontinuesrecords the lineage andlivenessdefaults to the prior run's; a start into an existing session. It returns202with the run id while the run isPreparing;Startedarrives on the events stream. A start into a session with a live run is409naming that run ([FEATURE]: A runtime that owns sessions and their runs #780). (background: truebelongs to the Responses API, which AURA does not serve; if it ever does, it can front this same start.)Started { prompt }and each run'sCompletedare the turns in order — but whether the server reconstructs it from the stream or [EPIC] State Management #210 stores messages beside it is [EPIC] State Management #210's decision, and until it is mademessagesis required. Nothing here injects a prompt into a running turn; HITL decisions and the turn nudge are the only input that enters a run, between its turns.POST /v1/runs/{id}/resume— rehydrate a parked run through the continuation surfaces inorchestration::parkand start it under the runtime with its recorded decisions. Also a new run thatcontinuesthe parked one; it is a separate route because its seed is the checkpoint, not messages. (It could becomePOST /v1/runs { continues }with nomessageson aParkedrun, the runtime dispatching on the prior run's status — left as an option. [FEATURE]: Liveness policy when a run's last claim detaches #784's resume-on-start does the same thing without a request.) PR Park and resume for human-in-the-loop approvals #760 (open) adds a resume endpoint atPOST /v1/sessions/{session_id}/runs/{run_id}, guarded by an in-processResumeClaimTable; this route is that endpoint under the runtime, and the two paths need to agree.Cross-instance — needs the session claim:
EventBusonsession:{session_id}with the sequence numbers and fell-behind contractbus_bridgehas, generalized from A2A tasks to every session. The relay is a lease in the holder'sLeaseStore([FEATURE]: Atomic Fence for VFS Claims #581), so a relaying instance that dies is detached withExpired. A cancel routes the waya2a:cancel:{task_id}does, and the holder records the outcome. Until [FEATURE]: Atomic Fence for VFS Claims #581 lands, a session elsewhere is reported as not found.Throughout:
Data structures
Proposed wire types, in
aura-web-server:Additional Context
There is no user or tenant model, so "the same as the existing routes" means any caller with the API key can list and attach to every session on the instance. That is the current exposure of
/v1/chat/completionsextended to sessions the caller did not start. It is accepted for V1 because the auth work in flight is where the answer belongs, not here.?after=is the session sequence number the client has already seen, so a reconnecting client asks forafter=<last seq>and gets no duplicate and no hole. It matchesCursor::After(#779); afromthat was ambiguous about inclusivity was the reconnect off-by-one waiting to happen. On the run-scoped route the default cursor is the run'sfirst_seq; an explicitafteris still a session sequence.The session summary's state is constructed, not stored — the derivation table is in #780 — so an instance can answer it for any session it holds without a store read, and a different implementation could construct it differently without this API noticing.
Presence on
?presence=trueis self-declared, as it is for every observer (#781).Searched Issues
Code of Conduct