Files
Genarrative/rust
kdletters 36ae9425e9 完善 AGC 独立 Agent 试验验证
支持使用 Fake Provider 验证跨进程 Agent 回合

修正独立 Agent 与排队模型的匹配和子进程超时回收

补充 AGC 到独立 App Server 的真实子进程 smoke
2026-09-10 13:07:09 +08:00
..
2026-09-10 00:05:02 +08:00
2026-09-09 19:20:54 +08:00
2026-09-10 00:05:02 +08:00
2026-09-10 00:05:02 +08:00

agent-runtime

一个与具体业务项目解耦的 Rust Agent 内核和单 Agent 主程序。

原始 P0–P6 建设计划与逐项状态见 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.rsmcp.rscontext.rsexternal.rscheckpoint.rscodex.rsexecution.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 1.96,并声明 rustfmtclippy 组件;独立 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。 该脚本还单独检查 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_commitStorage 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_orderCodex 当前 81。
  • 原始 P0–P6 仍部分完成;根 Vitest 的 3189/3189 为历史记录,本轮工作区未安装 vitest,尝试以退出码 127 结束,未计入当前通过项。

2026-09-05 当前增量

  • Core 扩展值对象和外部端口(ToolBindingSkillDefinitionSkillActivationAgentDescriptorBackendRequestBackendResultToolContext)在公开 serde/ 兼容入口复验;backend_result_as_tool 拒绝身份错配和未知副作用,Core 当前为 30 个单元、20 个 core_contracts 集成、1 个 tool_context_contracts 集成和 1 个 doctest。
  • Engine 在调用 ContextSource 前验证 ContextRequestHost/Engine 在工具、Skill、MCP 和外部 backend dispatch 前验证上下文与调用合同。Runtime/Storage/Host 提供固定排序、 状态/run 过滤和硬上限的只读 list_external_sessions,不会自动对账或重放。
  • agent-runtime 现在只包含 portable DurableRuntime<S>RuntimeSnapshotServiceWorkerLease/RunHandle 与中立 DurableStore 合同;SQLite-specific RuntimeService、 records、错误和跨表事务已迁移到 agent-runtime-sqlite。后者依赖 agent-runtime + agent-storage-sqlite,不反向污染 portable runtime。
  • agent-runtime-sqliteRuntimeService 通过 SqliteDurableStore 提供 prepare_run*、run/session 查询、claim/heartbeat/release、checkpoint fencing、approval pending-only CAS、external-session 候选、request-cancel/stale 扫描和 runtime-aware finish/recovery commandHost 统一从该 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 游标时,可使用 DurableToolCallCheckpointRuntimeCommitDurableStore/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/traitagent-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_timeoutapp_server_process_lifecycle_sink_distinguishes_reader_eof);当前定向 Codex 101、Host 80、MCP 52Clippy -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 且不触发 Providerlegacy 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 计数和 完整 P0P6 状态以 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> 并注入 RuntimeSnapshotServiceagent-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 中出现结构化 工具块也会直接拒绝,不会静默过滤。

清理构建目录后可用临时数据库直接验证程序入口:

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 能力评测(固定输入、最终输出和流式轨迹断言)使用:

./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 4Core 为 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 可注入已经装配好的 RuntimeServiceHost 与调用方共享同一 durable 控制面,不会重新打开或平行持有 SQLite。Host 的 CodexHostServerRequestHandler 处理 item/tool/call,在审批或执行前校验已注册工具的 JSON Schema,再使用 Host 的审批策略和 ToolRouter;它是同步的低层桥接,不创建 durable approval/checkpoint/auditAsk 以 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 transporthook 不获取 channel mutex,也不自动 修改 Host/Runtime 状态。未配置独立 transport 时仍使用 channel 自带的串行 interrupt 因此不能把该 opt-in 接线当成真实 Codex control wire 或强制取消实现。

P4 还提供随 crate 分发的本地 fixtureagent-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

离线依赖漏洞审计

