Skip to content

Repository files navigation

Agent Builder

Agent Builder 是一个从零构建的 Claude Code 风格智能体运行时。0.2.0 是首个受支持的 single-operator、本地优先版本:认证 Web UI、类型化命令、规范事件流、 可恢复会话与同会话多轮、每 Run 独立强沙箱 Worker、受信模型代理和真实 Ollama 模型调用和显式子智能体委派已经贯通。

支持合同限于 GNU/Linux x86_64、受信且防火墙保护的网络和单一 operator。当前已有通用 Agent Capsule create/upgrade/delete、Capsule 内安全文本读取/搜索、经审批的原子 Edit/Write、固定 allowlist 命令执行及 durable background Task,以及可选的 Agent 专属持久研究/文档环境,并支持从受信目录按 Turn 切换模型;默认目录目前只启用 qwen3.5:2b。已有 versioned sandboxed Skill 和独立 Capsule 子智能体,但没有任意 Shell、默认启用的 MCP/LSP、RAG 或 TLS,不应直接暴露到互联网。冻结范围、平台矩阵、发布/备份/回滚和已知限制见 release contract

当前链路

Browser :20815
    │ HTTP + SSE
    ▼
Authenticated Web Gateway / trusted Control Plane
    ├── AgentRegistry → lazy per-Agent RuntimeManager
    ├── CommandBus → bounded per-Agent QueryEngineRegistry
    │                   └── one logical QueryEngine per Conversation
    ├── QueryEngine → RunService → ConversationStore / Agent state.sqlite (WAL)
    │                              └── canonical sequencer → durable journal
    ├── prompt-source snapshots + ContextCompiler + EffectiveToolSet → ContextPlan
    ├── trusted Ollama Broker ── TCP ──> iollama:11434 / qwen3.5:2b
    ├── Capability Broker → file/document executors + singleton command runner
    │                       └── Agent-scoped persistent research dependency env
    ├── BackgroundTaskManager → TaskStore → isolated Task root + singleton runner
    ├── SubagentCoordinator → parent Task/mailbox → child Agent Conversation/Run
    └── bounded stdio IPC
             ▼
        one Worker per Run
        Landlock + seccomp + rlimits
             │
             ▼
        HarnessKernel
        context-plan reference → model → local/brokered Tool → model → terminal

Web 页面采用 conversation-first 工作台:左侧导航集中 Agent 与会话,中央只保留多轮 对话和固定输入区,默认关闭的右侧“运行详情”承载四泳道时序、事件检查和 ContextPlan 元数据。消息流在用户跟随最新内容时自动吸底,主动回看历史后暂停并提供“回到最新消息”; composer 的上下文主指标来自服务端 next-turn projection,展示当前 committed 固定上下文的 “占用约 X / 模型窗口”和剩余百分比,并在次级信息回答下一条消息安全可写量与自动整理前 余量;不可用时明确说明原因而不猜数字。上一轮 Provider 实际用量只作弱化的计算量参考。 窄屏下导航和 运行详情均变为互斥浮层;键盘可用 Enter 发送、Shift+Enter 换行,高级上下文压缩与 Slash Command 帮助收进输入区的更多选项。Agent 管理区域可以创建、切换、重命名和删除普通 Agent;重命名只更新显示元数据,不增加 generation、不排空运行时或重建虚拟环境。“重建 运行环境”位于每个普通 Agent 的高级操作中,只用于基础环境、安全策略或 Agent 定义发生 实质变化时切换到下一 generation。固定 ID 00000000-0000-4000-8000-000000000001 是正式默认的系统 Agent,不能重命名、升级或删除。 切换 Agent 会同时切换 Agent-scoped Command、会话、Run 和回放视图。页面还可以新建、 重命名、分支、分页恢复和删除会话,并在同一会话连续对话。Run 在切换会话后继续后台 接收 SSE;每个会话独立保留运行标记、草稿、模型和整理选项,切回后恢复实时视图。中央按 durable Turn 展示用户消息、成功回答和带安全错误码/阶段/耗时的非成功终态;可重试失败只 把原消息填回输入框,必须由用户再次发送才能创建新 Turn。点击 Turn 会打开所选 Run 的详情,并把 canonical events 按 User/Harness/LLM/Tool 四条泳道及 seq 顺序投影。Turn 与 Run/节点可双向定位,节点支持 步进、播放、类型过滤和常驻详情检查。详情明确区分界面推导事实、逻辑消息、canonical payload、完整 EventEnvelope 和按需读取的上下文元数据;模型原始请求、响应帧和隐藏 prompt 从未因此暴露。用户消息按纯文本渲染;完成的 assistant 消息使用受限 DOM Markdown, 原始 HTML 保持文本,链接只允许无凭据的 http/https 且不加载远程图片或其它资源。 会话右侧操作使用浏览器 top-layer Popover,并按当前 visual viewport 在触发器上方或下方 展开后夹紧到视口;重命名、从当前结尾分支和删除不会再被会话列表的滚动边界裁切。 终态对账时 durable assistant 是唯一正文事实;同一 Run 已有 canonical assistant 后,浏览器 不会再次把内存中的 live assistant buffer 投影成另一张 Turn 卡。

