Session Operation 流式事件

背景

早期的 ExecuteSessionOperation 是 unary request。这适合短命令和原子文件 mutation,但 interactive Runtime 的一次 operation 可能要经过多次 model request 和 tool call 才产生最终回答。在完整 operation 返回前,caller 无法得知 Session 是否仍在工作。

本设计为单个 Session operation 增加通用、有序的实时事件流。它不是 agent 专用 API:Runtime 可将它用于 command output、progress reporting 或 interactive agent turn。旧 unary operation API 已移除。

目标

  • 在 operation 运行时向 caller 提供 accepted、progress、output、terminal result 和 error event。
  • 保留 owner runtimed 的 FIFO queue,以及既有 cancellation、authorization、assignment fencing 与 operation timeout 语义。
  • Runtime Server 与 runtimed port 保持私有;Runtime gateway 仍是唯一公开 data-plane endpoint。
  • 使用同时适合 browser 和 CLI、并能使用 bearer authentication 的流式 transport。
  • 限制每个 Runtime-emitted event 的大小,且不引入无界 event buffer。

首次交付的非目标

  • Runtime Pod 丢失后的 durable event retention 或 replay。
  • client disconnect 后的 durable operation replay。live operation 可以在 connection 关闭后继续, 但仍受其普通 timeout 与 Session lease 约束;disconnected client 没有 retained event cursor。
  • Runtime 定义的双向 user input 或 approval。WebSocket transport 支持 client cancellation;更多消息需要单独的 Runtime protocol extension 与 operation admission design。
  • 将高频 event 写入 Run.status、Kubernetes Events 或 container logs。runtimed structured log 仍是 audit path。

durable operation record、idempotent turn submission 和 cursor-based replay 继续由 #36 跟踪。实时流刻意作为这些能力的兼容基础,不能过早 宣称已经解决 recovery semantics。

API

Runtime gRPC contract 增加 server-streaming method:

rpc StreamSessionOperation(SessionOperationRequest)
    returns (stream SessionOperationEvent);

SessionOperationEvent 具有每个 operation 严格递增的 sequence number,以及 oneof payload:

  • accepted:owner runtimed 已将 operation admission 到 FIFO queue;
  • output:command Runtime 的有界 stdout 或 stderr bytes;
  • progress:Runtime 定义的有界 event(text_delta、tool_call_started、 tool_call_finished 或 status),包括 typed kind、可选 tool identity 和 JSON payload;
  • completed:适用时,与 unary operation 返回相同的有界 SessionCommandResult;
  • failed:terminal 的 gRPC-compatible code 与安全 message。

由 owner runtimed 而非 gateway 分配 sequence number。它在 queue entry 处于 active 时转发 Runtime Server event stream。这样即使 gateway request 到达非 owner Runtime Pod、再单跳转发给 owner,也不会破坏 queue mutation ordering。

公开的实时 operation endpoint 仅支持持久双向 WebSocket;旧的 unary HTTP 与 NDJSON endpoint 已移除。 upgrade 前使用普通 HTTP authentication、authorization 与 error mapping;upgrade 后以 event frame 或 {"type":"error","error":"..."} 表示失败。

Session connection 的内部 transport 是已有的 operation WebSocket:

GET /v1/namespaces/{namespace}/runtimes/{runtime}/sessions/{runUID}/operations:ws
Upgrade: websocket

HTTP upgrade 会在 connection 被接受前完成 authentication 和 authorization。带有 Origin header 的 browser request 必须为 same-origin;不带 Origin 的 native client 继续在 upgrade request 中使用 bearer token 或 client certificate。打开 connection 不会提交 operation。首个版本每条 connection 只允许一个 in-flight operation;这符合每次一个 agent turn 的模型,并且不会虚构 operation multiplexing semantics。

SDK 的 Send 会写入类似下面的内部 frame:

{
  "type": "send",
  "idempotencyKey": "optional-client-key",
  "operation": {
    "command": {"shell": "classify and label issue #123"}
  }
}

gateway 通过已有 operation admission path 转发 operation。它为每个有序 SessionOperationEvent 返回一个 server text frame。第一个 accepted event 提供 server-generated operation ID,SDK 将其作为 Send 的返回值;Receive 暴露后续 event,但不暴露 frame 或 WebSocket 细节:

{
  "sequence": 1,
  "type": "accepted",
  "accepted": {"operationID": "op-7b7b"}
}

