c709b3d9b2
为 agent-cli 增加 stdio JSON-RPC 2.0 服务入口与运行控制协议 补充 Host 实时流监听和流式取消入口 新增 App Server 子进程闭环测试及协议文档 更新 README、架构、TODO 与共享决策记录
648 lines
48 KiB
Markdown
648 lines
48 KiB
Markdown
# agent-runtime
|
||
|
||
一个与具体业务项目解耦的 Rust Agent 内核和单 Agent 主程序。
|
||
|
||
原始 P0–P6 建设计划与逐项状态见
|
||
[`docs/【计划】独立通用Agent内核与单Agent程序建设计划-2026-09-02.md`](docs/【计划】独立通用Agent内核与单Agent程序建设计划-2026-09-02.md)。本 README 只描述当前已实现的子集,不能作为完整计划的替代。
|
||
|
||
当前消息持久化按 Host 执行尝试内的已提交事件位置衔接 checkpoint 与 trace,避免正常工具
|
||
完成、连续工具和审批恢复重复写入同一消息;压缩前先提交旧上下文的工具结果。集成回归逐条比较
|
||
Engine 输出与 Runtime 消息,并从空快照重放事件;Fake CLI smoke 也会重新打开 SQLite 只读核验消息。
|
||
|
||
Host 的 durable 控制面已进一步下沉到 `agent-runtime-sqlite::RuntimeService`:取消、无主失败收口、
|
||
审批决议和 Provider/Tool 对账由 Runtime 负责,Host 只保留薄委托;Engine 执行、checkpoint/trace、
|
||
worker 和外部工具桥仍留在 Host。该拆分不改公开 Host API、SQLite schema 或 Cargo.lock。
|
||
Host 内部桥接按职责位于私有 `tools.rs`、`mcp.rs`、`context.rs`、`external.rs`、`checkpoint.rs`、
|
||
`codex.rs` 和 `execution.rs`;根模块显式 re-export 稳定类型。`execution.rs` 只承接 run 入口、
|
||
worker/lease 领取和执行恢复编排,checkpoint/trace 与具体适配器仍由各自模块负责。
|
||
公开许可证、registry、自动 webhook、跨主机调度及全量 Codex schema 不属于本期完成门槛,
|
||
以权威计划「原始范围复核」为准,不采用下方历史增量中的扩大范围表述。
|
||
|
||
当前仓库先保证一个最小闭环:中立运行时契约、已提交边界内可重放的事件状态、模型与工具循环、SQLite durable Runtime,以及可替换的 MCP/Skill/Provider 适配器。Codex 外部 backend 和 DAG 编排基础已经作为独立库提供;编排器目前包含有界的配额、消息去重、节点隔离/修复和可选的任务图/协调器原子快照,但真实 Codex wire、HTTP 服务和持久化的完整多 Agent 调度仍不进入内核依赖。
|
||
|
||
`agent app-server --stdio` 是独立 Agent 自己的 JSON-RPC 2.0 JSONL 服务入口,不是 Codex
|
||
App Server 兼容层。它复用 `agent-host` 的 run/lease/checkpoint/approval 控制,在一个连接
|
||
内按“先接受响应、后异步通知”运行单个 worker,支持查询、取消、审批恢复和 durable 事件;
|
||
具体协议见 `rust/docs/【协议】Agent应用服务标准输入输出-2026-09-09.md`。AGC 可以把它当作
|
||
外部 Agent 子进程连接,双方不共享 Rust 类型或 SQLite。
|
||
|
||
独立 workspace 的工具链由 [`rust-toolchain.toml`](./rust-toolchain.toml) 固定为
|
||
Rust 1.96,并声明 `rustfmt` 与 `clippy` 组件;独立 CI runner 需要在执行 workflow
|
||
前预装同一工具链和组件。这样 `cargo check`、测试、格式化与 Clippy 使用同一编译器,
|
||
不会依赖父仓库的工具链配置。
|
||
|
||
workspace 内部 path 依赖带有 `0.1.0` 版本要求,便于后续按依赖顺序发布到私有
|
||
registry;当前仍未发布独立远程仓库,`cargo package --list` 只作为本地 manifest
|
||
预检,不代表 registry 中已经存在这些 crate。
|
||
|
||
`scripts/check-package-manifests.sh` 使用离线 `cargo package --list --no-verify`
|
||
检查 15 个 crate 的 Rust 版本、描述、license、内部 path 依赖和待发布文件边界;
|
||
未显式设置 `CARGO_TARGET_DIR` 时,临时 target 放在 `~/data/tmp`(可用
|
||
`AGENT_PACKAGE_TMPDIR` 覆盖)并在退出时清理。它不会联网、上传 crate 或改变 registry。
|
||
依赖其它内部 crate 的完整
|
||
`cargo package` 校验必须在目标 registry 按依赖顺序发布后进行。
|
||
|
||
依赖边界可用 `./scripts/check-dependencies.sh Cargo.toml` 离线复核;发布前的
|
||
manifest/打包边界可用 `./scripts/check-package-manifests.sh Cargo.toml` 复核;它们检查
|
||
workspace path 依赖、`agent-runtime-core` 的反向依赖和 `agent-runtime-contracts` 的
|
||
适配器黑名单,不替代带漏洞数据库的
|
||
`cargo-audit`/`cargo-deny` 报告。审计范围和独立 workspace 复制验收见
|
||
[`docs/【审计】独立Agent依赖边界与CI验收-2026-09-02.md`](docs/【审计】独立Agent依赖边界与CI验收-2026-09-02.md)。
|
||
该脚本还单独检查 portable `agent-runtime --no-default-features` 不带 SQLite,并确认
|
||
`agent-runtime-sqlite` 只沿 `agent-runtime + agent-storage-sqlite` 单向装配。
|
||
|
||
## 2026-09-04 当前增量
|
||
|
||
- SQLite RuntimeStore 从空快照重放完整 event history,校验 revision 连续性与事件语义;
|
||
event log 缺失、断档或语义篡改均 fail-closed。新增
|
||
`runtime_store_rejects_event_history_gap_on_load_and_commit`;Storage all-features
|
||
5+45=50,直接 no-default 4+36=40。
|
||
- Skill discovery/list 从 discovery 阶段即受 `max_body_bytes` 硬上限,正文超限返回
|
||
`BodyTooLarge`;新增 `discovery_and_list_reject正文超过配置上限`,Skill 当前 28。
|
||
- Codex process backend runtime event bridge 显式映射 request→notification→result,并
|
||
保留显式 handler 变体;新增
|
||
`app_server_process_backend_runtime_event_bridge_maps_notification_order`,Codex 当前 81。
|
||
- 原始 P0–P6 仍部分完成;根 Vitest 的 `3189/3189` 为历史记录,本轮工作区未安装
|
||
`vitest`,尝试以退出码 127 结束,未计入当前通过项。
|
||
|
||
## 2026-09-05 当前增量
|
||
|
||
- Core 扩展值对象和外部端口(`ToolBinding`、`SkillDefinition`、`SkillActivation`、
|
||
`AgentDescriptor`、`BackendRequest`、`BackendResult`、`ToolContext`)在公开 serde/
|
||
兼容入口复验;`backend_result_as_tool` 拒绝身份错配和未知副作用,Core 当前为
|
||
30 个单元、20 个 `core_contracts` 集成、1 个 `tool_context_contracts` 集成和 1 个 doctest。
|
||
- Engine 在调用 `ContextSource` 前验证 `ContextRequest`;Host/Engine 在工具、Skill、MCP
|
||
和外部 backend dispatch 前验证上下文与调用合同。Runtime/Storage/Host 提供固定排序、
|
||
状态/run 过滤和硬上限的只读 `list_external_sessions`,不会自动对账或重放。
|
||
- `agent-runtime` 现在只包含 portable `DurableRuntime<S>`、`RuntimeSnapshotService`、
|
||
`WorkerLease`/`RunHandle` 与中立 `DurableStore` 合同;SQLite-specific `RuntimeService`、
|
||
records、错误和跨表事务已迁移到 `agent-runtime-sqlite`。后者依赖
|
||
`agent-runtime` + `agent-storage-sqlite`,不反向污染 portable runtime。
|
||
- `agent-runtime-sqlite` 的 `RuntimeService` 通过 `SqliteDurableStore` 提供
|
||
`prepare_run*`、run/session 查询、claim/heartbeat/release、checkpoint fencing、approval
|
||
pending-only CAS、external-session 候选、request-cancel/stale 扫描和 runtime-aware
|
||
finish/recovery command;Host 统一从该 crate 装配,未保留 `agent_runtime::RuntimeService`
|
||
平行 re-export。
|
||
- `DurableRuntime<S: DurableStore>` 提供不绑定 SQLite 的拥有式控制面 facade;可直接注入、
|
||
借用或取回任意 durable adapter。SQLite convenience API 位于
|
||
`agent-runtime-sqlite::RuntimeService`,不复制连接或状态。
|
||
- Runtime 测试还提供 `cfg(test)` 的 `InMemoryDurableStore` contract harness,实际覆盖
|
||
bundle、lease、snapshot CAS、safe requeue、finish 和 expired recovery;它只证明泛型 facade
|
||
可接入非 SQLite adapter,不改变生产构建。
|
||
- Host 的工具请求/结果现在写入 `tool_calls` durable 表,支持按 run 查询、JSONL 导出和
|
||
相同 identity 幂等重试;请求/结果与对应 Core runtime snapshot/events 通过
|
||
`DurableToolCallRuntimeCommit` 在 SQLite 单事务中提交,checkpoint 仍保持独立。
|
||
- 当调用方已同时拥有工具结果和 Engine 游标时,可使用
|
||
`DurableToolCallCheckpointRuntimeCommit`;`DurableStore`/`RuntimeService` 会把工具行、
|
||
checkpoint 与 Core runtime snapshot/events 在同一个 SQLite `IMMEDIATE` 事务中提交,
|
||
并以 lease/CAS 失败整体回滚。Host 的 `ToolCompleted` trace 在已有 checkpoint 时已复用
|
||
该边界;Host 对首次 `awaiting_approval` checkpoint 已把 `ToolRequested`、工具行、
|
||
checkpoint 和 Core runtime event 放进同一事务,后续普通 checkpoint 与终态仍保持各自
|
||
边界,不能把这条窄接线误读成全链路全局事务。
|
||
- 最新隔离门禁:all/no-default workspace 测试、check、Clippy `-D warnings`、rustdoc、
|
||
fmt、Storage 直接 no-default、依赖/manifest、独立复制、Fake 2/2、能力 10/10、shell、
|
||
编码和 `git diff --check` 均通过;Storage 为 all-features 7+53、直接 no-default 6+36,
|
||
Runtime portable 8 + Runtime SQLite 31、Host 80(另有 7 个消息持久化集成测试)、Engine 55、Codex 101;编排、Host、
|
||
Skill、SQLite Storage 测试默认回退 `~/data/tmp`,显式 `TMPDIR` 仍可覆盖。
|
||
- 原始 P0–P6 的本地可交付出口已逐项通过;整体交付仍保留独立远端 CI/registry/许可证、
|
||
真实 Provider/Codex session、自动外部对账/订阅以及真实 Codex generated wire 的外部证据边界。
|
||
|
||
## 2026-09-06 当前增量
|
||
|
||
- `CodexSessionMetadataSink` 增加向后兼容的 `persist_lifecycle` 扩展;ProcessBackend 在
|
||
request 成功、post-dispatch 失败和匹配 cancel 收束后发送中立生命周期观察,Host sink
|
||
通过 read/merge/write 更新 `external_sessions`,保留 custom audit 字段并防止 terminal
|
||
状态复活。
|
||
- 新增 `agent-runtime-contracts` crate,承接 DurableStore command/view/trait;`agent-runtime`
|
||
已完成 portable 化;SQLite-specific Service/adapter 位于独立的
|
||
`agent-runtime-sqlite` crate,最终依赖树不再把 SQLite 带入 `agent-runtime`。
|
||
- 长连接 `thread/start`/`turn/start` accepted response 在 child 仍存活时记录为 `active`,
|
||
`ProcessControl` 可读取已回收 child 的退出码;进程级 sink 支持一次性自然退出、显式终止、
|
||
cancel/timeout/drop 观察,并可由 session sink 转发。
|
||
- Codex lifecycle fixture 与 Host merge/terminal 回归通过(含
|
||
`app_server_process_lifecycle_sink_reports_timeout`、
|
||
`app_server_process_lifecycle_sink_distinguishes_reader_eof`);当前定向 Codex 101、Host 80、MCP
|
||
52,Clippy `-D warnings` 通过。协议级 turn interrupt 和真实 generated wire/session
|
||
仍未验收。
|
||
- `JsonRpcAppServerRouter` 提供独立的有界 pending map、后台 reader、乱序 response
|
||
分发和并发 `turn/interrupt` transport 接缝;`CodexAppServerProcessRouter` 将其接入
|
||
ProcessControl 管理的真实 stdio child,但不猜测具体 Codex wire。
|
||
- Host `from_host` 构造的 Codex 中立/0.152.1 typed handler 复用同一 Runtime 的
|
||
`tool_calls`:已完成 call_id 返回缓存,in-flight 重复拒绝,工具错误写入 `error` 终态;
|
||
直接 `new` 构造仍不要求 SQLite。
|
||
- 后台 queue metadata 会在 worker claim 前核对带 `providerKind` marker 的 persisted model;
|
||
模型错配保持 queued 且不触发 Provider,legacy provider-only metadata 继续兼容。
|
||
|
||
## 2026-09-04 当前门禁增量
|
||
|
||
queued 取消的最终条件由 SQLite `RunFinishGuard::QueuedUnclaimed` 在同一
|
||
`BEGIN IMMEDIATE` 事务内复核;领取竞争会返回明确冲突并由 Host 发出 cooperative
|
||
cancel,不会把仍持有 lease 的 run 写成 cancelled。Codex JSONL channel 对空白 keep-alive
|
||
行采用循环跳过,连续 8192 行空帧有回归测试。当前 Runtime/Host/Codex/Storage 计数和
|
||
完整 P0–P6 状态以 [`docs/【计划】独立通用Agent内核与单Agent程序建设计划-2026-09-02.md`](docs/【计划】独立通用Agent内核与单Agent程序建设计划-2026-09-02.md)
|
||
及最新验收记录为准;远端仓库/CI、真实 Provider/Codex wire/session 和自动外部恢复仍
|
||
未完成,Runtime/SQLite 物理拆分已由 `agent-runtime-sqlite` 完成。
|
||
|
||
Runtime-only snapshot/event 还提供公开 `DynRuntimeStore` newtype,可承载
|
||
`Box<dyn RuntimeStore>` 并注入 `RuntimeSnapshotService`;`agent-runtime-sqlite::RuntimeService::snapshot_store()`
|
||
还可把同一 SQLite 状态以该窄 facade 暴露给调用方;它保留 typed CAS/error,
|
||
不把 SQLite durable run/session/lease/checkpoint 伪装成已经可替换。
|
||
|
||
上下文压缩器被视为不可信扩展:Engine 会拒绝其返回的 `Tool` 角色或结构化
|
||
`ToolCall`/`ToolResult`,避免下一次 Provider 请求伪造工具历史。Core 的默认
|
||
`ModelProvider::stream` 在只有 `complete` 实现时也会保留 tool-call delta 和 usage
|
||
事件,流式观察不会静默丢失结构化结果;压缩 Provider 响应的 content 中出现结构化
|
||
工具块也会直接拒绝,不会静默过滤。
|
||
|
||
清理构建目录后可用临时数据库直接验证程序入口:
|
||
|
||
```bash
|
||
mkdir -p "$HOME/data/tmp"
|
||
AGENT_DB="$HOME/data/tmp/agent.db" cargo run --locked -p agent-cli -- run "hello"
|
||
```
|
||
|
||
该命令默认选择 Fake Provider,完成一次工具调用闭环;真实 OpenAI 请求仍需显式配置
|
||
凭据和 endpoint。
|
||
|
||
真实 LLM 能力评测(固定输入、最终输出和流式轨迹断言)使用:
|
||
|
||
```bash
|
||
./scripts/run-agent-llm-eval.sh --real --from-codex-config
|
||
```
|
||
|
||
该命令显式读取当前 Codex 配置并产生真实请求;默认回归不会联网。
|
||
|
||
## 2026-09-03 继续执行的外部会话边界(历史快照)
|
||
|
||
Codex App Server 可通过 `CodexAppServerBackend::invoke_node_with_runtime_events` 显式
|
||
把 request、事件和结果映射成 Core `RuntimeEvent`;调用方负责把 sink 事件交给 reducer
|
||
或 RuntimeStore,未知事件和未知副作用不会被静默完成。Host 外部工具桥在重开进程后可
|
||
通过 `AgentHost::cancel_external_request(tool_name, request_id)` 查 durable request-id
|
||
别名并调用 backend cancel;没有记录时才执行兼容的无归属 cancel,成功/失败状态仍需按
|
||
`cancelled`/`unknown` 和显式 reconciliation 处理。
|
||
|
||
本轮后 Codex 为 78 个测试、Host 为 62 个测试,Engine 为 52 个测试,CLI 为 23 个测试,
|
||
Runtime 默认特性为 27 个测试(package-only no-default-features 为 6 个),OpenAI 为 29 个测试;
|
||
Storage 为 all-features 5+44、直接 no-default-features 4+36;运行时快照篡改回归与相关 all/no-default 回归和静态门禁通过。原始 P0–P6 仍为
|
||
“部分完成”:真实 Codex wire/session、自动外部对账、远端仓库/CI、registry/许可证策略、
|
||
完整持久化多 Agent 调度和最终 Host/Runtime 拆分不由本地 fixture 代替。
|
||
|
||
## 2026-09-03 最新复核(历史快照)
|
||
|
||
当时本地实现的测试计数为:Engine 51、Host 61、MCP 47、CLI 23、Codex 78、Runtime 27
|
||
(package-only no-default-features 为 6)、Orchestration 38、Skill 27、OpenAI 29、Fake 4;Core 为 25 个单元测试 + 16 个集成测试,
|
||
Storage 为 all-features 的 5 个单元测试 + 43 个集成测试(48),以及直接
|
||
no-default-features 的 4 个单元测试 + 36 个集成测试(40)。
|
||
|
||
MCP resources/prompts 只会在调用方通过显式 selection 选择后注入上下文;这些外部上下文
|
||
保持不可信,Engine 在模型边界把不可信的 system/developer/assistant 角色降级为 User,避免
|
||
外部文本获得高权限角色;其中结构化 tool-call/tool-result 也会渲染为普通 User 文本,不能
|
||
伪造工具历史。Engine 对 `ProviderResponse.content` 只接受 `Text`/`Image`,结构化
|
||
tool call/result 必须分别走 `tool_calls` 或工具结果回填路径。Host 的
|
||
`AgentHost::with_runtime` 可注入已经装配好的 `RuntimeService`,Host 与调用方共享同一
|
||
durable 控制面,不会重新打开或平行持有 SQLite。Host 的 `CodexHostServerRequestHandler` 处理
|
||
`item/tool/call`,在审批或执行前校验已注册工具的 JSON Schema,再使用 Host 的审批策略和
|
||
ToolRouter;它是同步的低层桥接,不创建 durable approval/checkpoint/audit,`Ask` 以
|
||
JSON-RPC error 返回。另有明确命名的 `codex_01521_server_request_handler`,将已核对的
|
||
0.152.1 `tool`/`callId` 请求转换为 `contentItems`/`success` typed response;它仍只覆盖
|
||
dynamic-tool 子集。需要持久化审批和恢复时仍应走 Engine/Runtime 执行路径。
|
||
|
||
Host 已提供显式 `NamespaceToolResolver` 端口和 `StaticNamespaceToolResolver` 映射表。
|
||
默认未注入映射时,中立和 0.152.1 typed bridge 对显式非 `null` namespace 仍在
|
||
approval/execution 前 fail-closed;调用方显式注册 `(namespace, tool) -> registered_tool`
|
||
后才会继续走同一套工具定义、Schema 和审批校验。缺省或 JSON `null` namespace 仍按全局
|
||
工具名处理,Host 不猜测分隔符或静默改写工具名。
|
||
|
||
MCP stdio 同步 client 等待响应时暂存的 pending 消息队列也有硬上限
|
||
(`MAX_STDIO_PENDING_MESSAGES`,当前为 4096);溢出返回协议错误,不会把跨请求的通知
|
||
积累成无界内存。
|
||
|
||
MCP initialize 在发送 `notifications/initialized` 前会校验服务端返回的
|
||
`protocolVersion`;默认候选是 `2025-06-18`,调用方可配置一个有界候选列表。明确版本拒绝时,
|
||
只有带可重建配置的连接会创建新 transport 尝试下一个版本;缺失、非字符串或未知版本会保持
|
||
连接未初始化,工具调用不会自动重放。
|
||
Skill loader 的 frontmatter 仍是轻量、有界的行式子集,但现在会拒绝未闭合引号/括号、空列表项
|
||
和空工具名,避免畸形元数据被静默解释。需要在同一进程内让多个 Runtime facade 共享一个
|
||
存储实例时,可使用 Core 的 `SharedRuntimeStore<S>`;它只提供 `Arc<Mutex<_>>` 同步和
|
||
`Unavailable` 锁错误映射,不改变 `RuntimeStore` trait 或跨进程语义。
|
||
|
||
通用 `CodexAppServerBackend` 的同步 channel 若需要在阻塞 invoke 期间中断,可显式使用
|
||
`with_interrupt_hook` 注入独立 control transport;hook 不获取 channel mutex,也不自动
|
||
修改 Host/Runtime 状态。未配置独立 transport 时仍使用 channel 自带的串行 interrupt,
|
||
因此不能把该 opt-in 接线当成真实 Codex control wire 或强制取消实现。
|
||
|
||
P4 还提供随 crate 分发的本地 fixture:`agent-mcp/fixtures/stdio-jsonrpc-server.sh` 可用于
|
||
stdio 握手、工具发现和调用回归;`agent-skills/fixtures/skills/review/SKILL.md` 与非法
|
||
frontmatter fixture 用于 metadata-first/显式激活测试。fixture 只返回固定数据,不读取密钥
|
||
或执行外部脚本。
|
||
|
||
编排 JSON 文件快照的 revision CAS 通过同目录 sidecar advisory lock 保护跨进程写入;
|
||
该锁只覆盖本机文件临界区,不是跨主机锁,也不会启动自动 scheduler。
|
||
|
||
原始 P0–P6 仍全部为“部分完成”。尚未有证据的出口包括独立远程仓库/CI、registry 与正式
|
||
许可证策略、真实 Provider/Codex wire/session、自动外部对账/订阅、完整持久化多 Agent 调度、
|
||
跨主机协调,以及最终 Host/Runtime 拆分。
|
||
上下文、提示词和 Skill 元数据的确定性边界见
|
||
[`docs/【审计】上下文提示词与Skill边界-2026-09-03.md`](docs/【审计】上下文提示词与Skill边界-2026-09-03.md)。
|
||
|
||
## 离线依赖漏洞审计
|
||
|
||
`scripts/run-cargo-audit.sh` 是 CI 和本地共用的离线 wrapper。它从脚本自身位置
|
||
解析 workspace 根目录,因而不会因为调用者的当前目录而误审其它 `Cargo.lock`;
|
||
workspace manifest/锁文件、advisory DB 或工具缺失时会直接失败。runner 必须预先
|
||
提供本地 RustSec advisory-db checkout(`RUSTSEC_ADVISORY_DB`),并预装固定版本的
|
||
`cargo-audit`;需要指定可执行文件时设置 `CARGO_AUDIT_BIN`:
|
||
|
||
```bash
|
||
RUSTSEC_ADVISORY_DB=/path/to/advisory-db \
|
||
CARGO_AUDIT_BIN=/path/to/cargo-audit \
|
||
./scripts/run-cargo-audit.sh
|
||
```
|
||
|
||
脚本实际调用 `cargo-audit audit --no-fetch --db <db> --file Cargo.lock`,不会
|
||
安装工具、联网拉取或改写 advisory DB。仓库已用 fake binary 自测参数、工作目录
|
||
及缺失输入;2026-09-04 另用隔离临时目录安装的 `cargo-audit 0.22.2` 与 RustSec advisory-db
|
||
提交 `5a0ebedfe8bdd2e295b171f4162f8c977bcad9a5` 完成真实离线扫描(1239 条 advisory、
|
||
188 个锁定依赖,退出码 0,无漏洞或 warning)。临时工具和数据库已清理;独立远端
|
||
CI 仍需由 runner 提供固定输入并留下运行记录。
|
||
|
||
## 快速开始
|
||
|
||
```bash
|
||
# 从 Genarrative-master 根目录进入独立 workspace;这样 agent.db 也会留在这里。
|
||
cd rust
|
||
cargo test --workspace
|
||
cargo run -p agent-cli -- "把这句话复述一遍"
|
||
AGENT_DB=agent.db cargo run -p agent-cli -- run --background "后台执行一个任务"
|
||
# 设置 OPENAI_API_KEY 后,CLI 会切换到 OpenAI Responses
|
||
OPENAI_API_KEY=... cargo run -p agent-cli -- run "回答一个问题"
|
||
|
||
# OpenAI-compatible 网关:base URL 会自动补成 /responses
|
||
OPENAI_BASE_URL=https://your-gateway.example/v1 \
|
||
OPENAI_API_KEY=... \
|
||
cargo run -p agent-cli -- run "通过网关回答"
|
||
|
||
# 面向脚本的逐行 JSON 输出(前台最后一行 type=result)
|
||
AGENT_PROVIDER=fake cargo run -p agent-cli -- run --no-stream --jsonl "脚本任务"
|
||
```
|
||
|
||
`run --jsonl` 只改变 CLI 的呈现格式,不改变 SQLite 持久化或默认的 pretty JSON
|
||
输出。前台 run 在 Host 返回后将已收集的事件按顺序编码为 NDJSON:每个
|
||
Engine 事件一条 `type=engine_event`,每个 Provider 流事件一条
|
||
`type=stream_event`,最后一条 `type=result` 携带完整 `HostRunOutput`;每行都带
|
||
`session_id`、`run_id` 和 `runtime_id`。因此它是稳定的完成后批次,不是实时流日志。
|
||
后台 `run --background --jsonl` 只输出一条 `type=queued`(含 worker PID 和运行
|
||
身份),worker 的最终结果需另用 `inspect` 或 `export` 查询。
|
||
|
||
## 测试集与真实 Provider smoke
|
||
|
||
仓库提供一组可重复的 Agent 回归用例,先用 Fake Provider 验证工具闭环、流式路径、
|
||
SQLite 持久化和 JSONL 导出,再按需追加一次真实 Provider 请求:
|
||
|
||
```bash
|
||
./scripts/run-agent-test-set.sh # Cargo 测试 + 离线测试集
|
||
./scripts/run-agent-test-set.sh --quick # 只跑离线测试集
|
||
./scripts/run-agent-test-set.sh --quick --real # 追加真实 Provider smoke
|
||
```
|
||
|
||
测试数据和真实 Provider 配置说明见
|
||
[`docs/【测试】Agent测试集与真实Provider接入-2026-09-02.md`](docs/【测试】Agent测试集与真实Provider接入-2026-09-02.md)。真实 smoke 只在显式
|
||
`--real`(或 `AGENT_TEST_REAL_PROVIDER=1`)时读取 key;默认不访问网络。
|
||
测试脚本默认在 `~/data/tmp` 下创建本轮的 `agent-test-set.*` 目录,数据库、Cargo
|
||
target 和中间文件都放在其中;显式设置 `TMPDIR` 或 `AGENT_TEST_TMPDIR` 时尊重调用方的
|
||
临时父目录。脚本只删除自己创建的目录,不会把本轮构建产物留在 workspace 的 `target/`
|
||
中。
|
||
|
||
本地 workspace 回归基线(2026-09-06,`cargo test --locked --workspace
|
||
--all-features --no-fail-fast`)为:CLI 24、Host 80(另有 7 个消息持久化集成测试)、MCP 52、Engine 55、OpenAI
|
||
29、Fake 4、Runtime 36、Codex 101、Orchestration 40、Skill 28;Core 为 30 个
|
||
单元测试加 21 个集成测试,Storage 为 7 个单元测试加 53 个集成测试(共 60,含
|
||
工具调用联合事务、event-history gap、外部 session 候选回归)。
|
||
直接以 `--no-default-features` 测试 Storage 时为 6 个单元测试加 36 个集成测试(共
|
||
42)。Runtime package-only `cargo test --locked --offline -p agent-runtime
|
||
--no-default-features` 另有 6 个 portable 测试;workspace 级 `--no-default-features`
|
||
会因 Host 的默认依赖特性合并 SQLite,不能替代该 package-only 检查。这些是本地命令计数,
|
||
不代表远端 CI 或真实 Provider/许可证数据库已经验收。
|
||
CI 样例同时执行 all-features 与 no-default-features 的 check、test 和 Clippy,避免
|
||
只在默认特性下验证隐藏的可选依赖边界。
|
||
|
||
公开 `validate_tool_arguments` 也会在 JSON Schema 校验前复验 `ToolCall`、
|
||
`ToolDefinition` 及二者的工具名匹配;因此 approval UI 或入队方即使直接使用该预检
|
||
入口,也不会把 serde/兼容构造出的非法调用当成可执行参数。Engine 当前定向测试为
|
||
55 个,内部执行路径仍会在真正审批和工具副作用前再次校验。
|
||
|
||
CLI 默认使用确定性的 Fake Provider,便于离线运行和回归测试;检测到配置的 key
|
||
环境变量(默认 `OPENAI_API_KEY`)时切换到 `agent-provider-openai`。真实 Provider 通过
|
||
`AgentHost` 注入,不把密钥写入会话或日志。
|
||
|
||
OpenAI endpoint 有四类装配入口:
|
||
|
||
- `OpenAiProvider::new(key)` 使用官方默认的
|
||
`https://api.openai.com/v1/responses`。
|
||
- `OpenAiProvider::with_endpoint(key, full_endpoint)` 使用手动指定的完整地址。
|
||
- `OpenAiProvider::with_base_url(key, base_url)` 或
|
||
`OpenAiProvider::with_config(key, &OpenAiProviderConfig)` 使用配置对象;base
|
||
URL 会自动追加 `/responses`。
|
||
- 已创建的 Provider 也可调用 `set_endpoint(full_endpoint)` 或
|
||
`set_base_url(base_url)` 热切换地址;两者先完成同一套 URL/凭据校验,失败时
|
||
保留原 endpoint。
|
||
|
||
`OpenAiProvider::from_env()` 读取 `OPENAI_API_KEY`(可用
|
||
`OPENAI_API_KEY_ENV` 改名),并读取 `OPENAI_ENDPOINT` 或 `OPENAI_BASE_URL`。
|
||
完整 endpoint 优先于 base URL。CLI 还接受 `OPENAI_MODEL`、TOML 的
|
||
`openai_endpoint`/`openai_base_url`;环境变量优先于 `agent.toml`。该适配器发送
|
||
Responses API 的 `model`、`input`、`tools` 等字段,网关需要兼容同一请求/响应
|
||
协议,详见 [OpenAI Responses API reference](https://developers.openai.com/api/reference/cli/resources/responses/methods/create)。
|
||
|
||
Provider 注册时,`ProviderInstanceId` 表示具体实例(例如 `openai-prod`),
|
||
`ProviderProtocolId` 表示 wire 协议(例如 `openai-responses`),两者是不同的强类型。
|
||
`ModelProvider::protocol_id()` 是可选的适配器自描述端口;OpenAI Responses adapter
|
||
会报告 `openai-responses`,Registry 在注册和解析时核对 descriptor,省略 descriptor
|
||
协议时自动补齐。旧/自定义 Provider 返回 `None` 时仍可由调用方声明协议;这项本地
|
||
校验用于防止错配,不替代真实网关兼容矩阵。
|
||
OpenAI 的 Host 装配 helper 会同时绑定一个拥有式上下文压缩器;长消息超过预算时
|
||
会复用同一 Provider 做摘要。自定义 Provider 可在最终选定实例后调用
|
||
`with_provider_context_compressor()`,专用摘要 Provider 则继续使用
|
||
`with_context_compressor()`。
|
||
|
||
最近的适配器增量还收紧了四个边界:MCP 的认证环境变量会在高层连接、HTTP
|
||
直接构造和 stdio 直接构造三条路径一致解析;Skill 激活正文用有界读取抵抗文件在
|
||
检查后的增长;通用 Codex JSON-RPC channel 提供显式 server-request handler(默认
|
||
仍拒绝未知请求),不会把这些适配器能力下沉到 Core。Engine 在 Provider 响应和
|
||
ToolExecutor 结果进入事件、历史或下一次请求前重新执行 Core 构造校验;Codex CLI
|
||
参数过滤也会规范化检查大小写、连字符和 header/bearer 形式的凭据参数。审批记录的专用 binding token
|
||
在 SQLite JSONL 导出和 CLI `approval list/get/allow/deny` 展示边界都会递归移除,
|
||
但 Host 内部仍保留完整记录供显式 `resume` 校验。
|
||
|
||
## Prompt、Skill 与 MCP
|
||
|
||
CLI 不把这些扩展写死在项目代码里,而是通过环境变量显式接入:
|
||
|
||
- `AGENT_SYSTEM_PROMPT`、`AGENT_DEVELOPER_PROMPT`、`AGENT_CONTEXT_PROMPT` 会分别
|
||
形成独立的初始消息;context section 在当前兼容消息合同中走 user 通道。
|
||
- Skill 需要同时设置 `AGENT_SKILL_ROOT`(或冒号分隔的
|
||
`AGENT_SKILL_ROOTS`)和 `AGENT_SKILLS`。名称必须显式列出,不会扫描后自动
|
||
激活:
|
||
|
||
AGENT_SKILL_ROOT=./skills AGENT_SKILLS=review,writer \
|
||
cargo run -p agent-cli -- run "检查这段代码"
|
||
|
||
- MCP 二选一配置 `AGENT_MCP_STDIO_COMMAND`(可选
|
||
`AGENT_MCP_STDIO_ARGS`,空白分隔或 JSON 字符串数组)或
|
||
`AGENT_MCP_HTTP_URL`。HTTP 认证头可通过
|
||
`AGENT_MCP_HTTP_HEADERS`='{"Authorization":"Bearer ..."}' 传入;可选
|
||
`AGENT_MCP_SERVER`、`AGENT_MCP_TIMEOUT_SECS`:
|
||
|
||
AGENT_MCP_SERVER=workspace \
|
||
AGENT_MCP_STDIO_COMMAND=npx \
|
||
AGENT_MCP_STDIO_ARGS='["-y","@modelcontextprotocol/server-filesystem","."]' \
|
||
AGENT_MCP_ALLOW=read_file \
|
||
cargo run -p agent-cli -- run "列出工作区文件"
|
||
|
||
MCP 工具会被命名为 `mcp:<server>:<tool>`。发现目录不会自动授予权限;
|
||
`AGENT_MCP_ALLOW` 中的原始工具名会按当前 server 补全命名空间,也可直接
|
||
写完整名称。stdio 参数按 argv 传递,不经过 shell。
|
||
|
||
transport 对远端不可信输入使用固定上限:单条 stdio JSON-RPC 消息最多 1 MiB,
|
||
Streamable HTTP 响应最多 4 MiB,SSE 单行最多 1 MiB、单事件累计 `data` 最多
|
||
4 MiB;`tools/list`、`resources/list`、`prompts/list` 各最多跟随 1024 页。
|
||
超限会返回协议错误,不会静默截断;这些是 transport 读取上限,不替代 Engine
|
||
的上下文/工具结果预算。
|
||
|
||
`agent.toml` 也可以用 `[[mcp.auth]]` 保存认证环境变量引用,而不把 token
|
||
写进 TOML:
|
||
|
||
[[mcp.auth]]
|
||
variable = "TEAM_MCP_TOKEN"
|
||
target = "http_bearer"
|
||
|
||
stdio 子进程认证使用 `target = "stdio_environment"` 并额外设置 `name`;
|
||
自定义 HTTP 头使用 `target = "http_header"`、`name` 和可选 `prefix`。旧的
|
||
`AGENT_MCP_HTTP_HEADERS` 仅作为运行时兼容入口。
|
||
|
||
Resource/prompt 默认只做能力发现,不会自动进入上下文。需要显式读取时,在
|
||
`[mcp]` 下列出 `context_resources`(URI 数组)或 `context_prompts`(名称数组),
|
||
或设置 `AGENT_MCP_CONTEXT_RESOURCES` / `AGENT_MCP_CONTEXT_PROMPTS`(逗号或空白
|
||
分隔);CLI prompt 使用空参数。Host 会先按 URI/name 精确匹配,再把读取结果作为
|
||
`ContextSource` 的不可信候选项交给 Engine;带 prompt 参数的场景使用库 API 的
|
||
`McpContextSelection::with_prompt_selection`,不会隐式读取整个远端目录。CLI `mcp list`
|
||
只调用一次 `McpClient::capability_snapshot()`,从同一份快照生成 tools 和 fingerprint,
|
||
因而不会重复发 `tools/list`;resources/prompts 的发现错误也会沿原路径返回。
|
||
|
||
Skill 文件采用一个有界的 `SKILL.md` frontmatter 子集:`name` 必填,支持
|
||
`description`、`version`、`allowed-tools` 和字符串扩展字段;解析器不是完整 YAML
|
||
实现。目录发现只读元数据,正文在显式激活时读取并重新校验路径、UTF-8 和大小;
|
||
空正文会被激活拒绝。每个逻辑字段只能出现一次;历史别名 `allowed_tools` 与
|
||
`allowed-tools` 视为同一字段,重复时直接拒绝,避免后值覆盖造成激活语义漂移。
|
||
`allowed-tools` 只作为元数据,不会自动放行工具。
|
||
|
||
`run --background` 只把任务消息和运行身份写入 SQLite;隐藏 worker 会从继承的
|
||
环境重新加载 Skill 并重新建立 MCP 连接。因此扩展配置和 Skill 文件应在 worker
|
||
启动前保持可用;库调用方若自行创建 worker,也要在每个 worker 进程重建这些适配器。
|
||
|
||
排队和终态是原子边界:Host 会一次事务写入 session、queued run、runtime 初始
|
||
快照/事件;正常完成、失败或取消会一次事务更新 run/runtime/session 并清理
|
||
checkpoint。worker 在 claim 前发现 Provider、Skill 或 MCP 配置错误时会把 run
|
||
记录为 `failed`,不会静默留下 queued;claim 后的本地准备错误会先进入
|
||
`reconciling`,保留恢复证据后再释放 lease。
|
||
|
||
Host 默认审批策略只放行无副作用的内置 `echo`;接入自己的工具时,通过
|
||
`with_approval` 显式提供策略。Engine 会把可选的 session/run 身份传给上下文源
|
||
和工具执行器,但不会自动把这些身份拼进模型提示词。
|
||
|
||
可用命令:`run`、`inspect`、`checkpoint`、`export`、`doctor`、`cancel`、`resume`、`resume-safe`、`reconcile`、`reconcile --stale [limit]`、`reconcile-provider`、`reconcile-tool`、`approval list/get/allow/deny/resume`、`codex validate`。需要后台运行时使用
|
||
`run --background`;它先持久化 `queued` run,再启动一个隐藏 worker。`cancel`
|
||
通过 SQLite 发出跨进程可见的 cooperative 请求,当前 Provider/工具调用返回后在
|
||
下一个 step 收口。每个 worker 还持有带 token 的 lease 并定期心跳,避免第二个
|
||
worker 写回迟到结果。`resume` 只重新领取尚未启动的 `queued` run,不会重放已经运行
|
||
过的外部调用;Engine 会在 Provider/审批/工具边界写入当前 run 的增量 checkpoint,
|
||
`reconcile` 只把 lease 已过期或历史上没有 lease 的 `running` run 转成 `reconciling`;
|
||
`reconcile --stale [limit]` 对同一类 run 做一次固定排序、有界批量扫描(省略 limit
|
||
时最多 256 项),并保留最后游标供宿主读取和完成外部副作用对账。Host 在尝试启动一个仍为
|
||
`running`/`cancel_requested` 的 run 时也会先做一次有条件的过期 lease 探测:有效
|
||
lease 会拒绝第二个 worker,过期 lease 会原子进入 `reconciling`,不会启动 Engine
|
||
或重放调用。库调用方还可使用 `agent-runtime-sqlite::RuntimeService::reconcile_stale_runs(limit)` 做
|
||
一次固定排序、最多 256 项的候选扫描;每项会重新检查 lease 后再进入同一原子 gate。
|
||
对账方先在 Provider/工具外部系统
|
||
确认结果,再提交完整 Core 消息历史:
|
||
|
||
```bash
|
||
# messages.json 是完整历史,不是仅新增的一条消息;也可把 - 换成 stdin,或直接传入以 [ 开头的 JSON
|
||
AGENT_DB=agent.db agent reconcile-provider <run_id> <provider_request_id> messages.json
|
||
AGENT_DB=agent.db agent reconcile-tool <run_id> <tool_call_id> messages.json
|
||
```
|
||
|
||
消息格式沿用 Core 的 serde 字段;例如工具结果后缀形如:
|
||
|
||
```json
|
||
[
|
||
{"role":"user","content":[{"type":"text","text":"执行任务"}]},
|
||
{"role":"assistant","content":[{"type":"tool-call","id":"call-1","name":"echo","arguments":{"text":"hello"}}]},
|
||
{"role":"tool","content":[{"type":"tool-result","toolCallId":"call-1","output":{"ok":true},"isError":false}]}
|
||
]
|
||
```
|
||
|
||
实际提交时应以 `agent checkpoint <run_id>` 返回的历史为前缀,只追加已经从外部
|
||
系统核对到的结果。
|
||
|
||
提交消息必须保留 checkpoint 的逐项前缀,并追加已观察的 assistant 响应(Provider)
|
||
或匹配的 `tool` 消息中的 `tool-result` 内容块(工具);Host/SQLite 会校验 phase、调用 ID、step、
|
||
attempt、角色、顺序和重复项。命令只写入 `safe` checkpoint,不会查询外部系统、
|
||
执行 Provider 或再次执行工具;成功后使用 `resume-safe`,它先把 run 重新排队(尚未启动 worker 时重复执行也幂等);worker 领取后在启动 Engine 前用一次
|
||
RuntimeStore CAS 补齐已观察的消息/工具结果,再从 `next_step` 继续。两步之间
|
||
退出仍可重试;`provider_in_flight` 和 `tool_in_flight` 仍不会自动重放。
|
||
`AGENT_DB` 指定 SQLite 文件路径;不设置时使用当前目录的 `agent.db`。
|
||
|
||
Provider 在进入工具调度前会校验 response 的 request/model identity 和批次内
|
||
tool-call ID 唯一性。审批策略返回 `Ask` 时,worker 保留
|
||
`awaiting_approval` checkpoint 并进入 reconciliation gate,不会把它当作 deny
|
||
或自动执行。控制端先用 `approval list/get` 读取请求,再用 `approval allow` 或
|
||
`approval deny <id> <原因>` 做 pending-only 决议,最后用 `approval resume <id>`
|
||
显式重新排队并启动 worker;同一决议可安全重试,已取消或已决议记录不能被覆盖。
|
||
审批 `Deny` 会生成失败的 tool result;若同一批次还有后续调用,checkpoint 会
|
||
指向下一调用,只有整批处理完成才可标记 `safe`。
|
||
|
||
MCP stdio 子进程只继承 `PATH` 和显式环境变量,MCP 配置/请求/错误的调试与 CLI
|
||
展示会脱敏;tools/list 会限制分页并拒绝循环游标。同步 client 提供显式
|
||
`poll_notification`、有界 `McpReconnectScheduler`(可输出调度审计)和调用前
|
||
权限审计 gate;重连只重做握手,不缓存或重放 `tools/call`,也不能强杀已经阻塞
|
||
的同步 I/O。已完成握手的 stdio client 还可通过
|
||
`into_notification_subscription` 转成独占的后台通知订阅:队列有界、支持
|
||
cooperative cancel,`Drop`/`join` 会回收线程和子进程;订阅期间不能再对原 client
|
||
发 request。Streamable HTTP 也支持独占 GET/SSE 长连接(内部 worker 使用私有
|
||
current-thread runtime),自定义 transport 仍需显式覆盖订阅 hook;订阅线程不会
|
||
自动应答、重连或重放调用。transport 对远端不可信输入使用固定上限:单条 stdio JSON-RPC 消息
|
||
最多 1 MiB,Streamable HTTP 响应最多 4 MiB,SSE 单行最多 1 MiB、单事件累计
|
||
`data` 最多 4 MiB;`tools/list`、`resources/list`、`prompts/list` 各最多跟随
|
||
1024 页。超限会返回协议错误,不会静默截断;这些上限不替代 Engine 的上下文/
|
||
工具结果预算。CLI `mcp list` 使用一次 capability snapshot 同时产出工具列表和指纹,
|
||
不会为指纹再次请求 `tools/list`。OpenAI adapter 在 complete/stream 请求中以本地
|
||
request id 发送 `Idempotency-Key`,并将 HTTP 408/429/502/503/504 归类为可限次重试的
|
||
`Unavailable`,其它非 2xx 归类为 `Upstream`;厂商 response id 仍只是外部对账线索,
|
||
adapter 不会自行查询结果或重放。若网关不承诺幂等,应把调用方的重试预算设为 0。
|
||
|
||
## Crate 边界
|
||
|
||
- `agent-runtime-core`:纯数据契约、端口和状态 reducer;不依赖异步运行时或外部协议。
|
||
- `agent-runtime-contracts`:数据库无关的 durable command/view 与 `DurableStore` trait;
|
||
只依赖 Core 和 JSON,不持有 SQLite、连接或线程生命周期。
|
||
- `agent-runtime-engine`:单 Agent Loop、上下文预算、工具调度与 checkpoint 边界回调;
|
||
`ContextObservation` 记录候选上下文的 selected/skipped 及 trusted/untrusted 计数,
|
||
不复制正文。进入 Engine 的 serde 输入、ContextSource 项、压缩器输出和压缩响应
|
||
身份都会在边界重新校验;Provider response 的 `content` 只允许 Text/Image,结构化
|
||
tool call/result 不得混入该字段;校验失败发生在 Provider、checkpoint 或工具副作用之前。
|
||
- `agent-runtime`:portable durable command/view facade、`DurableRuntime<S: DurableStore>`、
|
||
`RuntimeSnapshotService<S>`、`RuntimeRunHandle` 和 `WorkerLease`;无 SQLite feature、
|
||
`rusqlite` 或 `agent-storage-sqlite` 依赖,package-only no-default 测试覆盖其 portable
|
||
合同。不启动 Engine 或 worker。
|
||
- `agent-runtime-sqlite`:SQLite-backed `RuntimeService`、records/error 转换、journal mode、
|
||
JSONL/export 和跨表 lease/checkpoint/终态/recovery 事务;依赖 `agent-runtime` 与
|
||
`agent-storage-sqlite`,Host 统一通过它装配,未保留 `agent_runtime::RuntimeService` 的
|
||
平行 re-export。
|
||
- `AgentHost::load_runtime_snapshot`:RuntimeSnapshot 的只读观察入口,同样通过
|
||
Runtime facade,不领取 lease、不改变状态;跨表原子事务由 Runtime 的 SQLite
|
||
实现负责,Host 只保留 `store()` 等旧兼容 accessor,不形成第二份连接所有权。
|
||
- `AgentHost::observe_external`:通过 Runtime facade 调用 Core
|
||
`ExternalObservationSource` 查询已有 provider/tool/external 引用;这是只读观察,
|
||
不写 checkpoint、消息、requeue 或 reconciliation,外部错误分类会原样返回。
|
||
- `DurableApprovalCheckpointRuntimeCommit`:Host 在 Engine 生成 approval binding 后,
|
||
通过 Runtime/SQLite 的单事务重新校验 live lease、awaiting checkpoint 和 runtime
|
||
revision,再幂等写入 pending approval;Engine callback 早于 binding 生成的窄窗口仍需
|
||
reconciliation/failure gate 处理。
|
||
- Host 的 Runtime-only 事件/快照 CAS 通过 `agent-runtime-sqlite::RuntimeService::commit_runtime_snapshot`;
|
||
该入口不替代跨表 run/session/lease 事务。
|
||
- `agent-storage-sqlite`:SQLite 事件/快照、增量 checkpoint,以及 worker lease/fencing;
|
||
具体 RuntimeService 装配位于 `agent-runtime-sqlite`。
|
||
- `agent-mcp`、`agent-skills`:外部协议和文件格式适配器;前者提供同步
|
||
stdio/Streamable HTTP JSON/SSE、显式通知轮询、stdio 与 HTTP/SSE 的有界独占通知
|
||
订阅、有界重连调度和权限审计;stdio 同步等待响应时的 pending 消息队列同样有
|
||
`MAX_STDIO_PENDING_MESSAGES` 硬上限,后者实现 Core `SkillSource` 并执行显式激活。
|
||
- `agent-provider-openai`:OpenAI Responses 请求/响应映射。
|
||
- `agent-provider-fake`:可脚本化的离线 Provider fixture。
|
||
- `agent-codex`:受限 Codex CLI 一次性调用(含超时、取消、Unix process-group
|
||
终止和输出上限;子进程启动后的非法输出、超限或异常退出均保留为未知副作用)、
|
||
可注入 App Server channel、带版本字段的本地 JSONL fixture,以及不绑定发行版的
|
||
最小 JSON-RPC/JSONL channel;另提供窄 V2 `CodexAppServerClient` 和真实 stdio
|
||
`CodexAppServerProcess`,覆盖 initialize/initialized、thread/start、turn/start 接受、
|
||
通知轮询、可选通用 server-request handler 和精确 `threadId`/`turnId` interrupt。真实进程配置只接受显式
|
||
`program + args`,有界 supervisor 负责 stdin/stdout 排空、deadline、取消、进程组
|
||
终止、wait/reap 和 reader/writer join;不会自动重连或重放请求。不嵌入 `codex-core`,
|
||
也不替宿主持有 Host 的会话和持久化真相;V2 不是具体 Codex 发行版兼容承诺,完整
|
||
generated schema、服务端审批/工具请求和版本特定 wire 仍由上层适配器负责;
|
||
`codex_0_152_1` 只接受调用方已核对的 `codex-cli 0.152.1`,并提供当前 typed
|
||
子集与 provenance/hash 清单,不是任意 v2 发行版兼容层。
|
||
另提供独立 `JsonRpcAppServerRouter`,以有界 pending map 和后台 reader 支持
|
||
乱序 response/并发 `turn/interrupt`;另有 `CodexAppServerProcessRouter` 将其接入
|
||
ProcessControl 管理的真实 stdio child,并在 timeout/protocol/cancel/Drop 时收束。
|
||
ProcessBackend 可注入 `CodexSessionMetadataSink`,在 request/cancel 收束后报告中立
|
||
lifecycle;Host 的实现把状态、external ID、退出码和取消结果合并进 `external_sessions`,
|
||
但不覆盖进程级 EOF/terminate/Drop 或自动外部对账。
|
||
- `agent-runtime-orchestration`:通用 DAG、Delegation/Join/Proposal、状态和下游修复,
|
||
以及 Coordinator 的配额、消息去重、节点隔离和显式修复;持久协调器还提供
|
||
revision-CAS 保护的 `cancel_run`,原子标记 `Cancelled` 并释放活动配额;支持把任务图与协调器
|
||
控制面作为同一版本化快照保存的 `PersistentCoordinator`,并提供带 revision CAS
|
||
的内存/原子 JSON 文件 `OrchestrationSnapshotStore`。其中
|
||
`JsonFileOrchestrationSnapshotStore` 通过同目录 sidecar advisory lock 把 revision
|
||
CAS 与 rename 串成遵守该适配器的本机跨进程写临界区;旧
|
||
`JsonFileCoordinatorStore` 仍只有进程内锁。它们都不直接接 SQLite、不创建 Runtime
|
||
run、不调度线程,也不提供自动多 Agent scheduler。
|
||
- `agent-host`:组合依赖并提供稳定库 API;`AgentBuilder`/`AgentService` 当前是
|
||
`AgentHost` 的类型别名,`AgentHost::new`/`builder` 是轻量装配入口,不创建第二
|
||
套 Runtime 或生命周期。Host 内部不再重复持有 Store;`store()` 已标记为 deprecated,
|
||
仅作为旧导出/诊断调用的兼容 accessor 委托给 Runtime,Runtime 仍是当前 SQLite-backed
|
||
具体实现。新代码可使用 `list_events`、`list_runtime_events`、`get_session`、
|
||
`export_jsonl` 和 `export_runtime_jsonl` 等窄 facade。
|
||
- `agent-app`:无状态的通用程序配置与装配输入层,承接 `AgentTomlConfig`、环境/TOML
|
||
优先级、OpenAI endpoint、MCP 认证引用、effective model 和 queued metadata;不依赖
|
||
Host、Runtime、线程或数据库,不拥有运行状态。
|
||
- `agent-cli`:面向人和脚本的最小命令行入口。
|
||
|
||
可选的真实 Codex wire 探测使用 `scripts/probe-codex-app-server.sh`。默认只在隔离
|
||
`CODEX_HOME` 中执行 `initialize`/`initialized`/`thread/start`;加 `--schema` 可生成
|
||
本机 v2 schema 的大小/hash 摘要。它不需要 API key、不发送 `turn/start`,也不进入默认 CI。
|
||
|
||
库调用方也可直接使用 `AgentHost::read_checkpoint` 观察游标,再调用
|
||
`reconcile_provider_result` / `reconcile_tool_result` 和 `requeue_safe_run`;这些
|
||
入口只接受已核对的完整消息历史。若宿主已有具体外部查询实现,可注入
|
||
`ExternalSessionResolver` 调用 `reconcile_external_sessions(limit, resolver)` 做有界批处理:
|
||
Completed 只推进 safe checkpoint,Pending/NotFound 保守保留 unknown,仍不会自动 requeue、
|
||
重放或启动 Engine。自动幂等键查询、长连接结果订阅和未知调用的自动重放需要绑定具体外部
|
||
系统,暂不由通用内核假设。`agent-codex` 的 Host 接入
|
||
方式是实现/注入 Core `ExternalBackend`,调用 `AgentHost::with_external_backend`,
|
||
再通过审批策略显式放行;未知结果会写 `external_sessions` 并停在
|
||
`tool_in_flight`,不会被自动当作成功。多工具批次在首个 `Ask` 后恢复时只会把当前
|
||
pending call 及其前缀物化到 Core,后续调用仍留在 checkpoint;MCP 工具桥对发送后
|
||
timeout/断线/协议/编码/HTTP/远端错误统一标记为未知副作用,避免显式 failed 重试
|
||
策略重放未知调用。`doctor` 不联网、不启动外部进程,但会打开并按需初始化/迁移本地
|
||
SQLite/WAL,再分项校验 SQLite、Provider key/endpoint、Skill、MCP 和 Codex 配置;Codex
|
||
program 只输出已配置标记,不回显路径。
|
||
|
||
需要观察 durable 审计时,可注入 `with_durable_event_listener` 或
|
||
`with_durable_event_callback`。回调在每条 `events` 行的 SQLite append 事务提交后
|
||
同步执行,并收到 `(run_id, revision, event)`;它只证明该行已落盘,不代表 runtime
|
||
trace、approval 或终态事务已完成,也不能回滚或直接修改 Host 状态。Engine 的普通
|
||
`EventListener`/`StreamEventListener` 仍是提交前的观察接口。
|
||
|
||
Engine 的当前边界回归还覆盖五类容易被 serde 绕过的输入:顶层 AgentInput 的消息、工具
|
||
定义和请求字段;ContextSource 返回的消息项;压缩器返回的消息集合;压缩响应的
|
||
`request_id`/`model` 身份;以及压缩阶段的取消传播。它们统一在进入 Provider、checkpoint
|
||
或工具副作用前失败,避免不合法数据被截断、重写或继续执行。
|
||
|
||
Codex 一次性 CLI 的配置可放在 `agent.toml` 的 `[codex.cli]` 下,并运行
|
||
`agent codex validate` 做无副作用的参数白名单检查;实际调用由
|
||
`CodexCliBackend` 在有界 supervisor 中管理 child、超时和取消。JSONL App Server
|
||
channel 只规定本仓库的中立 `request/event/result/interrupt` frame,并以
|
||
`protocolVersion` 做本地 fixture 握手;需要当前窄 V2 生命周期时可使用
|
||
`CodexAppServerClient` 的 `initialize`、`thread_start`、`turn_start`、
|
||
`poll_notification` 和 `turn_interrupt`。若需要直接启动本地 app-server,可使用
|
||
`CodexAppServerProcessConfig` 配置显式 `program + args`,再通过
|
||
`CodexAppServerProcess::spawn` 获取同一组方法;它会为 stdin/stdout 使用有界的
|
||
reader/writer supervisor,并在 timeout、cancel、EOF 或 Drop 时完成 process-group
|
||
终止、wait/reap 和线程 join。每个 argv 必须匹配调用方提供的
|
||
`allowed_arg_prefixes`,不会把一整段字符串再交给 shell 解析。客户端提供
|
||
`CodexServerRequestHandler` 这一中立回调,用于回应审批/动态工具等 server request;Host
|
||
还提供只覆盖 Codex 0.152.1 dynamic-tool wire 的 `codex_01521_server_request_handler`,
|
||
返回该版本的 `contentItems`/`success` 形状。具体参数 schema、审批策略、工具执行、异步
|
||
通知订阅或自动重连仍需由上层适配器负责,避免把厂商协议写进 Kernel。进一步的依赖/CI 证据见
|
||
[`docs/【审计】独立Agent依赖边界与CI验收-2026-09-02.md`](docs/【审计】独立Agent依赖边界与CI验收-2026-09-02.md)。
|