输入以 / 开头时不会发送给模型。认证后的 Command registry 目前提供 /status/context [run_id]/model [model_id]/compact/permissions/cancel [run_id]/clear(另有 /ctx/perms/stop alias)。Web 的 Slash Commands 面板展示 schema/help,命令结果明确标记 model_invoked=falseturn_created=false/model/compact 只设置下一次普通 Turn 的界面参数,不创建 第二份服务端状态。运行中 capability ask 会在输入区显示完整 bounded preview/diff 及 批准/拒绝按钮,决策仍走 authenticated same-origin CSRF API 和原 permission audit。

详细设计见 architectureevent protocolAgent Capsule

环境要求

当前运行时代码接受以下 host;已有完整本机资格证据的平台范围更窄:

  • GNU/Linux x86_64 已完成当前基础资格;
  • GNU/Linux aarch64 已有 bootstrap/sandbox 实现,但在原生主机完成等价 cold-checkout lifecycle RR 前仍是待资格平台,不能视为受支持部署;
  • 可用的 /proc、Landlock ABI 6+ 和 seccomp;
  • bashcurlflockps 及基本 GNU 用户空间工具;
  • 可以解析并连接 iollama:11434
  • Ollama 已安装具有 completion 与 Tool 能力的 qwen3.5:2b

其它平台没有经过等价的 cold-checkout 验证,不能视为受支持。Worker 沙箱资格或 模型资格失败时启动会 fail closed,不会退回假模型或未隔离运行。

模型上下文与预算

受信 ModelCatalog 由 operator 在进程构造边界提供,浏览器只能读取有界公开画像并选择 其中的稳定 model_id,不能提交 endpoint、provider model name 或 generation options。 当前默认目录只有 qwen3.5:2b;Web 的“本轮模型”选择在 Turn 接受前固化 profile,模型 只能在 Turn 之间切换。切换后从 SQLite 完整 durable transcript 按新窗口、能力和 estimator 重编 ContextPlan;不支持 Tool 的模型得到空 ToolSet。provider endpoint iollama:11434 仅存在于受信目录和 Broker,不由 /api/models、Worker IPC 或 Run 事件暴露。 当前 Ollama 生成链路固定使用流式 POST /api/chat;运行时不调用 /api/generate,也不允许 浏览器或 Worker 选择 Provider 路径。普通无 Tool 回答固定使用 temperature=1.0/top_p=0.95/top_k=20/presence_penalty=1.5/seed=0;带 Tool schema 的选择阶段 固定使用 temperature=0/seed=0。这些参数属于受信 generation policy,浏览器、Worker 和 单次请求均不能覆盖。

启动时,受信 Ollama Broker 会从 /api/show 的架构专属 context_length 读取模型 原生上下文窗口,而不是把 4096 或某个模型名称对应的窗口写死。当前 qwen3.5:2b 报告 262144 tokens;运行策略取原生窗口与受信上限 32768 的较小值, 目录输出上限与运行窗口的有界比例共同决定输出预留;当前预留 4096 tokens,所以硬输入 预算为 28672 tokens。

ContextPlan 同时记录模型 digest、原生/运行窗口、输出预留、ToolSpec digest、会话历史投影 和四个不可混用的计数域:AdmissionUpperBound 只用于硬安全准入, ProviderObservedUsage 只描述绑定 exact request digest 的实际 input/output,带误差的 SoftContextEstimate 用于自动压缩,服务端 NextTurnProjection 用于用户发送下一条消息前 的容量预览。未来切换模型时,所有计数按新 profile/renderer/ToolSet 重建,不把 28672 或 某个模型名称写进会话数据,也不把 UTF-8 byte 上界冒充真实 token。

Web 的上下文主指标用 NextTurnProjection.fixed_context_tokens 与同一投影的 operational_context_tokens 展示当前 committed state 的占用和窗口剩余百分比; safe_user_tokenscompact_before_user_tokens、误差与上一轮 Provider input/output 是次级 信息。样本不足、active Run、scope 漂移或容量状态无法可靠投影时,界面显示 unavailable/stale 原因,不用 byte 上界或旧 Provider usage 猜测替代值。 服务端首先按下一条消息可能启用的 EffectiveToolSet 生成 conservative_tools 主投影;Web 只要该投影可用就优先展示。若其 ToolSet scope 尚无校准,但严格独立的空 ToolSet scope 已有 完整观测,响应可另带 chat_only_projection,界面明确标成“纯对话基线”;它的 ratio、误差 和余量不会转移到工具场景,下一条消息实际启用工具时仍在 Turn admission 前重新编译和准入。

