ThearchyHelios
|
b634c89be4
|
人工介入的驳回按钮不再是摆设,画布脏边与待办入口
## 点"驳回"实际等于放行
ApprovalCard 无条件渲染"通过/驳回"两个按钮,而后端 human 节点只在 approve
模式下读 approved:edit 和 input 分支直接取 value,__decision__ 硬编码成
"approved",approved 字段看都不看。按钮在、文案在、toast 说"已驳回",运行却
照常往下跑——比没有这个按钮更糟。
这两种模式在图上只有一个 out 出口(没有 rejected 那条边),所以驳回的正确
语义是终止运行。缺省仍是通过,只传 value 不带 approved 的老调用方行为不变。
前端按钮文案改成"驳回并终止",toast 也说清楚。
顺带:run 级的错误消息不再给 NodeError 套类型名前缀,界面上从
"NodeError: 人工驳回:内容不合格" 变成 "人工驳回:内容不合格"。
## 改分支 case key 会留下看不见、删不掉的脏边
出口 handle 是从 config 算出来的(分支的 case key、human 的 mode),而
Inspector 的输入框逐字符 setConfig。key 从 yes 改成 yesno 的过程中 handle id
会依次变成 y/ye/yes…,已有边的 sourceHandle 仍是 yes。React Flow 对这种边只在
console 打一条 008 警告然后不渲染——看不见、点不到、删不掉,却照样被 toGraph
序列化存盘,后端校验也不管。
updateNode 现在同步处理:出口数量没变就按位置重映射(改名时连好的线跟着走),
出口真的没了就把边一并清掉。
## 未保存的编辑会被静默丢弃
切换工作流直接 load() 覆盖画布,没有 dirty 检查;全局也没有撤销和
beforeunload。加了切换确认与关页面拦截。撤销栈是更该补的,但那是另一件事。
## 刷新后"等待人工"的运行没有任何入口
审批卡片挂在 studio store 的当前 run 上,而那是纯内存的,刷新后为 null,卡片
就不渲染了。更糟的是导航把待办徽标挂在"运行"页上,用户点进去——而 RunsPage
从头到尾没有审批 UI,只有只读事件列表。现在 RunsPage 为选中的运行渲染
ApprovalCard,徽标指向的地方终于能处理待办。
回归:input/edit 两种模式的通过与驳回、approve 模式、代码节点 strict 档、
分支、口径卡出具 formal、工具节点、SSRF 拦截、记忆工具,全部通过。
|
2026-09-14 15:46:45 +08:00 |
|
ThearchyHelios
|
761117344b
|
沙箱会话生命周期与记忆检索
## 每跑一次图就多一台常驻 VM,没有任何自动回收路径
_reap_idle 只在 run() 里调,也就是说没人再跑代码时闲置 VM 永远收不掉,
microvm_idle_seconds 形同虚设;而 thread_id 每个 run 都是新的,runner 的
finally 又从不调 cleanup,于是每跑一次带代码节点的图就多一台几百 MB 的常驻
microVM(seatbelt 那边同样积了几十个历史会话目录)。
两处一起补:run 到终态就 cleanup(run_id)(interrupted 除外,人回来还要接着跑),
外加 lifespan 里起一个后台回收任务定期 _reap_idle——回收本来也不该占着请求
路径的时间。实测连跑三次 live_vms 稳定回到 0。
## 超时会连人带文件销毁整台 VM
会话契约是"同 session_id 共享文件系统,可以先写文件下一步再读回来",而
microVM 超时直接 _destroy,工作区连同用户数据一起没了,返回结果里没有任何
提示。另外三个后端超时只 killpg、工作目录照留——换到 microVM 之后同一张图的
行为就变了。现在超时先在 VM 内 pkill 掉跑飞的进程(入口名带唯一后缀,不会
误伤同会话的并发执行),确认 VM 还活着就保住工作区;收拾不动才销毁。
限额变更那条没法避免重建(VM 内存是启动时定死的),但不能不吭声:同一个 run
里两个代码节点填了不同的 memory_mb 就会触发。ExecResult 新增 session_reset,
代码节点在时间线上明确告警,否则下游只会看到一个无从追查的 FileNotFoundError。
## 新建 scope 写入第一条后永远搜不到
hybrid_rank 的 _norm 在 hi==lo 时把所有分数抹成 0,而候选只有一条时 hi 恒等于
lo,合并分 0.0 会被 min_score 过滤掉。"先写一条、下一轮再 recall"恰恰是 agent
最常见的用法。全都一样分是"都一样好",不是"都一样差",改成返回 1.0。
## 记忆去重误判,写入被静默丢弃且返回不相关的旧记忆
remember 拿 recall 的分数判重,但那是 min-max 排名归一化的结果——排第一的那条
在两路信号上都会被归成 1.0,与它和查询像不像毫无关系。于是 scope 里只要已有
两条记忆,新内容随便撞一个字排到第一就被判成重复丢掉,接口还返回 201 并把那条
不相关的旧记忆当成"刚保存的"交回去。改成直接比未归一化的余弦相似度。
验证:三条互不相关的记忆都能写进去、完全相同的仍被去重、单条 scope 可检索。
|
2026-09-14 15:41:05 +08:00 |
|
ThearchyHelios
|
b0575a7d1b
|
引擎四处:错误处理、恢复语义、幂运算炸弹
## 一条分支条件能把后端冻死
求值器放行 ast.Pow,唯一护栏是 AST 节点数 <= 200。`9**9**9**9` 只有 11 个节点,
却让 CPython 无限期算大整数。而求值是在事件循环里同步执行的(分支条件、
transform、口径卡、每个节点的 skip_if),没有线程池隔离也没有超时,asyncio 的
cancel 打断不了同步 CPU。给底数和指数加上界,超界报 ExpressionError。
2**10、2**0.5、10**-2 这些正常用法不受影响。
## NodeError 绕过 retries / on_error / node.failed
`except NodeError: raise` 排在重试与兜底之前,于是节点自己抛的错既不重试、
也不发 node.failed、更不被 on_error=continue 接住——而 NodeError 恰恰是所有
执行器表达失败的标准方式(缺必填输入、模型没配、口径卡算不出…)。同一张图
配着同样的 retries=1 + on_error=continue,1/0 会重试并继续,而 JSON 模板非法
直接整个 run 失败。两条路径现在行为一致,node.failed 也带上 node_id
(前端靠它把出错节点从"转圈"收敛成"失败")。
## 没有 checkpoint 的运行被"恢复"成一份假结果
重启扫描把 running 和 queued 一律标成 interrupted,而 resume 不检查有没有
checkpoint 就发 Command。queued 的运行从没写过 checkpoint,LangGraph 会拿空
state 从 START 重跑整张图——run.input 完全没注入,用字段默认值算完还落成
succeeded。现在 queued 直接标 failed(没有进度可恢复,请重新发起),resume
先 aget_state 确认有断点,没有就 409 并说清为什么。
## 并行人工节点:一次 resume 打爆整个 run
同一 superstep 有多个 interrupt 时,resume 仍只发标量 Command,LangGraph 直接
抛 RuntimeError;而所有 pending 审批已经被写成同一个回复。Approval.interrupt_id
和 ResumeIn.approval_id 这两个字段一直存在却从没被用过。现在按 interrupt_id
逐条收集:多条待办时必须指定 approval_id,回复的那条转 answered 继续等,
凑齐引擎在等的全部 interrupt 才发 Command(resume={iid: value})。
验证:两个并行 human 节点——不指定 approval_id 被 409 拒、回复第一条后仍
interrupted 且剩 1 条 pending、凑齐后 succeeded。单人审批与 decide 通道回归通过。
|
2026-09-14 15:35:47 +08:00 |
|
ThearchyHelios
|
0afffd4b9a
|
补上四道形同虚设的门禁
## 沙箱关不掉
get() 先看 prefer,而 _resolve_named 完全不看 settings.sandbox_backend。
配置里 off 只挡得住默认路径,图里任何 isolation=strict 的代码节点照样能跑。
另外 alias.get(prefer, prefer) 对未知档位原样透传,而 isolation 是图 JSON 里的
自由字符串,写 isolation="local" 就能点名那个明说了"没有任何访问控制"的兜底
后端——档位是用来提高隔离的,不该能降级。现在 off 优先判定,档位限白名单。
## formal 可以跑从未过闸的版本
"不可变"和"过过闸"是两件事:PATCH 存出来的草稿同样有不可变快照。原来只查
version 存不存在,于是工作流只要发布过任意一版,之后所有草稿都能以 formal
身份启动,发布期的 validate_graph + lint_for_publish 全部绕过。这和代码里
"正式运行的全部语义就这一条规则:必须引用一个不可变的已发布版本"直接矛盾。
现在要求 version == published_version,否则 409。
放大这个问题的是 update_workflow 改图不重置 status:发布过 governed 之后把
画布改成含 supervisor 的图,界面上仍显示 governed。status 说的是当前画布的
状态,改图就退回 draft;已发布的那版由 published_version 指着,formal 不受影响
(所以 formal 的前置检查也从看 status 改成只看 published_version)。
## governed lint 只查 contract 这个 key 在不在
契约内容一律不校验,于是 contract={"metrics_from": "随便一个字符串"} 就满足了
"受管模板必须声明出具契约"这条硬门禁——一个结构上保证每次都出 formal 的空壳
模板能合法发布成受管模板。门禁写成这样还不如没有:它给的是"这张图过过闸"的
错觉。现在实质校验:metrics_from 必须指向图里真实存在的口径卡节点、narrative
必须非空、governed 下 required 必须非空、非 strict 给警告。
端到端验证:空壳契约发布被拒(4 条 issue)、改图后 status 退回 draft 且
published_version 保留、formal 跑 v2 被 409 拒、跑已发布的 v1 正常。
|
2026-09-14 15:30:57 +08:00 |
|
ThearchyHelios
|
2207269e07
|
修 critical:中文叙述里的数字校验一直是空转的
_NUM_RE 的后瞻写的是 (?<![\w.]),而 Python 的 \w 按 Unicode 判定,汉字也算
词字符。中文写作数字紧贴汉字不加空格是常态,于是整个 token 抽不出来:
extract_numbers('本周营收9999元,订单7777单。') → [] 全部漏检
extract_numbers('本周营收 9999 元,订单 7777 单。') → ['9999','7777']
等于说"叙述层无算术权限 + 数字回指校验"这套机制只对西式排版生效,中文叙述
完全不设防,而出具物照样盖 formal 章、印 matched_numbers: 0——读的人无法
分辨是"查过都对"还是"根本没查"。改成只排 ASCII 词字符,版本号 v1.2.3、
标识符 sha1 这些仍然豁免。
顺带修同一文件里的两条 high:
## 跳过规则只看形态不看语境
年份规则的"年"字是可选的,等于把 1900–2099 的任何独立整数都免检了——而
客户数、门店数、万元金额大量落在这个区间。日期规则不限年份范围,"9600/12"
这种比值写法会被整段当日期吞掉。引用标记 \[\d+\] 不限位数,[98765] 也放行。
'本周新增客户 2050 家,流失 1987 家。' → [] 两个数字都免检
'全年目标 9600/12 个月' → [] 斜杠两边都免检
年份改成必须带"年"字,日期的年份限 19xx/20xx、月份限 1-12,引用标记限三位。
宁可误杀(要求它回指指标集)也不放过——这是校验不是排版。
## 校验没发生被当成校验通过
decide_tier 只看 missing/unmatched,而这些列表全空既可能是"查过了都对",
也可能是"根本没查":叙述模板里一个笔误让 narrative 渲染成空串、metrics_from
指向不存在的节点,都会得到一模一样的空列表,然后盖 formal 章。
contract.narrative = "{{ vars.does_not_exist }}" → tier=formal, matched=0
contract.metrics_from = "压根没这个节点" → tier=formal, metrics=0
新增 gaps 承载"校验为什么不完整"(叙述渲染为空、指标源未解析、指标集为空),
非空就不允许 formal,并把原因印进 issuance。校验没发生却盖 formal,比漏检
某个数字更糟:它让整套机制看起来在工作。
端到端四例验证:编造数字→withheld、真实数字→formal、模板写错→degraded
(带 gaps)、指标源不存在→withheld(带 gaps)。
|
2026-09-14 15:27:33 +08:00 |
|
ThearchyHelios
|
7d00c24ab1
|
microVM 沙箱后端:内存限额终于真限得住,但网络关不干净
Seatbelt 在 macOS 上最难受的缺口是 RLIMIT_AS 不被强制——设 256MB 仍能分配
900MB。microVM 不是"更严格的访问控制",是换了一类边界:宿主 Darwin 27.0.0,
VM 里 Linux 6.12.99(libkrunfw),PID 1 是 init.krun,/Users 根本不存在,
free 只看得见 512MB。申请 900MB 会被 OOM killer 杀掉而 VM 照常存活。
实现按实测契约写,不按别的 SDK 的惯例推断:create() 直接返回已启动的
Sandbox,没有 handle/connect 两层;cwd/env/timeout 都是 exec 的原生参数;
超时抛 ExecTimeoutError 而不是返回非零码。
必须写明的缺口:network=false 时 HTTP/HTTPS 与域名解析都断得掉,但 UDP/53
拦不住,手写 DNS 包仍能拿到真实响应。default_egress=DENY、Rule.deny_dns()、
显式 deny UDP、max_connections=0 全试过都堵不上——deny_dns 生成的规则只针对
宿主,DNS 出口在 microsandbox 网络栈里是硬编码放行的。所以 health 里 network
标成 partial 而不是 true,设置页对应显示黄色「部分生效」:拦了一半要是显示成
全绿,比显示成全红更危险。
成本也照实说:首次拉 OCI 镜像实测 54.5s,之后热启动 0.19s、执行 7~30ms。
贵的只是第一次,所以 auto 在镜像没缓存时先用 Seatbelt,不让人干等一分钟;
显式选 strict 档不受此限——那是使用者自己要的硬隔离。
端到端 17 项验证通过:隔离、内存拦截、超时 124、退出码透传、多文件与 env、
会话间互不可见、VM 回收。Seatbelt 那档回归未受影响。
|
2026-09-14 14:10:10 +08:00 |
|
ThearchyHelios
|
1b595f699c
|
Copilot 流式生成:图在画布上实时长出来
此前点击生成后是 20-30 秒黑盒等待,模态框关着,用户看不到任何过程。
改成操作流协议:模型按 NDJSON 逐行输出 add_node / add_edge /
update_node / remove_node / done,后端边流边经 SSE 转发,前端每收
一个操作就应用到画布——节点带紫色描边逐个出现(临时摆位跟着入边源),
左下角浮条显示当前动作并可随时取消;done 后后端统一排版+校验,
以 final 事件整体落位,高亮几秒后淡出。修改现有图的场景只输出改动
操作,用户能看到 Copilot 具体动了哪里。
行解析对模型犯规免疫:markdown 围栏、夹杂的解释文字、done 之后的
多余输出都被跳过或截断(单测覆盖跨块行缓冲)。
顺手修结构化输出必失败的 bug:langchain 靠 title 字段识别裸 JSON
Schema dict,四处 with_structured_output 的 schema 都缺它,抛
ValueError: Unsupported function 后靠 fallback 文本解析兜底——
每次都白调一次模型。补上 title(llm 节点 output_schema、branch
语义分类、supervisor 路由、旧版 copilot generate)。
实测:首节点 7.2s 上画布(原先干等 27s),浏览器采样节点数
0→2→3→4→5→6 逐步生长,6 个新节点全部高亮,零页面错误。
|
2026-09-14 10:54:31 +08:00 |
|
ThearchyHelios
|
dcba9a63e0
|
一次思考在轨迹里只落一条事件
思考流上线时漏了把它登记进 ephemeral 集合,导致每个流式增量都
落库——一条思考链在执行轨迹里被拆成几十条 llm.thinking 事件。
拆成两种事件:llm.thinking.delta 是流式增量,只推给画布做实时
滚动、不落库(与 llm.token 同类);llm.thinking 是调用收尾时落的
单条汇总,携带完整思考内容(截断 6000 字符,完整版在节点输出
工件里)。顺带修好刷新回放:此前回放重现不了思考(增量被隐藏、
汇总不存在),现在一条汇总事件直接恢复全文,时间线上也可展开查看。
验证:确定性假思考模型过完整管道(3 条增量实时推、汇总恰一条且
内容完整);真模型跑模板⑤,轨迹 29 条降到 18 条,llm.thinking
恰好 1 条。adaptive thinking 对简单任务自主跳过思考时两类事件
都不产生,已从工件核实是模型行为而非管道丢失。
|
2026-09-14 10:40:20 +08:00 |
|
ThearchyHelios
|
46eb674bc2
|
指标集与出具契约:口径卡节点、叙述数字回指、三档出具、周报模板
派生边界收死在固定层:新增 metrics 节点(口径卡),指标由 AST 白名单
求值器确定性计算,产出受控指标集(含口径名与版本)。叙述层没有算术
权限——不是求模型别编数,而是它编了也过不了校验。
output 节点配 contract 即升级为出具契约:核对必需/期望指标是否齐、
从叙述中抽取所有数字逐个回指指标集(支持千分位、百分数↔比率、
万/亿后缀、按书写精度给舍入容差;日期、ISO 周、枚举序号、引用标记
不参与回指),三档判定 formal / degraded / withheld,缺口清单和
口径版本一起印在成果上,另发 issuance 事件入流水。白名单(allow_numbers)
是模板配置的一部分,进版本、被审查。
新增模板⑧周报:取数固定 → 口径卡算数 → LLM 只许引用清单 → 出具
逐数回指,是'模板定骨架、agent 填关节'的完整示范。
回指校验器 10 用例 + 三档判定 + 端到端五场景全部通过。
|
2026-09-14 10:17:45 +08:00 |
|
ThearchyHelios
|
4fdd58e3be
|
运行分级与版本钉死:formal/exploratory、方法卡钉版、口径升版被迫处置
formal 运行的全部语义就一条规则:必须引用一个不可变的已发布
WorkflowVersion(按 graph_hash 钉住);传裸 graph 的运行自动是
exploratory。发布后改图不影响已发布版本——formal 永远执行快照。
子图节点支持 workflow_version 钉版,这就是方法卡的机制核心:
钉住后上游改方法卡不会让口径悄悄漂移。钉住的方法卡出现新版本时,
formal 启动被拒,除非节点已声明 upgrade_policy(recompute 回算 /
dual 双印 / incomparable 标注不可比,无默认值);已声明的升版
以 caliber.upgrade 事件记入流水。
发布端点带两级门禁:published 只拦基础错误,governed 额外过治理
lint(禁 supervisor、方法卡必须钉版、agent 不得关闭全部审批、
output 必须声明出具契约)。X-Actor 记录发布人与运行发起人。
|
2026-09-14 10:14:37 +08:00 |
|
ThearchyHelios
|
2df591d05f
|
工件库:内容寻址存储、节点输出与工具快照落盘、运行清单哈希
事件流里的输出是给画布看的截断预览,出具溯源需要完整证据。
新增 data/artifacts/ 内容寻址工件库(sha256 即地址,取回时复验哈希,
篡改即报错);节点完整输出与工具调用快照(query+结果集)自动落盘,
事件只携带工件 id。运行终态对全部事件计算清单哈希存入 Run,
事后对事件流的增删改会与它对不上。
graph_hash 剔除 viewport 后计算——挪画布不改变图的身份。
Run/Workflow/WorkflowVersion/Approval 预置分级与治理列,
init_db 附带 SQLite 轻量列迁移。
|
2026-09-14 10:10:20 +08:00 |
|
ThearchyHelios
|
964eeaa829
|
AgentLab:可视化 Agent 编排实验台
在画布上拖拽编排 agent 工作流,并实时看着它执行。
后端 FastAPI + LangGraph:把画布 JSON 编译成 StateGraph,15 种节点
(模型/Agent/多Agent、工具/沙箱、分支/循环/子图、记忆/检索/整形、
人工介入/结构校验)。选 LangGraph 是因为可视化编排真正难的是暂停、
恢复和回放,而 checkpointer + interrupt() 正好把这几件事做在了底层。
前端 React + React Flow:节点元数据驱动属性面板与连接桩,运行时
节点高亮、边流动画、卡片内流式 token 全部由同一份事件流驱动。
关键实现:
- Agent 内工具审批用 @task 缓存模型调用,interrupt 重放时不重复计费
- 沙箱保持只读根文件系统,代码经 exec+base64 写入(put_archive 会被
只读容器拒绝),断网、非 root、内存/CPU/超时限额
- 检索为向量与 BM25 混合,本地哈希 embedding 零配置可用
- 分支条件走 AST 白名单求值器;HTTP 工具逐 IP 拦截内网与元数据地址
- Mock provider 让无 API Key 也能跑通完整链路
7 个内置模板全部冒烟通过,含真实模型端到端验证。
|
2026-09-13 17:27:00 +08:00 |
|