Skip to content

已知限制

这些是当前预览版的刻意边界,而非 bug。每条目描述其含义、何时会遇到,以及可能的变通方案。

多主机协调需要 Postgres

含义: 单主机多工作者已发布——代理运行一个常驻 WorkerLoop 池(NOETA_AGENT_NUM_WORKERS,默认 1),因此多个任务在一台主机上同时推进。多主机共享一个数据库仅在 Postgres 上受支持:emit 追加在事务中针对活跃 lease 进行 fencing,lease 过期在数据库时钟上运行(因此每主机时钟偏差不会导致脑裂),worker_id 列记录持有者。SQLite内存后端保持单主机——它们没有跨主机 fencing,因此将两个主机进程指向同一个 SQLite 文件是不安全的。

何时遇到: 你希望多台机器上的工作者进程排空一个共享存储。

变通方案: 对多主机部署使用 Postgres 后端。在 SQLite 上,保持单主机(该主机上的多工作者池没问题),或为不同的工作负载配置各自的 SQLite 文件——就绪队列中没有跨存储路由,因此一个存储中的任务对排空另一个存储的工作者不可见。

为什么是这样: 跨主机 fencing 需要嵌入式存储不提供的共享事务时钟和 lease 仲裁;Postgres 同时提供两者。该设计记录在多主机 lease fencing ADR 中。

步骤中途崩溃恢复不会撤销副作用

含义: 工作者在步骤中途崩溃(kill -KILL、断电)会在下一次 lease 时自动恢复:被中断的尝试被一个持久化的 StepAttemptAbandoned 标记密封,当该尝试记录的所有内容在无需人工审批门的情况下运行时,步骤会被重新驱动。当尝试存在不可证明的副作用时——或在一轮中连续 3 次密封后——任务改为停放:作为已停止的对话挂起,带有一条 origin="system" 通知,列出每个被中断的调用及其是否完成。在人工批准的工具执行期间崩溃总是停放,在同一审批上重新挂起。恢复从不静默终止任务,也从不静默重新运行有副作用的调用——但它也不会撤销崩溃尝试已经做的任何事情。

何时遇到: 硬杀或断电发生在已经运行了有副作用工具的尝试期间(被中断的 shell_run、已完成的 edit、执行中途的已批准调用)。正常关闭(SIGTERM)不会触发此情况,读取或规划期间的崩溃无需人工参与即可恢复。

变通方案: 打开停放的对话——通知列出了被中断的内容。验证这些操作是完全、部分还是完全没有应用,然后直接输入继续(该轮从干净的尝试前基线恢复)或重新批准待处理的调用;close / cancel 照常工作。

为什么是这样: 分类复用了控制实时执行的同一权限面,因此恢复永远不会比代理自身的审批规则更宽松。但是,运行了一半的 shell_run 是否真的改变了世界,无法从日志中证明——该设计防止静默重复,并将半应用效果的判断留给人工。

有界关闭,但无进程内线程中断

含义:stop()(SIGTERM / SIGINT)时,WorkerLoop 等待进行中的步骤完成,最多等待 shutdown_grace_s。如果未完成,循环放弃该步骤并返回。但 Python 无法杀死被放弃的步骤线程——它可能仍在运行并写入 EventLog。

何时遇到: 步骤挂起(例如对无响应的外部 API 的工具调用)且宽限期到期。

**变通方案:**退出进程。 放弃后,主机必须调用 sys.exit() 或等效操作。被放弃的线程随进程死亡;其 lease 过期,requeue_stale() 在下一次启动时回收任务。设置 shutdown_grace_s=None(或 <= 0)可无限等待——然后卡住的步骤需要外部 kill -KILL <pid>

心跳保活窗口有上限

含义: 心跳保持慢步骤的 lease 活跃,但不是永远的。Dispatcher 限制心跳扩展(heartbeat_max),因此 heartbeat_interval × heartbeat_max 是一个步骤可以持有 lease 的最长时间。超过上限后,lease 被强制释放,步骤的下一次 EventLog 写入失败并返回 InvalidLease