scripts/run-cargo-audit.sh 是 CI 和本地共用的离线 wrapper。它从脚本自身位置 解析 workspace 根目录,因而不会因为调用者的当前目录而误审其它 Cargo.lock workspace manifest/锁文件、advisory DB 或工具缺失时会直接失败。runner 必须预先 提供本地 RustSec advisory-db checkoutRUSTSEC_ADVISORY_DB),并预装固定版本的 cargo-audit;需要指定可执行文件时设置 CARGO_AUDIT_BIN

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 提供固定输入并留下运行记录。

快速开始

# 从 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_idrun_idruntime_id。因此它是稳定的完成后批次,不是实时流日志。 后台 run --background --jsonl 只输出一条 type=queued(含 worker PID 和运行 身份),worker 的最终结果需另用 inspectexport 查询。

测试集与真实 Provider smoke

仓库提供一组可重复的 Agent 回归用例,先用 Fake Provider 验证工具闭环、流式路径、 SQLite 持久化和 JSONL 导出,再按需追加一次真实 Provider 请求:

./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。真实 smoke 只在显式 --real(或 AGENT_TEST_REAL_PROVIDER=1)时读取 key;默认不访问网络。 测试脚本默认在 ~/data/tmp 下创建本轮的 agent-test-set.* 目录,数据库、Cargo target 和中间文件都放在其中;显式设置 TMPDIRAGENT_TEST_TMPDIR 时尊重调用方的 临时父目录。脚本只删除自己创建的目录,不会把本轮构建产物留在 workspace 的 target/ 中。

本地 workspace 回归基线(2026-09-06cargo 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 28Core 为 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 校验前复验 ToolCallToolDefinition 及二者的工具名匹配;因此 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_ENDPOINTOPENAI_BASE_URL。 完整 endpoint 优先于 base URL。CLI 还接受 OPENAI_MODEL、TOML 的 openai_endpoint/openai_base_url;环境变量优先于 agent.toml。该适配器发送 Responses API 的 modelinputtools 等字段,网关需要兼容同一请求/响应 协议,详见 OpenAI Responses API reference

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_PROMPTAGENT_DEVELOPER_PROMPTAGENT_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_SERVERAGENT_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 MiBSSE 单行最多 1 MiB、单事件累计 data 最多 4 MiBtools/listresources/listprompts/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_resourcesURI 数组)或 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/listresources/prompts 的发现错误也会沿原路径返回。

Skill 文件采用一个有界的 SKILL.md frontmatter 子集:name 必填,支持 descriptionversionallowed-tools 和字符串扩展字段;解析器不是完整 YAML 实现。目录发现只读元数据,正文在显式激活时读取并重新校验路径、UTF-8 和大小; 空正文会被激活拒绝。每个逻辑字段只能出现一次;历史别名 allowed_toolsallowed-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 身份传给上下文源 和工具执行器,但不会自动把这些身份拼进模型提示词。

可用命令:runinspectcheckpointexportdoctorcancelresumeresume-safereconcilereconcile --stale [limit]reconcile-providerreconcile-toolapproval list/get/allow/deny/resumecodex 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 消息历史:

# 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 字段;例如工具结果后缀形如:

[
  {"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_flighttool_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 allowapproval 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 cancelDrop/join 会回收线程和子进程;订阅期间不能再对原 client 发 request。Streamable HTTP 也支持独占 GET/SSE 长连接(内部 worker 使用私有 current-thread runtime),自定义 transport 仍需显式覆盖订阅 hook;订阅线程不会 自动应答、重连或重放调用。transport 对远端不可信输入使用固定上限:单条 stdio JSON-RPC 消息 最多 1 MiBStreamable HTTP 响应最多 4 MiBSSE 单行最多 1 MiB、单事件累计 data 最多 4 MiBtools/listresources/listprompts/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-runtimeportable durable command/view facade、DurableRuntime<S: DurableStore>RuntimeSnapshotService<S>RuntimeRunHandleWorkerLease;无 SQLite feature、 rusqliteagent-storage-sqlite 依赖,package-only no-default 测试覆盖其 portable 合同。不启动 Engine 或 worker。
  • agent-runtime-sqliteSQLite-backed RuntimeService、records/error 转换、journal mode、 JSONL/export 和跨表 lease/checkpoint/终态/recovery 事务;依赖 agent-runtimeagent-storage-sqliteHost 统一通过它装配,未保留 agent_runtime::RuntimeService 的 平行 re-export。
  • AgentHost::load_runtime_snapshotRuntimeSnapshot 的只读观察入口,同样通过 Runtime facade,不领取 lease、不改变状态;跨表原子事务由 Runtime 的 SQLite 实现负责,Host 只保留 store() 等旧兼容 accessor,不形成第二份连接所有权。
  • AgentHost::observe_external:通过 Runtime facade 调用 Core ExternalObservationSource 查询已有 provider/tool/external 引用;这是只读观察, 不写 checkpoint、消息、requeue 或 reconciliation,外部错误分类会原样返回。
  • DurableApprovalCheckpointRuntimeCommitHost 在 Engine 生成 approval binding 后, 通过 Runtime/SQLite 的单事务重新校验 live lease、awaiting checkpoint 和 runtime revision,再幂等写入 pending approvalEngine callback 早于 binding 生成的窄窗口仍需 reconciliation/failure gate 处理。
  • Host 的 Runtime-only 事件/快照 CAS 通过 agent-runtime-sqlite::RuntimeService::commit_runtime_snapshot 该入口不替代跨表 run/session/lease 事务。
  • agent-storage-sqliteSQLite 事件/快照、增量 checkpoint,以及 worker lease/fencing 具体 RuntimeService 装配位于 agent-runtime-sqlite
  • agent-mcpagent-skills:外部协议和文件格式适配器;前者提供同步 stdio/Streamable HTTP JSON/SSE、显式通知轮询、stdio 与 HTTP/SSE 的有界独占通知 订阅、有界重连调度和权限审计;stdio 同步等待响应时的 pending 消息队列同样有 MAX_STDIO_PENDING_MESSAGES 硬上限,后者实现 Core SkillSource 并执行显式激活。
  • agent-provider-openaiOpenAI 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 收束后报告中立 lifecycleHost 的实现把状态、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:组合依赖并提供稳定库 APIAgentBuilder/AgentService 当前是 AgentHost 的类型别名,AgentHost::new/builder 是轻量装配入口,不创建第二 套 Runtime 或生命周期。Host 内部不再重复持有 Store;store() 已标记为 deprecated, 仅作为旧导出/诊断调用的兼容 accessor 委托给 RuntimeRuntime 仍是当前 SQLite-backed 具体实现。新代码可使用 list_eventslist_runtime_eventsget_sessionexport_jsonlexport_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_resultrequeue_safe_run;这些 入口只接受已核对的完整消息历史。若宿主已有具体外部查询实现,可注入 ExternalSessionResolver 调用 reconcile_external_sessions(limit, resolver) 做有界批处理: Completed 只推进 safe checkpointPending/NotFound 保守保留 unknown,仍不会自动 requeue、 重放或启动 Engine。自动幂等键查询、长连接结果订阅和未知调用的自动重放需要绑定具体外部 系统,暂不由通用内核假设。agent-codex 的 Host 接入 方式是实现/注入 Core ExternalBackend,调用 AgentHost::with_external_backend, 再通过审批策略显式放行;未知结果会写 external_sessions 并停在 tool_in_flight,不会被自动当作成功。多工具批次在首个 Ask 后恢复时只会把当前 pending call 及其前缀物化到 Core,后续调用仍留在 checkpointMCP 工具桥对发送后 timeout/断线/协议/编码/HTTP/远端错误统一标记为未知副作用,避免显式 failed 重试 策略重放未知调用。doctor 不联网、不启动外部进程,但会打开并按需初始化/迁移本地 SQLite/WAL,再分项校验 SQLite、Provider key/endpoint、Skill、MCP 和 Codex 配置;Codex program 只输出已配置标记,不回显路径。

需要观察 durable 审计时,可注入 with_durable_event_listenerwith_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 生命周期时可使用 CodexAppServerClientinitializethread_startturn_startpoll_notificationturn_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 requestHost 还提供只覆盖 Codex 0.152.1 dynamic-tool wire 的 codex_01521_server_request_handler 返回该版本的 contentItems/success 形状。具体参数 schema、审批策略、工具执行、异步 通知订阅或自动重连仍需由上层适配器负责,避免把厂商协议写进 Kernel。进一步的依赖/CI 证据见 docs/【审计】独立Agent依赖边界与CI验收-2026-09-02.md