operation 期间,Cancel(operationID) 写入内部 frame {"type":"cancel","operationID":"op-7b7b"}。gateway 拒绝 ID 不是当前 operation 的 cancellation。 terminal event 前第二个 send 是 connection protocol error。SDK 通过 application frame {"type":"heartbeat"} 续租已配置的 lease。任意 interactive input 或 approval message 尚未成为 Runtime protocol 的一部分。

WebSocket upgrade 前的 error 仍是普通 HTTP error。upgrade 后的 error 将以 {"type":"error","error":"..."} 发送,connection 可继续处理下一个 operation;成功 upgrade 后不能再更改 HTTP status。

HTTP representation 使用小写 protocol value:output 的 stream 为 stdout 或 stderr;progress 的 kind 为 status、text_delta、tool_call_started 或 tool_call_finished。二进制 data field 使用标准 JSON base64 string。

SDK Session connection

SDK 是 agent sandbox API,而不是 HTTP transport wrapper。Go 与 Python 都只暴露一套 Session connection model:

Runtime.AcquireSandbox (create Session Run) -> assigned Runtime Pod
-> Sandbox.OpenSession -> Send / Receive / Cancel -> Session.Close
-> Sandbox.Release (stop/delete Session Run)

Sandbox 是 SDK object,它的唯一 Session 由一个 session-mode Run 表示。AcquireSandbox 创建 该 Run,并在成功完成 scheduling 与 registration 后才返回 active Sandbox。此版本每个 Sandbox 只支持 一个 Session。

capacity 在 AcquireSandbox 创建 Run 并由 scheduler 分配给 Runtime Pod 时原子地获得。OpenSession 只会打开到该 Ready Session 的 streaming connection,不会创建 Run 或改变 capacity。

公开的 data-plane vocabulary 与 transport 无关:

操作含义
Send提交一个 Session operation 并返回其 operation ID
Receive接收下一个有序 event,其中包含 operation ID
Cancel请求取消一个已提交 operation
Session.Close只关闭 streaming connection
ReleaseSandbox停止 Session、等待 cleanup,然后删除其 Session Run

SDK 不暴露 Stream、Resume、StreamWebSocket、NDJSON 或 WebSocket 名称。其内部维护一个持久、 已认证的双向 WebSocket connection。client message 携带 operation request 或 cancellation;server message 携带有序的 SessionOperationEvent 及 operation ID。upgrade 后的 gateway error 会转换为 typed SDK transport error,而不是普通 event。

由 Kubernetes REST configuration 构造的 SDK 会为该 connection 使用其中的 TLS CA bundle 和可选的 client certificate/key,在 endpoint 提供 Run.status.endpoint.caBundle 时信任该证书,并在 upgrade 前应用 Kubernetes REST 认证 wrapper(包括轮换的 token file 和 exec credential)。custom SDK transport 必须自行实现这一 connection boundary;仅有 HTTP RoundTripper 无法完成 WebSocket upgrade。受限的 Console port-forward adapter 会像改写普通 HTTP endpoint 一样改写 connection endpoint,并保留 caller credential。

首个版本不会在 transport failure 后自动 retry 或 replay operation;此时 operation outcome 未知,caller 可以在 Session lease 到期前打开新 connection 并读取 Session state。durable operation replay 不属于该 live-connection contract。

Lifecycle、cancellation 与边界

gateway 会对 connection 的目标 Session Run 授权。Cancel、gateway shutdown、Immediate Session termination 和 effective operation timeout 会取消 active Runtime Server operation 并释放 queue entry。 关闭 SDK Session connection 只会 detach event consumer,不会取消已 admission 的 work,也不会 release Session capacity。owner runtimed 继续强制 operation timeout 与 Session lease。Drain 允许已 admission 的 operation 完成,并像现在一样拒绝新的 operation。

runtimed 限制每个 Runtime-emitted event,并直接转发 event,而不累计无界 response buffer。超限或 malformed event 会以 resource-limit failure 结束 operation。Runtime 提供的 progress 不能包含 credential、command stdin 或无界 tool output。已有 response-size limit 继续约束 terminal command result 和每条 gateway JSON line。

兼容性与 rollout

ExecuteSessionOperation、operations:execute 与 NDJSON operations:stream endpoint 均已移除。 所有 built-in Runtime 都通过 StreamSessionOperation 实现 operation;custom Runtime 也必须实现该 method。

Go 与 Python Session SDK 暴露 AcquireSandbox、OpenSession、Send、Receive、Cancel、 Session.Close 与 Sandbox.Release,而不是 transport-specific streaming helper。Console 为 agent turn 打开 Session connection 并逐步渲染 event。