何时遇到: 单个步骤(一次 LLM 轮次加上其所有工具调用)花费的时间超过上限窗口。默认值很宽松(以小时计,而非分钟),因此这种情况很少见。

变通方案: 此上限命中是一个运维故障信号,而非恢复路径。循环记录日志并继续下一个任务,但命中上限的任务可能需要运维人员检查。检查任务是否仍然可行或是否应该关闭。

可靠性事件是进程本地的,非持久化的

含义: 工作者发出 ReliabilityEvent——stale_requeuedsuspended_without_wakestep_failed_retryableheartbeat_invalid_leaseshutdown_abandonedtimers_firedattempt_abandonedattempt_parked——到一个可注入的接收器(默认:结构化日志)。这些不是 EventLog 事件,不持久化,也不在进程重启后存活。

何时遇到: 你试图在工作者可靠性信号之上构建监控或告警。

变通方案: 挂载一个自定义 reliability_sink,将事件转发到你的监控系统。每个事件以工作者可从 dispatcher seam 证明的内容命名(例如 heartbeat_invalid_lease 是一个症状——原因可能是上限/过期/重新入队)。

人机交互等待无带外通知

含义: 人机交互完全在带内连接:Engine 在 HumanResponseReceived 唤醒事件上挂起,answer / approve / deny 客户端动词传递响应,捆绑的 Web UI 渲染结构化问题表单(选项加自由文本)和审批提示。不存在的是带外通道——当任务开始等待人类时,没有 webhook、电子邮件或跨会话收件箱触发。

何时遇到: 代理在没有人打开聊天时提出问题或请求批准。任务持久化等待(这正是目的),但没有通知任何人它在等待。

变通方案: 为交互式会话保持 Web UI 打开,或在无头部署中轮询 GET /tasks 查找挂起的任务(并通过 client.answer(task_id, question_id=..., answers=...) 以编程方式回答)。订阅 EventLog 的自定义 Observer 可以将 UserQuestionRequested / ToolCallApprovalRequested 事件转发到你自己的通知通道。

沙箱副作用在工作者代际之间不受 fencing

含义: 当会话在沙箱容器中运行时(绑定到 AIO Sandbox 后端的 ExecEnv seam),其文件和 shell 副作用通过 HTTP 到达容器——在 fencing EventLog 写入的共享 Postgres 事务之外。一个被 fencing 出日志的工作者(GC 暂停、SIGSTOP 后恢复)仍然可以向容器 POST。因此,沙箱副作用是至少一次且不受 fencing 的,与主机上运行了一半的 shell_run 属于同一类别:回收的工作者重新连接到同一容器并重新驱动步骤,但缓慢的僵尸进程在此期间可能污染容器。每会话容器模型(2026-07-08)缩小了爆炸半径——僵尸现在只污染其自己会话的容器,而非主机共享的容器——但写入在代际之间仍然不受 fencing。

何时遇到: 持有沙箱会话的工作者停滞足够长的时间,导致其 lease 过期,另一个工作者回收任务,而停滞的工作者稍后唤醒并发出另一个容器调用。

变通方案: v1 中无自动方案——它受与覆盖崩溃步骤副作用相同的步骤尝试重新驱动和人工审查的约束(参见"步骤中途崩溃恢复不会撤销副作用")。未来的代际 token fence(seam 已预留该槽位)将关闭这个窗口。

为什么是这样: lease-fencing 设计假设没有负载性写入落在共享数据库之外;容器写入正是这样的写入。正确地对其进行 fencing 需要编排层 token 和容器前面的验证代理——真正的工作推迟到每容器的未来。该权衡记录在执行环境 seam ADR 中,该 ADR 链接回多主机 lease fencing ADR。