完成 Turn 以 CompletedTurnContext 保存有界、顺序化的 user、assistant Tool call、低权限 Tool result receipt 和 final assistant。自动或手动压缩从最旧端移动完整 bundle,使用经过 Provider observation 校准的 soft estimate 和明确误差;每层投影后仍独立执行硬 admission。 校准样本来自 Agent SQLite 中最近 16 条完整 Provider usage,且必须同时匹配 model profile、 renderer、ToolSet 与 compression policy;因此 Gateway 重启后的 preview、Run admission 和 自动压缩使用同一依据,零值或 incomplete response 不会制造虚假精度。 重复 guard 主动关闭 Provider stream 后无法取得可信终帧 usage,因此对应 soft preview 会在 新的同 scope 完整 observation 到来前明确显示 unavailable;这不会阻断下一轮提交。下一轮仍 从已提交的 CompletedTurnContext(含截断 marker)重新渲染,并以 exact request 的 AdmissionUpperBound 做硬准入,不会把中止调用的零计数当成窗口占用。 合法 Turn 在首次模型调用前还会为最大受准 final、Tool receipt 和最小下一条消息预留 continuation headroom;SQLite canonical transcript 从不因投影、摘要或模型切换而删除。

semantic-summary-v1 永久禁止新建,只保留严格旧 codec。当前默认启用已经重新资格的 semantic-summary-v2;operator 可在启动前设置 HARNESS_V2_SEMANTIC_SUMMARY_V2=0 紧急 回退到纯 deterministic collapse。v2 的规则位于独立可信 system role,source transcript 和 生成摘要都只以低权限 conversation data 进入主模型,ToolSet 固定为空。相同 source 跨 Turn/ Gateway 重启零 HTTP 复用,source 扩展使用 parent snapshot + 新 completed bundle 增量更新; 失败、超限、timeout 或 circuit open 都安全回退,不修改 canonical transcript。完整迁移、 回滚和资格证据见 Context reliability remediation plan

utf8-bytes-upper-bound-v1 admission fallback 在没有模型专用 tokenizer 时把每个 UTF-8 byte 按一个 token 计安全上界,并计入实际 section labels/roles、Provider Tool schema 和 256 tokens 的 provider template reserve,故意高估常见文本以便安全拒绝超限请求。 窗口决策依据实际模型 profile;provider 报告、Control Plane 校验过的 usage 是受信 观测,估算器仍是新渲染请求的保守 admission 边界。初始 ContextPlan 和每个后续模型 轮次都会对完整投影 transcript + 当前 Provider Tool schema 重做硬预算 admission;移出所有可 省略的完整历史 Turn bundle 后仍超限时,以 model_context_limit fail closed,不让 provider 静默截断 prompt。

完整 ContextPlan 只存在于受信 Control Plane。Worker 启动命令仍包含本轮用户消息, 但模型 IPC v2 只携带 plan/toolset 的 ID 与 digest 引用以及受信 Tool result call IDs; ContextPlan 中的平台/Agent/workspace 指令和完整 Tool manifest 不通过 IPC 交给 Worker, 也不能由浏览器覆盖。每轮由 ToolCatalog 与 policy 解析 EffectiveToolSet;Worker 只接收 有效 Tool ID 子集,并从运行时密封 catalog 解析同版本 ToolSpec 做本地校验与执行。 受信 Agent 指令要求创作和自包含问答默认直接生成;算术表达式使用常规运算优先级并在回答 前复核,不为这种自包含计算调用外部 Tool。 当前 release runtime ToolSet 包含 file/stat、file/read_text、file/glob、file/grep、 file/edit、file/write、exec/run 和 ask-only agent/delegate,并固定最多 2 次顺序调用:不可变 TurnRuntimeSnapshot 固化最多 4 次模型调用、 2 次 Tool 调用、模型/ContextPlan/Tool manifest、累计 usage 和 240 秒 deadline。每次受信结果 按原 call ID 回流;第二次预算用完后 provider ToolSet 收窄为空,并在最后一个不可信 Tool result 之后追加固定的受信 finalization marker,明确切换到普通文本最终答复。重复、乱序、 并行或第三次 Tool call 会以专用 model_tool_loop fail closed。builtin/echo 只留在密封 catalog 中兼容原型测试和旧 journal,不会暴露给新 release Run。

每个 Agent 创建时都会在自己的 data/agents/<agent-id>/workspace/agent-builder.md 得到一份 neutral default 指令文件; Gateway 启动时也会为缺失该文件的存量 active Agent 原子补齐,但不会覆盖已有内容。 operator 可直接编辑它。运行时只读取这一精确文件,不向上寻找,也不处理 include;同目录的 CLAUDE.mdAGENTS.md 等其它 harness 指令文件不会被读取。文件必须由当前用户拥有、是 UTF-8 regular file、只有一个硬链、不允许 group/world write,且不超过 32 KiB;编辑后推荐:

chmod 600 data/agents/<agent-id>/workspace/agent-builder.md

文件若在运行期间被删除,该轮按缺失 no-op;下次 Agent 加载或 Gateway 启动会重新补齐。 不安全、读取中变化或超限会拒绝新 Run。Control Plane 还会加入只含 UTC 日期/时区/Linux 的 allowlisted environment section。只有 Capsule workspace 自身是 Git repository 时才收集最多 16 KiB 的 tracked status;Git 在 2 秒只读 Landlock helper 内运行,输出标记为 untrusted project data,不会提升成 instruction。

