diff --git a/contracts/agents-api/environments.md b/contracts/agents-api/environments.md index 44e56e033..a5e397587 100644 --- a/contracts/agents-api/environments.md +++ b/contracts/agents-api/environments.md @@ -106,7 +106,7 @@ The application owns the machine. It creates the Session with a clean absolute ` ## Input admission -Session input goes through `POST /agents/sessions/{id}/events` in ordered batches of 1–16384 events. On an Environment-bearing Session, Core reserves input that cannot start yet until the Environment is connected and prepared. +Session input goes through `POST /agents/sessions/{id}/events` in ordered batches of 1–64 events. On an Environment-bearing Session, Core reserves input that cannot start yet until the Environment is connected and prepared. A valid node-backed creation can wait for compute capacity after committing. Initial input uses the same reservation and deadline while waiting; creating without input still requests Environment preparation. [Placement and generation selection](./sandbox-deployment.md#generation-ownership-and-rollout) define the scheduling rules. @@ -222,7 +222,7 @@ Executor admission validates only the frozen descriptor. The preparation owner m The daemon runs as its launching account and never uses sudo or raises its permissions. Only user-directory dependencies install during preparation. - System dependencies must be preinstalled in the managed image or by the owner of a self-hosted machine. A missing executable or library fails the operation that needs it. -- `packages.system` accepts null or an empty list, meaning no system packages, and rejects a nonempty list in Templates and inline configuration (400, param `packages.system`). Package responses carry the official required `system: []`, so a read configuration can be sent back unchanged. +- `packages.system` accepts null or an empty list, meaning no system packages, and rejects any other value in Templates and inline configuration (400, param `packages.system`). Package responses carry the official required `system: []`, so a read configuration can be sent back unchanged. - npm installs into a local prefix and Python/pip into a local target under the Runtime package directory; Node/npm and Python/pip must already be installed. Their dependencies are visible to native tools in every working directory. - Setup commands run with Bash; on Windows, Git Bash is required and no other shell substitutes. The default working directory is `/workspace`. - On Windows, npm installation and stdio MCP commands named `npm` or `npx` (including their `.cmd` shims) run through npm's JavaScript entry point with Node, without an extra shell. diff --git a/contracts/agents-api/harness-capabilities.md b/contracts/agents-api/harness-capabilities.md index ee52be4d1..83cc92f0f 100644 --- a/contracts/agents-api/harness-capabilities.md +++ b/contracts/agents-api/harness-capabilities.md @@ -20,6 +20,7 @@ Placements are `none` (no Environment), hosted (`openai_hosted`) and self-hosted | --- | --- | --- | --- | | Text Turns, active input, cancellation, restart and continuation | Verified on all placements | Verified on all placements | Verified on all placements | | [Files and Artifacts](./environment-files.md) | Verified: hosted, self-hosted | Verified: hosted, self-hosted | Verified: hosted, self-hosted | +| Agent `instructions` | Admitted | Admitted | At most 32 KiB; longer instructions are rejected | | [Whitespace-only message text](./message-content.md) | Admitted; delivered unchanged | Admitted; native drops it, and an all-blank message becomes `(no content)` | Rejected | | [Message images](./message-content.md#images) | PNG and JPEG data URLs: verified on all placements; other data URLs: admitted; remote URLs: rejected | PNG and JPEG data URLs: verified on all placements; remote URLs and GIF and WebP data URLs: admitted | Rejected | | `reasoning.effort` | `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`: admitted | `low`, `medium`, `high`, `xhigh`, `max`: admitted | `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`: admitted | @@ -59,7 +60,7 @@ These operations need a workspace, so they apply to hosted and self-hosted place | [Initial files, setup commands, Skills and Plugins](./environments.md#runtime-capability-preparation) | Verified: hosted, self-hosted | Verified: self-hosted; admitted: hosted | Verified: self-hosted; admitted: hosted | | Capability directories | Admitted | Admitted | Admitted | | npm and Python packages | Admitted | Admitted | Admitted | -| `packages.system` | Rejected when nonempty | Rejected when nonempty | Rejected when nonempty | +| `packages.system` other than null or `[]` | Rejected | Rejected | Rejected | | Network `disabled` or `restricted` | Rejected | Rejected | Rejected | | [Plugin MCP over stdio](./environments.md#plugin-mcp) | Verified: hosted, self-hosted | Verified: self-hosted; admitted: hosted | Verified: self-hosted; admitted: hosted | | Plugin MCP over HTTP | Admitted, with literal headers or HTTPS bearer | Admitted, with literal headers or HTTPS bearer | Verified: self-hosted; admitted: hosted; literal headers or HTTPS bearer | diff --git a/contracts/agents-api/index.md b/contracts/agents-api/index.md index 260f76dd9..a1b26c7d9 100644 --- a/contracts/agents-api/index.md +++ b/contracts/agents-api/index.md @@ -80,13 +80,14 @@ Each item is Core's deliberate or native behavior where the official service beh - A Session's `agent.tools` omits `tool_search` declarations. - Core rejects an inline `authorization` on a Session MCP transport, an `Authorization` entry in `headers` and a nonempty stdio `env`: credentials come only from Vaults (`credential_id`) and environment values only from the Environment (`env_vars`) ([MCP](./execution-tools.md#mcp)). - A `stdio` MCP server needs a workspace placement and the origin `environment`. Claude Code starts the server in its workspace directory, so `cwd` must equal it, with its whole process environment, which also holds the model provider key and other servers' Vault bearer tokens, and it needs `allowed_tools` null. MiniMax Code starts the server in its private home directory, so it rejects every `stdio` server ([MCP](./execution-tools.md#mcp)). -- Core rejects nonempty MCP `request_metadata`, a `stdio` server whose `connection_origin` is `service`, and a blank `server_label`, an empty `credential_id` and an empty `allowed_tools` name, which the pinned schema allows without defining their meaning (400 with the reason). +- Core rejects nonempty MCP `request_metadata`, which no Harness can send, as well as a `stdio` server whose `connection_origin` is `service`, a blank `server_label`, an empty `credential_id` and an empty `allowed_tools` name, which the pinned schema allows without defining their meaning (400 with the reason). - Claude rejects an MCP `server_url` or `headers` value containing `${`, because the native client expands `${VAR}` in both from a process environment that holds Core-held credentials. - Deleting a Session right after an events 202 returns 409, because Core admits the Turn in the same transaction; the official service returns 200. **Input, events and history** ([Sessions, events and history](./sessions-events.md), [message content](./message-content.md)) - Input to a `none` Session is admitted synchronously; Core does not emulate the official asynchronous admission window. +- An events request takes at most 64 events ([send input](./sessions-events.md#send-input)); the official limit is 16384. - A function result that resumes a waiting Turn emits `turn.in_progress`, and cancelling a Turn that waits on a function result emits an interim `agent.session.in_progress`. - A function call the native Harness abandons, such as a Subagent call dropped when the root closes or interrupts the child or a MiniMax Code call past its 120 s timeout, is withdrawn: it stops being a required action and a later result for it returns 409 `conflict_error`. The official API has no withdrawal. - Attaching to the stream mid-Turn sends no catch-up Item snapshots. @@ -122,7 +123,7 @@ Each item is Core's deliberate or native behavior where the official service beh **Configuration and tools** - A `reasoning.effort`, `reasoning.summary`, `service_tier`, `text.verbosity`, `web_search` mode or setting or `programmatic_tool_calling` value that the selected Harness does not declare is saved but rejected at Session admission ([Harness capabilities](./harness-capabilities.md#execution-and-input)). -- Harness support for tools, structured output, deferred discovery, subagents and MCP differs by Harness and placement; see the [Harness capabilities](./harness-capabilities.md). MiniMax Code has no image input. +- Harness support for tools, structured output, deferred discovery, subagents and MCP differs by Harness and placement; see the [Harness capabilities](./harness-capabilities.md). MiniMax Code has no image input and accepts at most 32 KiB of `instructions`. - Model-derived reasoning defaults are not resolved. **Execution and history** @@ -133,7 +134,8 @@ Each item is Core's deliberate or native behavior where the official service beh - Pinned Codex can lose command output emitted before its stream subscription. - Claude Code and MiniMax Code report no public usage. - `agent_message` and `interrupt_subagent_call` Items are accepted by the Runtime protocol, but no Harness adapter produces them. -- Native file changes have no official Item type ([Turns and Items](./sessions-events.md#turns-and-items)). +- Codex file changes have no official Item type ([Turns and Items](./sessions-events.md#turns-and-items)). +- Claude Code runs at most 64 messages in one Turn, counting its opening input ([Runtime boundary](./message-content.md#runtime-boundary)); a Turn that receives more fails with `internal_error`. - MiniMax Code receives a message that starts with `/` with a trailing blank line, because ACP otherwise runs it as an operator command. - Core gives no crash-safe or exactly-once guarantee for native side effects; claimed work fails on restart without replay. - `file_id` and `detail` on images are rejected; each Harness declares which `image_url` values it accepts ([message content](./message-content.md#images)). @@ -143,7 +145,7 @@ Each item is Core's deliberate or native behavior where the official service beh - Hosted workspace admission is limited by the [tenant capacity policy](../../docs/configuration.md#tenant-capacity) and the [input reservation deadline](./environments.md#reservations). - MiniMax Code does not declare `retained_native_history`, so it cannot run on microsandbox, which keeps every Environment on workspace storage; Core rejects the Session ([Harness capabilities](./harness-capabilities.md)). - Only `enabled` networking runs, so Sessions that need `disabled` or `restricted` are rejected ([restricted network policy](./environments.md#restricted-network)). -- A nonempty `packages.system` is rejected; system packages must be preinstalled. +- `packages.system` other than null or an empty list is rejected; system packages must be preinstalled. - Cold continuation does not restore process memory, background processes or temporary system-disk changes, and completed setup is not replayed ([Environment persistence](./environments.md#runtime-capability-preparation)). On compatible microsandbox nodes with retained NFS storage, Codex and Claude Code are qualified for [checkpoint transfer](../../docs/sandbox-provider.md#checkpoint-transfer) and for continuation after [confirmed compute loss](../../docs/sandbox-provider.md#confirmed-compute-loss). Takeover of an unconfirmed old writer, host-crash durability and arbitrary crash-damaged history are unqualified. **Files and Environment files** diff --git a/contracts/agents-api/sessions-events.md b/contracts/agents-api/sessions-events.md index f5261daa0..1fbd4bf5b 100644 --- a/contracts/agents-api/sessions-events.md +++ b/contracts/agents-api/sessions-events.md @@ -43,9 +43,9 @@ A Session stays usable after a Turn fails: new input starts a new Turn. Later re ## Send input -`POST /v1/agents/sessions/{session_id}/events` takes an ordered array of 1 to 16384 events: `agent.session.input.message`, `agent.session.input.cancel` and `agent.session.input.tool_result`. The whole batch is admitted atomically, and the response is 202 with no body once the batch is stored, before any Harness reads it. Admission never confirms native application. +`POST /v1/agents/sessions/{session_id}/events` takes an ordered array of 1 to 64 events: `agent.session.input.message`, `agent.session.input.cancel` and `agent.session.input.tool_result`. The whole batch is admitted atomically, and the response is 202 with no body once the batch is stored, before any Harness reads it. Admission never confirms native application. -- **Limits.** The request body is at most 4 MiB, and the stored input of one request at most 2 MiB. +- **Limits.** The request body is at most 1 MiB, and the stored input of one request at most 512 KiB. - **Null batch.** An explicit `null` for `events` is invalid. - **Empty batch.** `{"events": []}` checks that the Session exists and returns 202. It creates no Turn, Item or retry identity. - **Retries.** An `Idempotency-Key` of up to 128 bytes identifies the whole ordered batch. The same key and batch return 202 again without admitting anything twice; the same key with another batch returns 409 `idempotency_conflict`. A request without a key is always new. @@ -109,7 +109,7 @@ A retry with the same `Idempotency-Key` and `stream: true` returns 201 with only - **Lifetime.** The stream stays open across Turns and after a Turn fails. It ends when the Session is deleted or after the terminal `agent.session.failed` of an [Environment initialization failure](#environment-initialization-failure); a stream opened after that failure stays open. A keepalive comment is sent every 15 seconds. - **Buffer.** Core keeps at most 256 events and 64 MiB of events per Session, plus one larger event when needed. A reader that falls behind the buffer receives an `error` event with type `server_error` and code `stream_interrupted`, and the stream closes. A socket write that blocks for five seconds also closes it. Execution never waits for a reader. - **Key recheck.** An open stream checks the original Project key at most once per second while idle and before sending output. Revoking the key or archiving the Project closes the stream, and so does an authentication failure. A recheck uses the normal five-second authentication timeout and sends no Session data while it waits. Bytes already sent cannot be recalled. -- **Root work only.** Child Turns and child Items publish no Session events; `agent.session.subagent.*` events and root coordination Items do. Read child work through the [Subagent resources](./subagents.md). +- **Root work only.** Child Turns and child Items publish no Session events, except a Subagent's `function_call` Item, published once as `item.added`; `agent.session.subagent.*` events and root coordination Items do. Read child work through the [Subagent resources](./subagents.md). ### Event rules @@ -119,7 +119,7 @@ A retry with the same `Idempotency-Key` and `stream: true` returns 201 with only - `item.added` and `item.done` always carry `output_index`, null for input Items. Function results emit `item.added` only; `item.done` is for agent output. - An assistant message follows one sequence: `item.added` in progress with empty `content`, `content_part.added` with empty text, `output_text.delta` events, `output_text.done`, `content_part.done` and `item.done`. A message first observed complete, such as structured output, sends its whole text in one `output_text.delta`, byte for byte. The completed text replaces the accumulated deltas. - Codex command output streams as `agent.output.command_execution_output.delta` with the command's Item ID and output index. Native output quotas and text conversion apply, so the deltas are not a byte-exact capture; the completed Item is authoritative. -- A reasoning Item follows one sequence: `item.added` in progress with an empty `summary`; for each `summary_text` part in order, `reasoning_summary_part.added` with empty text, `reasoning_summary_text.delta` events, then `reasoning_summary_text.done` and `reasoning_summary_part.done` once the next part starts or the Item ends; then `item.done`. `reasoning_summary_part.done` has `status` `incomplete` for a part a cancelled or failed Turn cut off, otherwise null. A child Turn snapshot that rewrites earlier text reports it only in the done events. +- A reasoning Item follows one sequence: `item.added` in progress with an empty `summary`; for each `summary_text` part in order, `reasoning_summary_part.added` with empty text, `reasoning_summary_text.delta` events, then `reasoning_summary_text.done` and `reasoning_summary_part.done` once the next part starts or the Item ends; then `item.done`. `reasoning_summary_part.done` has `status` `incomplete` for a part a cancelled or failed Turn cut off, otherwise null. - A cancelled or failed Turn marks its unfinished Items `incomplete` and keeps their partial content. - A function call stays in `required_actions` until the Harness applies its result, or cancellation or the Turn's end removes it. A repeated notification emits no new state. @@ -136,7 +136,7 @@ Turn and Item lists take `after`, `limit` and `order` ([list rules](./wire-seman | `message` | User or assistant content parts. `phase` is the Harness's phase when it reports one (`commentary`, `final_answer`), otherwise null | | `command_execution` | Command, reported output, exit code, duration and working directory | | `mcp_call` | Server and tool identity, arguments, structured result or error | -| `function_call`, `function_call_output` | A linked call and its result. The result always carries `output` and `error`, null when the submission omitted them; stored results keep the submitted field presence. Native file changes appear as an `apply_patch` function call with the changes as arguments and no invented result | +| `function_call`, `function_call_output` | A linked call and its result. The result always carries `output` and `error`, null when the submission omitted them; stored results keep the submitted field presence. Codex file changes appear as an `apply_patch` function call with the changes as arguments and no invented result | | `web_search_call` | The supported action fields (`search`, `open_page`, `find_in_page`, `other`); Claude Code reports `search` for `WebSearch` and `open_page` for `WebFetch` | | `reasoning` | One native reasoning item, each native part one `summary_text` part, present only when the Agent sets `reasoning.summary`; with it null (the default) no `reasoning` Items or reasoning events appear for any Turn, root or child. [OpenAI compatibility](./index.md#differences-from-openai) describes the text. Status can be null on a child Turn's Item | | `agent_message` and the Subagent coordination calls | See [Subagents](./subagents.md). `agent_message` has no status | diff --git a/contracts/agents-api/zh/environments.md b/contracts/agents-api/zh/environments.md index 11257f0be..7d0a82934 100644 --- a/contracts/agents-api/zh/environments.md +++ b/contracts/agents-api/zh/environments.md @@ -1,7 +1,7 @@ --- title: "Environment 与模板" source: contracts/agents-api/environments.md -source_hash: 826b3d0c74ea82af5c4f7e6094b71257a7e56fba896d5fc8a58234bc3bed5249 +source_hash: eb7282383dcac86723189824b0391c7dd50336257db922a1fce2b2f2d2153049 --- Environment 是 Session 的执行资源,包括 Harness 运行所在的机器、工作区以及已完成准备的能力。Session 通过其 `environment` 配置创建 Environment;不存在独立的 create 调用。Environment 模板是 Session 创建时解析的可复用准备配置。本契约涵盖这两类资源、两种放置方式、输入接纳、能力准备、Skills、Plugins 和 MCP 连接来源。 @@ -108,7 +108,7 @@ Core 在 Session 创建事务中创建 Environment 记录;Session upsert 会 ## 输入接纳 {#input-admission} -Session 输入通过 `POST /agents/sessions/{id}/events` 以每次 1–16384 个事件的有序批次提交。对于带 Environment 的 Session,Core 会将尚不能启动的输入预留,直到 Environment 完成连接和准备。 +Session 输入通过 `POST /agents/sessions/{id}/events` 以每次 1–64 个事件的有序批次提交。对于带 Environment 的 Session,Core 会将尚不能启动的输入预留,直到 Environment 完成连接和准备。 有效的节点模式创建可以在提交后等待计算容量。初始输入在等待期间仍使用同一 reservation 和期限;无输入创建也会请求 Environment 准备。[Placement 与代次选择](./sandbox-deployment.md#generation-ownership-and-rollout) 定义调度规则。 @@ -224,7 +224,7 @@ Executor 接纳仅验证已冻结的描述符。准备所有者会在调用原 daemon 以启动它的账户身份运行,绝不使用 sudo 或提升权限。准备过程中只会安装用户目录中的依赖项。 - 系统依赖必须预先安装在托管镜像中,或由自托管机器的所有者安装。缺少可执行文件或库时,需要该依赖的操作会失败。 -- `packages.system` 接受 null 或空列表(表示没有系统软件包),模板和内联配置会拒绝非空列表(400,param `packages.system`)。软件包响应包含官方要求的 `system: []`,因此读取到的配置可以原样回传。 +- `packages.system` 接受 null 或空列表(表示没有系统软件包),模板和内联配置会拒绝其他任何值(400,param `packages.system`)。软件包响应包含官方要求的 `system: []`,因此读取到的配置可以原样回传。 - npm 会安装到本地 prefix,Python/pip 会安装到 Runtime 软件包目录下的本地 target;Node/npm 和 Python/pip 必须已经安装。其依赖项可被每个工作目录中的原生工具看到。 - 设置命令使用 Bash 运行;在 Windows 上必须使用 Git Bash,且不能由其他 shell 替代。默认工作目录为 `/workspace`。 - 在 Windows 上,npm 安装以及名为 `npm` 或 `npx` 的 stdio MCP 命令(包括其 `.cmd` shim)会通过 Node 调用 npm 的 JavaScript 入口点运行,而不经过额外的 shell。 diff --git a/contracts/agents-api/zh/harness-capabilities.md b/contracts/agents-api/zh/harness-capabilities.md index 89012f5be..9ffc01685 100644 --- a/contracts/agents-api/zh/harness-capabilities.md +++ b/contracts/agents-api/zh/harness-capabilities.md @@ -1,7 +1,7 @@ --- title: "Harness 能力" source: contracts/agents-api/harness-capabilities.md -source_hash: bd03ebf9e556b71091fed42d1ba52b2002207962ccc4f7c3765104c82bddc3b5 +source_hash: 8913afc18b99b847850a857e387e1a230085525ce5f1b28abd5a440abc9dcf16 --- 本页列出每个 Harness 在每种部署位置支持的能力。Core 根据 `internal/harnessconfig//declaration.go` 中 Harness 的声明决定准入,运行 Session 的 Runtime 也必须声明操作。所链接契约定义各操作;[Harness 接入](harness-onboarding.md#qualify-the-adapter)说明资格验证方法。 @@ -22,6 +22,7 @@ source_hash: bd03ebf9e556b71091fed42d1ba52b2002207962ccc4f7c3765104c82bddc3b5 | --- | --- | --- | --- | | 文本 Turn、活动输入、取消、重启与继续 | 所有位置已验证 | 所有位置已验证 | 所有位置已验证 | | [文件与 Artifact](environment-files.md) | 已验证:托管、自托管 | 已验证:托管、自托管 | 已验证:托管、自托管 | +| Agent `instructions` | 已准入 | 已准入 | 最多 32 KiB;更长的 instructions 会被拒绝 | | [仅空白消息文本](message-content.md) | 已准入;原样交付 | 已准入;原生会丢弃它,全空白消息变为 `(no content)` | 已拒绝 | | [消息图像](message-content.md#images) | PNG、JPEG data URL:所有位置已验证;其他 data URL:已准入;远程 URL:已拒绝 | PNG、JPEG data URL:所有位置已验证;远程 URL 及 GIF、WebP data URL:已准入 | 已拒绝 | | `reasoning.effort` | `none`、`minimal`、`low`、`medium`、`high`、`xhigh`、`max`:已准入 | `low`、`medium`、`high`、`xhigh`、`max`:已准入 | `none`、`minimal`、`low`、`medium`、`high`、`xhigh`、`max`:已准入 | @@ -61,7 +62,7 @@ Claude 结构化输出要求 medium verbosity,可与 `multi_agent`、Skill、P | [初始文件、设置命令、Skill 与 Plugin](environments.md#runtime-capability-preparation) | 已验证:托管、自托管 | 已验证:自托管;已准入:托管 | 已验证:自托管;已准入:托管 | | 能力目录 | 已准入 | 已准入 | 已准入 | | npm 与 Python 包 | 已准入 | 已准入 | 已准入 | -| `packages.system` | 非空时拒绝 | 非空时拒绝 | 非空时拒绝 | +| null 或 `[]` 以外的 `packages.system` | 已拒绝 | 已拒绝 | 已拒绝 | | 网络 `disabled` 或 `restricted` | 已拒绝 | 已拒绝 | 已拒绝 | | [stdio Plugin MCP](environments.md#plugin-mcp) | 已验证:托管、自托管 | 已验证:自托管;已准入:托管 | 已验证:自托管;已准入:托管 | | HTTP Plugin MCP | 已准入,字面值头或 HTTPS bearer | 已准入,字面值头或 HTTPS bearer | 已验证:自托管;已准入:托管;字面值头或 HTTPS bearer | diff --git a/contracts/agents-api/zh/index.md b/contracts/agents-api/zh/index.md index 24f7e3d51..880190c94 100644 --- a/contracts/agents-api/zh/index.md +++ b/contracts/agents-api/zh/index.md @@ -1,7 +1,7 @@ --- title: "OpenAI 兼容性" source: contracts/agents-api/index.md -source_hash: 4bccb4aaa93b5773708c46915447791443532cc3d0a9705124ee9e5812c5bf05 +source_hash: 685e26e496e369c1eb2b1b093ec2e26e6fb6fbdf082ec1b877d9c638b0ac17e6 --- Core 实现下方固定版本的 OpenAI Agents API。本台账列出各项资源的覆盖情况、相对于官方服务的所有差异以及已知缺口。 @@ -83,13 +83,14 @@ Core 自身字段位于 `x_agents_core` 中([Core extensions](../../../docs/zh - Session 的 `agent.tools` 会省略 `tool_search` 声明。 - Core 拒绝 Session MCP 传输中的内联 `authorization`、`headers` 中的 `Authorization` 条目以及非空的 stdio `env`:凭据只来自 Vault(`credential_id`),环境值只来自 Environment(`env_vars`)([MCP](execution-tools.md#mcp))。 - `stdio` MCP 服务器需要工作区部署位置和 `environment` 来源。Claude Code 在其工作区目录中启动服务器,因此 `cwd` 必须与之相同;服务器继承整个进程环境,其中也有模型提供方密钥和其他服务器的 Vault bearer 令牌;且 `allowed_tools` 须为 null。MiniMax Code 在其私有主目录中启动服务器,因此拒绝所有 `stdio` 服务器([MCP](execution-tools.md#mcp))。 -- Core 拒绝非空的 MCP `request_metadata`、`connection_origin` 为 `service` 的 `stdio` 服务器,以及空白的 `server_label`、空的 `credential_id` 和空的 `allowed_tools` 名称;固定版本的 schema 允许这些值但没有定义其含义(400,并说明原因)。 +- Core 拒绝非空的 MCP `request_metadata`,因为没有 Harness 能发送它;同样拒绝 `connection_origin` 为 `service` 的 `stdio` 服务器,以及空白的 `server_label`、空的 `credential_id` 和空的 `allowed_tools` 名称,固定版本的 schema 允许这些值但没有定义其含义(400,并说明原因)。 - Claude 会拒绝包含 `${` 的 MCP `server_url` 或 `headers` 值,因为原生客户端会从持有 Core 所持凭据的进程环境中展开这两处的 `${VAR}`。 - 在 events 202 之后立即删除 Session 会返回 409,因为 Core 会在同一事务中准入该 Turn;官方服务返回 200。 **输入、事件和历史**([Sessions, events and history](sessions-events.md)、[message content](message-content.md)) - 提交给 `none` Session 的输入会同步完成准入;Core 不会模拟官方异步准入窗口。 +- 一次 events 请求最多包含 64 个事件([发送输入](sessions-events.md#send-input));官方上限为 16384。 - 用于恢复等待中 Turn 的函数结果会发出 `turn.in_progress`;取消正在等待函数结果的 Turn 会发出临时 `agent.session.in_progress`。 - 原生 Harness 放弃的函数调用(例如根关闭或中断子级时被丢弃的 Subagent 调用,或超过 120 秒超时的 MiniMax Code 调用)会被撤回:它不再是待执行操作,之后为它提交结果会返回 409 `conflict_error`。官方 API 没有撤回机制。 - 在 Turn 中途接入流时,不会发送补发 Item 快照。 @@ -125,7 +126,7 @@ Core 自身字段位于 `x_agents_core` 中([Core extensions](../../../docs/zh **配置和工具** - 所选 Harness 未声明的 `reasoning.effort`、`reasoning.summary`、`service_tier`、`text.verbosity`、`web_search` 模式或设置、`programmatic_tool_calling` 取值,会被保存,但在 Session 准入时被拒绝([Harness 能力](harness-capabilities.md#execution-and-input))。 -- Harness 对工具、结构化输出、延迟发现、subagents 和 MCP 的支持因 Harness 和部署位置而异;请参阅 [Harness capabilities](harness-capabilities.md)。MiniMax Code 不支持图像输入。 +- Harness 对工具、结构化输出、延迟发现、subagents 和 MCP 的支持因 Harness 和部署位置而异;请参阅 [Harness capabilities](harness-capabilities.md)。MiniMax Code 不支持图像输入,且 `instructions` 最多 32 KiB。 - 由模型推导出的推理默认值不会被解析确定。 **执行和历史** @@ -136,7 +137,8 @@ Core 自身字段位于 `x_agents_core` 中([Core extensions](../../../docs/zh - 固定版本的 Codex 可能会丢失在订阅其流之前发出的命令输出。 - Claude Code 和 MiniMax Code 都不报告公共用量。 - Runtime 协议接受 `agent_message` 和 `interrupt_subagent_call` Item,但没有 Harness 适配器会产生它们。 -- 原生文件变更没有对应的官方 Item 类型([Turns and Items](sessions-events.md#turns-and-items))。 +- Codex 文件变更没有对应的官方 Item 类型([Turns and Items](sessions-events.md#turns-and-items))。 +- Claude Code 一个 Turn 最多处理 64 条消息(含开头输入,见 [Runtime 边界](message-content.md#runtime-boundary));收到更多消息的 Turn 以 `internal_error` 失败。 - 以 `/` 开头的消息到达 MiniMax Code 时末尾多一个空行,否则 ACP 会将其作为操作员命令执行。 - 对于原生副作用,Core 不提供崩溃安全或恰好一次保证;已认领的工作若不重放,会在重启后失败。 - 图像上的 `file_id` 和 `detail` 会被拒绝;每个 Harness 声明自己接受哪些 `image_url`([消息内容](message-content.md#images))。 @@ -146,7 +148,7 @@ Core 自身字段位于 `x_agents_core` 中([Core extensions](../../../docs/zh - Hosted 工作区准入受[租户容量策略](../../../docs/zh/configuration.md#tenant-capacity)及既有[输入预留截止时间](environments.md#reservations)限制。 - MiniMax Code 未声明 `retained_native_history`,因此无法在 microsandbox 上运行(其每个 Environment 都使用工作区存储);Core 会拒绝该 Session([Harness 能力](./harness-capabilities.md))。 - 只有 `enabled` 网络可以运行,因此需要 `disabled` 或 `restricted` 的 Session 会被拒绝([restricted network policy](environments.md#restricted-network))。 -- 非空的 `packages.system` 会被拒绝;系统软件包必须预先安装。 +- 除 null 或空列表以外的 `packages.system` 都会被拒绝;系统软件包必须预先安装。 - 冷续接不恢复进程内存、后台进程或临时系统盘修改,已完成的 setup 不会重放(参见 [Environment 持久化](environments.md#runtime-capability-preparation))。在使用保留 NFS 存储的兼容 microsandbox 节点上,Codex 和 Claude Code 已通过[检查点转移](../../../docs/zh/sandbox-provider.md#checkpoint-transfer)以及[已确认的计算丢失](../../../docs/zh/sandbox-provider.md#confirmed-compute-loss)后续接的资格验证。接管未确认停止的旧写入者、主机崩溃时的持久性及任意崩溃受损历史的完整性仍未通过资格验证。 **Files 和 Environment files** diff --git a/contracts/agents-api/zh/sessions-events.md b/contracts/agents-api/zh/sessions-events.md index a0ba61cb4..f44880d08 100644 --- a/contracts/agents-api/zh/sessions-events.md +++ b/contracts/agents-api/zh/sessions-events.md @@ -1,7 +1,7 @@ --- title: "Session、事件和历史" source: contracts/agents-api/sessions-events.md -source_hash: 2984a8001807449fb923cecbd50fae872c1de9a4a1575ebb055d155b32c361ed +source_hash: cc519d898ede4653a4e8abd0d59da6da9d88e2da1587870a75d5b1bab8809fe0 --- 本契约涵盖 Session 内部发生的事情:发送输入、实时事件流,以及读取 Turn、Item 和使用量的持久化历史。Session 资源本身(创建配置、重试标识、更新、列出和删除)见 [API 行为](wire-semantics.md)。消息和函数结果内容见[消息内容](message-content.md)。[Agents API 指南](../../../docs/zh/api/public-agent-api.md)展示了使用 SDK 和 HTTP 的调用方式。 @@ -45,9 +45,9 @@ Turn 失败后 Session 仍可使用:新输入会启动一个新 Turn。后来 ## 发送输入 {#send-input} -`POST /v1/agents/sessions/{session_id}/events` 接收包含 1 到 16384 个事件的有序数组:`agent.session.input.message`、`agent.session.input.cancel` 和 `agent.session.input.tool_result`。整个批次以原子方式接纳;批次存储后且在任何 Harness 读取它之前,响应为无正文的 202。接纳绝不表示已在原生侧应用。 +`POST /v1/agents/sessions/{session_id}/events` 接收包含 1 到 64 个事件的有序数组:`agent.session.input.message`、`agent.session.input.cancel` 和 `agent.session.input.tool_result`。整个批次以原子方式接纳;批次存储后且在任何 Harness 读取它之前,响应为无正文的 202。接纳绝不表示已在原生侧应用。 -- **限制。** 请求正文最多为 4 MiB,单次请求存储的输入最多为 2 MiB。 +- **限制。** 请求正文最多为 1 MiB,单次请求存储的输入最多为 512 KiB。 - **空值批次。** 将 `events` 显式设为 `null` 无效。 - **空批次。** `{"events": []}` 会检查 Session 是否存在并返回 202。它不会创建 Turn、Item 或重试标识。 - **重试。** 最长 128 字节的 `Idempotency-Key` 标识整个有序批次。相同密钥和批次会再次返回 202,而不会重复接纳任何内容;相同密钥搭配另一个批次会返回 409 `idempotency_conflict`。不带密钥的请求始终是新请求。 @@ -111,7 +111,7 @@ Turn 失败后 Session 仍可使用:新输入会启动一个新 Turn。后来 - **生命周期。** 流会跨 Turn 保持打开,并在 Turn 失败后继续存在。流会在 Session 被删除时,或在 [Environment 初始化失败](#environment-initialization-failure)产生终止 `agent.session.failed` 后结束;在该失败之后打开的流会保持打开。每 15 秒发送一次 keepalive 注释。 - **缓冲区。** Core 为每个 Session 最多保留 256 个事件和 64 MiB 事件,必要时还会额外容纳一个更大的事件。读取方落后于缓冲区时,会收到一个 `error` 事件,其 type 为 `server_error`、code 为 `stream_interrupted`,随后流会关闭。套接字写入阻塞五秒也会关闭流。执行过程绝不会等待读取方。 - **密钥重新检查。** 打开的流在空闲期间以及发送输出前,每秒最多重新检查一次原始项目 API 密钥。撤销密钥或归档项目会关闭流,身份验证失败也会关闭流。重新检查使用正常的五秒身份验证超时,并且等待期间不会发送任何 Session 数据。已经发送的字节无法收回。 -- **仅根工作。** 子 Turn 和子 Item 不发布 Session 事件;`agent.session.subagent.*` 事件和根协调 Item 会发布。请通过 [Subagent 资源](subagents.md)读取子工作。 +- **仅根工作。** 子 Turn 和子 Item 不发布 Session 事件,Subagent 的 `function_call` Item 除外,它只以 `item.added` 发布一次;`agent.session.subagent.*` 事件和根协调 Item 会发布。请通过 [Subagent 资源](subagents.md)读取子工作。 ### 事件规则 {#event-rules} @@ -121,7 +121,7 @@ Turn 失败后 Session 仍可使用:新输入会启动一个新 Turn。后来 - `item.added` 和 `item.done` 始终携带 `output_index`;输入 Item 的该值为 null。函数结果只发出 `item.added`;`item.done` 用于代理输出。 - 助手消息遵循以下单一顺序:先发送 `content` 为空的进行中 `item.added`,再发送文本为空的 `content_part.added`,然后发送 `output_text.delta` 事件、`output_text.done`、`content_part.done` 和 `item.done`。首次观察时已完整的消息(例如结构化输出)会在一个 `output_text.delta` 中逐字节发送全部文本。完整文本会替换此前累积的增量。 - Codex 命令输出以 `agent.output.command_execution_output.delta` 流式传输,并携带命令的 Item ID 和输出索引。原生输出配额和文本转换规则适用,因此这些增量并非逐字节捕获的原始结果;完整 Item 才是权威结果。 -- reasoning Item 遵循以下单一顺序:先发送 `summary` 为空的进行中 `item.added`;然后按顺序对每个 `summary_text` 部分发送文本为空的 `reasoning_summary_part.added` 和 `reasoning_summary_text.delta` 事件,并在下一个部分开始或 Item 结束时发送 `reasoning_summary_text.done` 和 `reasoning_summary_part.done`;最后发送 `item.done`。被取消或失败的 Turn 截断的部分,其 `reasoning_summary_part.done` 的 `status` 为 `incomplete`,否则为 null。子 Turn 快照改写了先前文本时,新文本只出现在 done 事件中。 +- reasoning Item 遵循以下单一顺序:先发送 `summary` 为空的进行中 `item.added`;然后按顺序对每个 `summary_text` 部分发送文本为空的 `reasoning_summary_part.added` 和 `reasoning_summary_text.delta` 事件,并在下一个部分开始或 Item 结束时发送 `reasoning_summary_text.done` 和 `reasoning_summary_part.done`;最后发送 `item.done`。被取消或失败的 Turn 截断的部分,其 `reasoning_summary_part.done` 的 `status` 为 `incomplete`,否则为 null。 - 已取消或失败的 Turn 会将其未完成的 Item 标记为 `incomplete`,并保留其部分内容。 - 函数调用会一直保留在 `required_actions` 中,直到 Harness 应用其结果,或取消操作或 Turn 结束将其移除。重复通知不会发出新状态。 @@ -138,7 +138,7 @@ Turn 和 Item 列表接受 `after`、`limit` 和 `order`([列表规则](wire-s | `message` | 用户或助手内容片段。Harness 报告阶段时,`phase` 为其阶段(`commentary`、`final_answer`),否则为 null | | `command_execution` | 命令、报告的输出、退出代码、持续时间和工作目录 | | `mcp_call` | 服务器和工具标识、参数、结构化结果或错误 | -| `function_call`、`function_call_output` | 关联的调用及其结果。结果始终携带 `output` 和 `error`;提交时省略这些字段则值为 null。存储的结果会保留提交时字段是否存在的信息。原生文件更改显示为 `apply_patch` 函数调用,更改内容作为参数,不会生成虚构结果 | +| `function_call`、`function_call_output` | 关联的调用及其结果。结果始终携带 `output` 和 `error`;提交时省略这些字段则值为 null。存储的结果会保留提交时字段是否存在的信息。Codex 文件更改显示为 `apply_patch` 函数调用,更改内容作为参数,不会生成虚构结果 | | `web_search_call` | 支持的操作字段(`search`、`open_page`、`find_in_page`、`other`);Claude Code 以 `search` 报告 `WebSearch`,以 `open_page` 报告 `WebFetch` | | `reasoning` | 一个原生推理 item,每个原生部分对应一个 `summary_text` 部分,仅在 Agent 设置了 `reasoning.summary` 时出现;为 null(默认)时,任何 Turn(根或子)都没有 `reasoning` Item 和 reasoning 事件。文本内容见 [OpenAI 兼容性](index.md#differences-from-openai)。子 Turn 的 Item 状态可以为 null | | `agent_message` 和 Subagent 协调调用 | 见 [Subagents](subagents.md)。`agent_message` 没有状态 | diff --git a/services/core/internal/api/environment_packages_test.go b/services/core/internal/api/environment_packages_test.go index bd350dec2..83f7d7219 100644 --- a/services/core/internal/api/environment_packages_test.go +++ b/services/core/internal/api/environment_packages_test.go @@ -10,7 +10,7 @@ import ( ) func TestSystemPackagesRejectedAtEveryInput(t *testing.T) { - for _, value := range []string{`["jq"]`} { + for _, value := range []string{`["jq"]`, `"jq"`} { packages := `"packages":{"npm":["semver"],"python":["packaging"],"system":` + value + `}` t.Run(value, func(t *testing.T) { if _, err := decodeTemplateInput([]byte("{" + packages + "}")); !errors.Is(err, errSystemPackages) { diff --git a/services/core/internal/api/environment_setup.go b/services/core/internal/api/environment_setup.go index 8e0958841..45ddccfb8 100644 --- a/services/core/internal/api/environment_setup.go +++ b/services/core/internal/api/environment_setup.go @@ -25,10 +25,7 @@ func rejectSystemPackages(raw json.RawMessage) error { return nil } var system []json.RawMessage - if json.Unmarshal(value, &system) != nil { - return sessions.ErrInvalidInput - } - if len(system) != 0 { + if json.Unmarshal(value, &system) != nil || len(system) != 0 { return errSystemPackages } return nil diff --git a/services/core/internal/api/inputs.go b/services/core/internal/api/inputs.go index 98d476978..a55265be1 100644 --- a/services/core/internal/api/inputs.go +++ b/services/core/internal/api/inputs.go @@ -21,7 +21,7 @@ type InputAdmission interface { } func (h *Handler) createEvents(w http.ResponseWriter, r *http.Request) { - raw, ok := readJSONObjectLimit(w, r, 4<<20, "Request exceeds 4 MiB.") + raw, ok := readJSONObject(w, r) if !ok { return } @@ -77,7 +77,7 @@ func (h *Handler) createEvents(w http.ResponseWriter, r *http.Request) { } func executionInputs(events []json.RawMessage) ([]sessions.Input, error) { - if len(events) == 0 || len(events) > 16384 { + if len(events) == 0 || len(events) > sessions.MaxInputBatch { return nil, sessions.ErrInvalidInput } inputs := make([]sessions.Input, 0, len(events)) diff --git a/services/core/internal/api/mcp_configuration.go b/services/core/internal/api/mcp_configuration.go index cc9927111..23829dcdb 100644 --- a/services/core/internal/api/mcp_configuration.go +++ b/services/core/internal/api/mcp_configuration.go @@ -51,7 +51,7 @@ func resolveMCPTool(raw json.RawMessage, saved bool) (json.RawMessage, error) { return nil, errors.New("MCP credential_id must be null or a nonempty string, and stdio servers take none.") } if len(input.RequestMetadata) != 0 { - return nil, errors.New("Nonempty MCP request_metadata is not supported yet.") + return nil, errors.New("No engine sends MCP request_metadata.") } var allowed *[]string if input.AllowedTools != nil { diff --git a/services/core/internal/sessions/inputs.go b/services/core/internal/sessions/inputs.go index 48aae4155..c4bb79be9 100644 --- a/services/core/internal/sessions/inputs.go +++ b/services/core/internal/sessions/inputs.go @@ -39,21 +39,25 @@ func ValidateInputKey(key string) error { return nil } -// ValidateInputs checks and normalizes an input batch: 1..16384 messages (the -// official events bound), cancellations and function results whose nonempty -// JSON object payloads total at most 2 MiB, which fits 16384 minimal messages, -// an empty cancellation payload and a well-formed function result. It returns -// the normalized batch and its encoding, the batch's retry identity. +// MaxInputBatch is the most events one request admits; the input reservation +// check in the baseline migration holds the same bound. +const MaxInputBatch = 64 + +// ValidateInputs checks and normalizes an input batch: 1..MaxInputBatch messages, +// cancellations and function results whose nonempty JSON object payloads total +// at most 512 KiB, an empty cancellation payload and a well-formed function +// result. It returns the normalized batch and its encoding, the batch's retry +// identity. func ValidateInputs(inputs []Input) ([]Input, json.RawMessage, error) { - if len(inputs) == 0 || len(inputs) > 16384 { - return nil, nil, fmt.Errorf("%w: input batch must contain 1..16384 events", ErrInvalidInput) + if len(inputs) == 0 || len(inputs) > MaxInputBatch { + return nil, nil, fmt.Errorf("%w: input batch must contain 1..%d events", ErrInvalidInput, MaxInputBatch) } batch := make([]Input, len(inputs)) size := 0 for i, input := range inputs { size += len(input.Payload) - if size > 2<<20 || len(input.Payload) == 0 || (input.Kind != "message" && input.Kind != "cancel" && input.Kind != "tool_result") { - return nil, nil, fmt.Errorf("%w: input payloads must be nonempty and total at most 2 MiB", ErrInvalidInput) + if size > 512*1024 || len(input.Payload) == 0 || (input.Kind != "message" && input.Kind != "cancel" && input.Kind != "tool_result") { + return nil, nil, fmt.Errorf("%w: input payloads must be nonempty and total at most 512 KiB", ErrInvalidInput) } payload, err := jsonobject.Normalize(input.Payload) if err != nil { diff --git a/services/core/internal/sessions/inputs_test.go b/services/core/internal/sessions/inputs_test.go index c2884b5d0..d847b3f82 100644 --- a/services/core/internal/sessions/inputs_test.go +++ b/services/core/internal/sessions/inputs_test.go @@ -136,12 +136,12 @@ func TestValidateInputs(t *testing.T) { } for name, inputs := range map[string][]Input{ "no inputs": nil, - "16385 inputs": make([]Input, 16385), + "65 inputs": make([]Input, 65), "unsupported kind": {messageInput("ok"), {Kind: "unsupported", Payload: json.RawMessage(`{}`)}}, "missing payload": {{Kind: "message"}}, "array payload": {{Kind: "message", Payload: json.RawMessage(`[]`)}}, "cancel with a target": {{Kind: "cancel", Payload: json.RawMessage(`{"target":"other"}`)}}, - "over 2 MiB": {messageInput(strings.Repeat("x", 1200*1024)), messageInput(strings.Repeat("y", 1200*1024))}, + "over 512 KiB": {messageInput(strings.Repeat("x", 300*1024)), messageInput(strings.Repeat("y", 300*1024))}, } { if _, _, err := ValidateInputs(inputs); !errors.Is(err, ErrInvalidInput) { t.Errorf("%s: %v", name, err) @@ -149,16 +149,6 @@ func TestValidateInputs(t *testing.T) { } } -func TestValidateInputsFitsTheOfficialMaximum(t *testing.T) { - inputs := make([]Input, 16384) - for i := range inputs { - inputs[i] = messageInput("a") - } - if _, _, err := ValidateInputs(inputs); err != nil { - t.Fatal(err) - } -} - func TestValidateMessageInputs(t *testing.T) { if _, encoded, err := validateMessageInputs([]Input{messageInput("hi")}); err != nil || string(encoded) != `[{"kind":"message","payload":`+hi+`}]` { t.Fatalf("batch %s, %v", encoded, err) diff --git a/services/core/tests/integration/function_execution_test.go b/services/core/tests/integration/function_execution_test.go index e4a163f26..4889c2143 100644 --- a/services/core/tests/integration/function_execution_test.go +++ b/services/core/tests/integration/function_execution_test.go @@ -141,9 +141,9 @@ func TestExecutionSubagentFunctionCallIsARequiredActionOfTheRootTurn(t *testing. if err != nil || state.LastTurn.ID != input.TurnID || state.LastTurn.Status != sessions.TurnWaiting || len(childItems.Data) != 1 || childItems.Data[0].CallID != action.CallID { t.Fatal(state, childItems, err) } - // The official SDK answers with the child Turn ID of the call's event. + // The root Turn answers the call; the child Turn ID answer has its own test. answer := json.RawMessage(`{"success":true,"output":"saved"}`) - if err := SubmitFixtureFunctionResult(t.Context(), h.s, h.tenant, h.session.ID, items.Identity(child.ID, "turn:child-turn"), action.CallID, answer); err != nil { + if err := SubmitFixtureFunctionResult(t.Context(), h.s, h.tenant, h.session.ID, input.TurnID, action.CallID, answer); err != nil { t.Fatal(err) } var reply proto.FunctionResultPayload diff --git a/services/core/tests/integration/inputs_test.go b/services/core/tests/integration/inputs_test.go index 36dd59666..91302ff3f 100644 --- a/services/core/tests/integration/inputs_test.go +++ b/services/core/tests/integration/inputs_test.go @@ -94,6 +94,19 @@ func reserveEnvironmentInput(t *testing.T, s *Store, tenant, session, key string return got } +// The largest batch the events route admits fits an Environment reservation. +func TestEnvironmentInputReservationHoldsTheLargestBatch(t *testing.T) { + s, _ := testStore(t) + tenant, session := environmentInputSession(t, s) + inputs := make([]sessions.Input, sessions.MaxInputBatch) + for i := range inputs { + inputs[i] = messageInput("input") + } + if _, err := sessionService(t, s).ReserveEnvironmentInput(t.Context(), tenant, session.ID, "largest", inputs); err != nil { + t.Fatal(err) + } +} + // cancelEnvironmentInput cancels the Session's pending Environment input as // Session cancellation does, then reads the reservation back. func cancelEnvironmentInput(ctx context.Context, s *Store, tenant, session, reservation string) (sessions.EnvironmentInputReservation, error) {