每会话沙箱:弱挂载隔离、无跨机器重连、空闲成本

含义: 每会话沙箱(2026-07-08)为每个根任务树配置一个全新容器LocalDockerSandboxProvider),因此两个并发对话获得独立的容器,持久化的 exec_env_ref 携带不同的 sandbox_id。本地 Docker 后端仍有三个成本:

  • 弱文件系统隔离。 容器通过 -v bind 挂载写入主机工作区,而非完整的 FS 监狱;仅挂载工作区 + 技能目录(从不挂载主机根目录),因此工具无法到达这些之外,但对挂载路径的写入直接落在主机文件系统上。
  • 无跨机器重连。 本地 Docker 容器绑定到运行它的机器。如果会话在不同的主机上被回收,attach 会失败——该主机无法到达原始机器上的容器。同主机恢复/回收正常工作。
  • 空闲容器成本 + 冷启动。 容器没有暂停/快照:它在会话活跃期间保持存活(并计费),包括长时间挂起;每个新会话支付秒级 AIO 冷启动。

何时遇到: 完全不能接触主机文件系统的不受信任代码;多主机部署中工作者崩溃且会话在另一台机器上被回收;留下许多沙箱会话挂起。

变通方案: 对于硬隔离,在 VM/容器中运行整个主机。对于跨机器回收,将会话的回收保持在其原始主机上(同主机路径有效),或使用分布式/NAS 后端的 provider(见下文)。对于空闲成本,关闭你已完成的会话以释放其容器。

为什么是这样: 该 seam 是为分布式 provider 系列(TAE / K8s)构建的,该系列从存储层移除跨机器重连——NAS 挂载使文件状态可从任何主机到达,因此回收主机只需针对同一 NAS 重新拉取容器。三个零返工钩子已经到位:SandboxHandle.auth(一个策略,而非静态密钥——短期 Bearer JWT 可直接放入)、网关路径前缀 base_url,以及 MountSpec.kind=nas。真正的 FS 隔离(copy-in/sync-out)和热池/暂停是未来的 provider 工作。参见执行环境 seam ADR(v2)。

沙箱 shell_run 超时是客户端侧的,非远程硬杀

含义: 在主机上,shell_runtimeout 映射到杀死进程的真实子进程超时。在沙箱下,AIO 容器没有远程硬杀,因此超时通过该单次调用的 HTTP 读取超时在客户端侧强制执行。你传递的 timeout 会被遵守——超过它运行的命令会以请求的预算报告给模型为超时运行(而非固定的适配器默认值)——但命令本身在调用返回后继续在容器中运行,直到 AIO lease 上限回收它。其副作用可能仍在工具报告超时后落地。

何时遇到: 沙箱 shell_run 的命令超过其 timeout(或默认值)——例如挂起的构建或测试运行。

变通方案: v1 中无自动方案。将超时的沙箱 shell_run 视为"可能仍在运行";后续命令可以观察或清理其部分效果。为真正长时间运行的命令提供明确更大的 timeout,以便客户端不会过早切断调用。

为什么是这样: AIO 的 /v1/shell/exec 是一个没有取消动词的同步调用,因此客户端拥有的唯一边界是其自己的读取超时。将每命令预算线程化到该超时中,使面向模型的 timeout 在无硬杀后端的限制内表现得像本地后端。容器持久化的、可取消的作业句柄是单独的未来工作(与解锁沙箱下后台 shell 的是同一项工作)。参见执行环境 seam ADR。

前端是小型 Vite MPA,非框架应用

含义: 发布的 Web 应用(/chat/trace)是一个使用原生 ES 模块的小型 Vite 多页应用。预览版没有计划进行 React / Vue / Svelte 迁移。

何时遇到: 你希望在代理之上构建复杂 UI。

变通方案: HTTP 接口是集成面。基于它构建你自己的前端,或直接通过 SDK 嵌入代理。

另见

Released under the MIT License.