模型还可通过 file/stat/file/read_text 读取当前 Agent 的 workspace 文件,并以 file/glob/file/grep 做有界、无索引搜索。该请求从 Worker 经有界 capability IPC 回到 Control Plane;Worker 本身没有 workspace 权限。 路径必须是规范相对路径,逐组件拒绝 symlink/xdev/危险目录;目标只接受 owned、单硬链、 非 group/world-writable、非稀疏、稳定的 UTF-8 regular file,最大文件 1 MiB,单次正文 最多 4096 bytes/256 行。结果包含 content/path identity digest 和 truncation receipt, 正文始终按不可信 Tool 数据处理,不生成 sidecar 或搜索索引。搜索限制 depth 16、entries 4096、files 1024、读取 2 MiB、matches 128、结果 12 KiB 和 wall time 1 秒;.git 不遍历, 结果按 UTF-8 path 稳定排序并携带 provenance/truncated reason。Grep 默认 literal;regex 只接受无 group/alternation/backreference/brace/*/+ 的受限子集,复杂模式 fail closed。

Agent 管理抽屉中的“研究与文档环境”是一个固定、显式安装的 Capsule capability,不是任意 pip install。首次安装由受信 Control Plane 使用 checkout-local uv 把 pypdf==6.14.2python-docx==1.2.0 及固定传递依赖以 --only-binary :all:--no-deps 安装到 .runtime/agents/<agent-id>/dependencies/research-documents/;source metadata 位于 同一 Agent 的 data/.../dependencies/。相同版本再次安装直接返回已有记录,因此该 Agent 后续任意 Conversation 都复用现有环境,不重复下载;其它 Agent 看不到也不会受其包变更影响。

安装成功后,未来 Turn 才会得到 document/extract_text。它支持当前 Agent workspace 中 最大 16 MiB 的 PDF、DOCX、HTML、Markdown 和 UTF-8 text,并按 offset_chars/max_chars 每次最多返回 4096 字符。Control Plane 以 descriptor 锚定方式拒绝 traversal、symlink、 hardlink、危险 mode、跨 device、稀疏或读取竞态,DOCX 还限制 archive entries 和展开总量; 随后只把一次性副本放入该 Run 的 private work root。解析器在 singleton Landlock/seccomp/rlimit domain 中运行,网络、fork、exec、workspace 和跨 Agent 路径均不可见, 只读固定依赖环境;terminal 后副本被删除。环境安装/删除会 fence 当前 Agent,active Run 存在时拒绝;删除环境不删 workspace/会话,删除 Agent 则连同环境完整清除。通用搜索引擎或 任意联网抓取仍应通过 operator-owned HTTPS extension 接入,不能由文档解析环境自行联网。

file/editfile/write 只能由受信 Control Plane 执行,并始终进入 Web 可见的 ask 审批。现存文件必须先在同一 Run 完整 file/read_text,mutation 绑定 path identity、完整 content digest 和精确 diff;Edit 的旧片段必须恰好出现一次。create 绑定 target-absent 与 parent identity,并用 no-clobber rename;replace 用同目录 0600 private temp、文件 fsync、 Linux renameat2(RENAME_EXCHANGE)、旧 inode/content 复核和 parent fsync,绝不原地写或 跟随 symlink。单次新内容 8 KiB、Edit 片段各 4 KiB、审批 preview 4 KiB、两次 fsync, 每个 Capsule 最多清理 16 个保留命名空间 temp;commit 结果不能证明时记录 outcome_unknown 且不会自动重放。

exec/run 不是通用终端。它接受固定 runtime-compile,或 builtin-only bounded-bash 单命令。Bash parser 只允许 printf|pwd|true|false,最多 8 个参数,拒绝 substitution、 backtick、env assignment、redirection/heredoc、subshell、pipe、glob、rc、git/package 命令; 再把规范 AST、cwd/env policy 与零 redirection 绑定审批。executable identity、cwd 和 clean env 均由 Control Plane 固定,模型不能提供 PATH 或 endpoint。每次执行必须由 operator 审批;runner 在同一 PID 中先安装 10 CPU 秒、256 MiB、32 FD、2 MiB/文件等 rlimit,随后 安装 Landlock 和拒绝 network、fork/clone、setsid 的 seccomp;compile 还拒绝后续 exec, bounded Bash 只允许进入已绑定的 Bash image,语法不能请求其它 exec。随后通过 ready/release handshake,Control Plane 用 PIDFD 和 /proc 复核后才放行, 12 秒 wall、12 KiB stdout+stderr 和 8 MiB allocated output 超限即杀死确切 PID;成功或 失败都会清空输出与 PID record。该 singleton kernel domain 是 v1 的完整后代容器,不依赖 仅能辅助清理的 process group。

同一个 runtime-compile 还可由认证、CSRF 管理面从既有 parent Run 显式提交为 background Task;它不是 shell 字符串或模型隐式 daemon。每 Agent 最多保留 128 个 Task、同时最多 4 个 active,单结果 16 KiB、每 Task 最多 4 条各 4 KiB 通知、终态保留 7 天。Task 有独立 HOME/TMP/XDG/work/output,复用 12 秒 wall 与 singleton sandbox;状态只在 queued/running/terminal 边界提交,不逐 stdout/stderr chunk 写盘。重启把未完成 Task 标为 interrupted 且不重放;取消、Conversation 删除、stop 和 Agent delete 均先收敛 runner、 清除精确 Task 根。

agent/delegate 只接受另一个 active Agent ID 和 4 KiB message,并始终要求 operator 审批。父 Agent 得到一个绑定 parent Run 的 durable Task 和最多两条 mailbox 事实;子 Agent 在自己的 Capsule、Conversation、Worker、模型 session 与 Landlock/seccomp sandbox 中 执行。全局同时最多 2 个委派、每 parent Run 1 个、depth 1、wall 45 秒,返回父模型的 answer 最多 8 KiB。取消、deadline、重启、父会话删除和 Agent drain 都会收敛 child Run;删除父 会话会继续删除其 child Conversation,删除 child Agent 会删除完整 Capsule。Web 的子智能体 卡片显示父→子/子→父消息,并通过 Agent-scoped replay/context API 单独查看 child Run; 普通 assistant 文本不构成 mailbox 或委派事实。

Agent、会话与同会话多轮

浏览器登录后的主要 API 是:

操作 路径
Agent create/list/get/rename `POST
Agent upgrade/delete POST /api/agents/<agent-id>/upgradeDELETE /api/agents/<agent-id>
研究环境状态/安装/删除 `GET
列出、创建会话 `GET
Slash Command 目录/执行 GET /api/agents/<agent-id>/commandsPOST /api/agents/<agent-id>/sessions/<id>/commands
分页读取、重命名、删除会话 `GET
从 completed head 续接/分支 POST /api/agents/<agent-id>/sessions/<conversation-id>/continue,`mode=continue
查看/取消本轮上下文准备 GET /api/agents/<agent-id>/sessions/<conversation-id>/preparationPOST .../preparation/cancel
查看下一轮安全余量 GET /api/agents/<agent-id>/sessions/<conversation-id>/context-preview
在会话内开始下一轮 POST /api/agents/<agent-id>/sessions/<conversation-id>/runs
订阅、取消或回放 Run `/api/agents//runs//events
检查本轮有效上下文元数据 GET /api/agents/<agent-id>/runs/<run-id>/context
查看/解析待审批 capability GET /api/agents/<agent-id>/permissionsPOST /api/agents/<agent-id>/permissions/<permission-id>
分页读取 capability 审计 GET /api/agents/<agent-id>/runs/<run-id>/capability-audit
提交固定 background Task POST /api/agents/<agent-id>/runs/<run-id>/tasks
列出/读取/取消 Task GET /api/agents/<agent-id>/tasks[/<task-id>]POST /api/agents/<agent-id>/tasks/<task-id>/cancel
读取 Task 语义通知 GET /api/agents/<agent-id>/tasks/<task-id>/notifications
读取会话的子智能体链路/邮箱 GET /api/agents/<agent-id>/sessions/<id>/subagents
回放/检查指定 Agent Run `GET /api/agents//runs//replay
查看当前扩展目录 GET /api/extensionsGET /api/agents/<agent-id>/extensions
列出/安装/删除 Skill `GET

preparation GET 在 idle 时返回 operation_id=null;preparing/cancelling 期间返回本次操作 稳定且非空的 ID,并在 admission 交接为 active Run 时保持不变。取消 POST 的 JSON 只能是 {"operation_id":"<GET 返回的 exact ID>"}202 仅表示 control request 已确认,不能当作 准备或 Run 已经停止;客户端继续 GET,直到 state 为 idle 才能展示“已安全取消”。若该 ID 已被新一轮准备替代,响应为 stale,旧请求不得误取消新操作;handoff 后同一个 ID 会把取消 定向到对应 Run。

Conversation 是持久容器,Turn 是一次用户提交及其终态,Run 是执行该 Turn 的独立 Worker/模型会话。一个 Conversation 同时最多有一个 active Run;活跃时再次发送、分支或 删除会返回冲突,先取消或等待终态;其它 Conversation 可以同时浏览或独立运行。详情默认 只返回最近 32 个 Turn;客户端只能回传响应中的 page.next_before_cursor 向前加载,不能 构造 Turn position。turn-page-v2 cursor 经认证并绑定 Agent、Conversation、revision 和 页边界;篡改、跨会话复用、rename 后旧 cursor 或 Gateway 重启后的旧 cursor 都以 invalid_session_cursor 拒绝。服务端每页只从 SQLite 读取该页的有界 Turn 正文,不复制、 改写或预读完整 canonical transcript。 成功终态会原子保存完整 completed Turn bundle;failed、 cancelled、interrupted Turn 仍在 UI 中保留用户输入和状态,但没有可提交的 assistant, 也不会进入下一轮模型历史。

Gateway 为每个打开的 Conversation 懒创建一个逻辑 QueryEngine;同一进程内相同 conversation ID 始终得到同一个 Engine。它固定 Agent/Conversation 身份、把每次提交 映射为新的 Turn/Run,并提供恢复、取消和删除入口。Engine 只持身份、短时入口锁和 retired 标记,不保存消息副本、事件、ContextPlan、模型连接或 Worker;因此刷新或重启 不会产生第二份会话事实。Registry 最多保留 100 个 Engine,与持久会话容量一致;删除 后旧 handle 立即失效并从 Registry 驱逐。

Gateway 启动时会把上次进程遗留的 running Turn 收敛为 interrupted,释放该会话后 即可继续发送。GET /api/sessions/<id> 从 Agent 专属 SQLite 恢复 durable transcript, 不复活旧 Worker。浏览器刷新后,如果同一 Gateway 仍保留该 active Run,前端会从 running Turn 的 run_id 重新附着,并携带最后应用的 Last-Event-ID 做最多 3 次有界 重连。若 live RunRecord 已不存在,events endpoint 固定切换为只读 durable replay source, 以明确 gap/snapshot control 收敛到已持久 terminal;选择已结束会话时,界面也会恢复最近 Run 的 durable 时间线。旧模型流和 Worker 不会复活,ephemeral delta 不跨进程恢复。

事件时序工作台可在同一 Conversation 的全部 Turn/Run 间切换,并用文字区分 Harness → LLMLLM / Broker → HarnessHarness → ToolTool/恢复 → Harness、Harness 内部状态和 replay control。每次逻辑模型调用固定增加 一个 model.request.started 和一个 model.response.finished durable 边界;每次实际首帧 HTTP attempt 各增加一对不含 endpoint/prompt/正文的 model.transport.attempt started/finished 诊断,因此实际尝试数为 N 时总数为 2 + 2*N,且不按 frame、token 或 timer tick 写入。逻辑轮次、恢复 attempt、provider 调用序号和 transport attempt 分开显示。精确 context/media overflow 在任何 provider frame 出现前最多触发一次 model.recovery.started 和一次重新 admission 的调用,第二次失败不循环, 认证、网络、格式、取消和部分流不触发该路径。事件详情展示的 是完整 canonical envelope,不是 Ollama 原始请求或响应正文。每个 Run 顶部的 User → Harness 节点来自对应 Turn 的 durable user message,是带清晰标签的界面投影, 不是伪造的 EventEnvelope,也不占用 canonical seq

上下文检查接口和 UI 只在用户显式点击时读取,响应强制 no-store。Gateway 仍保留该 Run 的不可变 ContextPlan 时,它返回经过重新校验的 section 顺序、role/trust/provenance、 字节/Token 估算、digest、纳入/省略历史计数、窗口和预算;ContextPlan 已退出内存时只返回 validated run.started 摘要。两种响应都不返回 system/hidden prompt、用户/assistant 正文 或 provider messages;Conversation 正文继续由会话 API 展示,也不会为了检查功能复制写入 事件 journal。

每次 Turn admission 还会在同一 SQLite 事务保存一条 digest-only context projection boundary,用于证明该模型视图绑定了哪一个 Conversation revision、model/profile、 instructions/history、Tool policy 和 renderer。它不复制 prompt 正文;后续边界替换使用 旧 digest 做 CAS,因此崩溃不会留下半写 snapshot,Conversation 删除会一并清理。

诊断正文提升默认关闭。只有 operator 在启动前显式设置 HARNESS_V2_CONTEXT_REVEAL=1 时才启用独立令牌、CSRF 和有界审计路径;即使启用, platform/Agent/workspace section 仍永不回显,其它 section 也只提供最大 2048 bytes 的 credential-redacted excerpt。令牌位于 .runtime/secrets/context-reveal-token,不得交给 browser JavaScript、放入 URL/命令参数、日志或提交。

这些接口使用同一个项目 bootstrap token,是 single-operator 管理面:所有已认证浏览器 session 都能管理全部 Agent。它不是用户账号或租户隔离系统。所有业务读取都 要求登录 session,创建、删除、发起 Turn 和取消还要求 exact same-origin 与 CSRF。 审批同样要求 CSRF,body 只接受 approve|deny;浏览器不能创建 pending request。 当前文件读取使用 policy allow,仍会留下完整的 permission/operation audit;因此它不会 出现在 pending 列表。Edit/Write、受限 command、安装后的 Skill 和 subagent delegate 使用 ask policy;extension catalog 默认为空,任意 Shell、stdio MCP 和请求级 endpoint 不存在。

一键启动与停止

./start.sh
# 仅在确需脱敏 prompt 诊断时:
HARNESS_V2_CONTEXT_REVEAL=1 ./start.sh

冷 checkout 时,start.sh 会按需调用 checkout-local bootstrap。也可以提前执行:

./bootstrap.sh
./bootstrap.sh --offline   # 仅使用已有的项目内缓存

Gateway 固定监听 0.0.0.0:20815。启动成功后访问:

  • Web UI:http://<主机地址>:20815
  • 健康检查:http://127.0.0.1:20815/health(返回 release: "0.2.0";其中 prototype: true 仅是不可删除系统 Agent 的旧协议兼容字段,不表示整个 release 的支持状态)

首次启动会生成权限为 0600 的登录 token:

.runtime/secrets/web-bootstrap-token

把 token 内容输入登录页。不要把它放入 URL、shell 命令参数、日志、截图或提交。 登录后使用 HttpOnly session cookie;修改状态的请求还需要 CSRF token。

需要显式轮换时使用交互式命令:

./set-access-token.sh

命令只从无回显终端读取两次,不接受 token argv 或环境变量;它会验证旧 secret path、 停止 Gateway、以同目录 0600 临时文件和 fsync/atomic replace 更新 token,再重启并使 全部旧浏览器 session 失效。默认自动生成值仍是 256-bit/64 位 lowercase hex;显式值 只允许 10..128 个 ASCII 字母、数字或 ._~+-。短口令的熵远低于默认值,在当前无 TLS、 监听 0.0.0.0 的部署中只能用于受信且防火墙保护的网络。

停止整个当前系统:

./stop.sh
./stop.sh --force   # 缩短退出期限;仍校验进程身份

脚本只会停止由本 checkout 记录且重新验证过身份的 Gateway/Worker。它不会因为 20815 被占用就杀死未知进程。启动失败会回滚已启动组件;停止会检查每个 Agent 的 Run 记录并清除已确认没有进程占用的临时 Run 根。

一致性备份/恢复会先 stop,并且只读写 checkout 内的 data/backups/ 和私有 staging:

./backup.sh before-upgrade-20260720
./restore.sh backups/before-upgrade-20260720.tar --yes

恢复会把替换前的数据保留在 .runtime/recovery/,不会静默删除。完整测试、依赖审计、 CycloneDX SBOM、真实模型资格和 source artifact 可一键执行:

./release.sh RR-QUA-20260720-01

这三个命令的停机、token、产物与回滚合同见 release runbook

项目内状态

所有环境、缓存、密钥、进程记录、日志、临时文件和数据均在本 checkout:

.tools/                         checkout-local uv 引导工具
.venv/                         控制面 Python 环境
.runtime/python|cache/          managed Python、uv/pip/bytecode 缓存
.runtime/home|tmp/              被重定向的 HOME 与临时目录
.runtime/config|share|state/    被重定向的 XDG 配置、数据与状态
.runtime/xdg-runtime/           被重定向的 XDG runtime
.runtime/control-plane/         Gateway PID、生命周期锁、有界轮转日志
.runtime/secrets/               Web bootstrap token
.runtime/agents/<agent-id>/     Agent worker-env、持久 dependency venv、日志和临时 runs
.runtime/qualification|release/ 资格记录、SBOM 和 release artifact
data/agent-registry.sqlite      Agent lifecycle/generation authority
data/agents/<agent-id>/         manifest、workspace、artifacts、dependency metadata、会话/事件 state.sqlite
backups/                        operator 创建的私有 data 备份(默认忽略)

env.sh 会清理继承的 Conda/virtualenv/package-install 变量,并把 HOME、TMP、XDG、 uv、pip 和 Python bytecode 路径重定向到 checkout 内。不要使用系统 pip、全局 npm、 用户 home 缓存或 /tmp 作为项目状态目录。

查看可清理范围:

./purge.sh --help

可选范围为 cache|logs|environments|data|dependencies|runtime|all,实际删除必须带 --yesdataall 会删除不可重建的 Agent 持久状态。

数据与 SSD 边界

  • Agent 的 Conversation/Turn 和 canonical events 共用私有 state.sqlite;SQLite 使用 WAL、synchronous=NORMAL、16 MiB journal limit 和有界数据库容量。
  • assistant.block.delta 只在内存和 SSE 中流动,不逐 token 写盘。
  • Turn 开始和终态只在语义边界做事务写;completed assistant 只在完成时一次保存, 不随流式 chunk 重写完整会话。删除会话原子删除其 Turn 和 Run events,不为每次删除 执行 VACUUM 或全库重写;这提供应用层删除,不承诺 SSD 的取证级物理擦除。
  • run.started 只记录 ContextPlan 的 ID、digest、section 数量和预算元数据,不写完整 prompt;每种 terminal 都记录 usage。token 数由 provider 报告、Control Plane 按模型 profile 校验并累计,Worker 不可伪造;complete=false 表示当前 provider 轮次没有 产生可校验终帧,因此累计值可能只覆盖此前完整轮次。
  • 旧 Run / 原型测试中的 builtin/echo 输入和结果分别限制为 8192 UTF-8 bytes。
  • 旧 Echo canonical 结果仍完整显示在事件中;送给模型的单结果投影最多 4096 bytes, 超限时使用带 call ID/原始 bytes/digest/reason 的确定性 receipt,单 Run 投影历史最多 16 KiB,随后重新执行模型 admission。
  • 每 Run 最多 512 个 live event、1 MiB live event bytes、256 KiB durable bytes。
  • 最多 4 个 active Run、内存保留 64 个近期 Run、journal 保留 256 个近期 Run。
  • Model Broker 最多同时打开 2 路 Ollama stream;其余 active Run 最多等待 30 秒,超时 以可重试 model_busy 收敛,不创建无界 provider 请求队列。
  • 每个受信 ModelCatalog entry 固化独立的阶段超时画像。默认 qwen 首帧等待 60 秒、帧间 空闲 20 秒、单次调用 120 秒;首帧前且没有任何 Provider 输出时可透明重试一次。失败分别 收敛为 model_first_frame_timeoutmodel_stream_idle_timeoutmodel_turn_deadlinemodel_transport_timeout,部分输出后绝不重试。
  • 同一受信 model/profile 的实际 HTTP attempt 连续两次零首帧失败后,Broker 开启 30 秒 有界健康短路并快速返回可重试 model_temporarily_unhealthy;成功首帧立即复位,状态表 最多 16 项且不持久化 endpoint 或跨 Gateway 重启继承。
  • 默认 qwen 的单次输出预算为 4096 tokens;这为长回答和模型内部推理留出余量,同时仍受 12 KiB 可见正文、1 MiB/4096 Provider 原始流上限、120 秒模型调用和 240 秒 Run deadline 的多重上限。
  • 普通无 Tool 回答在最近 2048 个 Unicode code point 内出现精确 suffix cycle 时,guard 只接受 32..512-byte cycle、至少 3 份且累计至少 512 bytes 的证据,并按每 64 个新接收 bytes 检查一次。命中后立即关闭 /api/chat,保留已发送正文、删除尚未发送的重复尾部, 并追加一次 [回答因重复死循环被截断;后续忽略重复尾部。]。该 Turn 以 run.completed.reason=repetition_truncated 完成,Provider usage 标为 incomplete;用户明确 要求逐字重复大段内容时也可能触发,这是为有界资源选择的已知取舍。
  • 普通文本回答达到 12 KiB commit ceiling 时,Broker 只提交 UTF-8 完整边界内的有界前缀和 一次受信截断标记,并继续有界排空 /api/chat 流直至取得、校验最终 usage;该 Turn 以 run.completed.reason=max_output 完成。达到上限时若同时出现 Tool call、没有可见正文, 或终帧/usage/协议不合法,仍 fail closed,不把不可用输出包装成成功回答。
  • 1 MiB/4096-frame Provider 原始流会被合并成最多 128 个 content IPC frame;受信 Broker error 的 code/retryability 由 Control Plane 写入 canonical run.failed,不信任 Worker 转述。
  • 每个 Run 有 240 秒 wall deadline;Run 树最多 1,024 项、16 MiB 逻辑数据、32 MiB 实际分配数据,并以不高于每秒一次的频率检查。
  • Gateway 日志单段 5 MiB,保留 3 个备份,总上界 20 MiB;写入批量刷新。

这些是当前代码的硬边界,不代表完成了长期 soak 或所有硬件磨损验证。

验证

source ./env.sh
./.venv/bin/python -m pytest
./governance.sh
./release.sh RR-QUA-YYYYMMDD-01

已启动 Gateway 的有界真实模型 workload、磁盘增长和残留记录使用 runtime qualification contract;该应用层记录不能替代 不受支持架构、长期 soak、内核/设备物理 flush 或 SMART 证据。独占 资格轮次可用 ./start.sh --qualification-sync-count 显式加载 checkout-local 的低写放大 libc sync-call counter;正常启动永不加载。其固定共享页只统计 supervisor、Gateway 和 Worker 的 fsync|fdatasync|msync|syncfs|sync_file_range|sync symbol 成功/失败,不代表 NVMe 实际写入。完整使用和 A/B 观测者效应要求见资格契约。

真实端到端验收还应执行 ./start.sh、检查 /health、通过 Web 发起一次 Run,确认 模型为 qwen3.5:2b、事件到达唯一终态;随后在同一会话用依赖首轮内容的问题验证 第二轮历史,重启 Gateway 后再发第三轮验证恢复后的 completed history 确实进入模型 上下文。删除验收会话后,详情与其 Run event endpoint 都应返回 404;最后执行 ./stop.sh 并检查无受管进程、PID 文件或 Run 根残留。

2026-07-24 的 repetition containment 资格把目标五输入会话在三个新 Conversation 中完整 执行三次,并另用独立受信测试进程恢复旧采样触发真实 guard;15 个普通 Turn、截断后的续轮、 Provider usage、删除和正常/强制 stop 均通过。实现合并到 main 后又从合并后的 checkout 完成 776 passed、治理和 /health 验证。完整的有界结果和限制见 REP-R03 record

仓库结构

src/agent_builder_v2/       当前唯一运行时
tests/                      当前运行时的单元、集成和安全回归测试
scripts/                    生命周期辅助程序
docs/                       原则、治理、设计和活动计划
references/claude-code/     Claude Code 研究资料及来源记录
_legacy-reference/          旧系统只读快照,不属于当前运行时

旧系统归档仅供用户明确要求时做历史调查;当前代码、脚本、测试和文档不得依赖或扫描 它。Claude Code 材料也是只读设计输入,不是运行时依赖,使用前请阅读 reference guideprovenance

文档入口

About

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages