@yoheinakajima:上周末继续开发 @activegraphai 参考包,结果发现需要对运行时进行加固:它已经……

X AI KOLs Timeline 工具

摘要

ActiveGraph 发布了七个运行时版本(1.2.0 到 1.7.1),用于加固其自主智能体的参考包,增加了安全的 fork-测试-推广循环,支持子进程隔离、清单哈希和完整的事件日志记录。

继续开发 @activegraphai 参考包的上周末工作导致需要对运行时进行加固: 它已经可以…… - 保留智能体所做的一切的完整历史记录 - 重放该历史记录 - 分叉到不同的时间线以尝试不同的想法 现在它可以…… - 意识到自己缺少某项能力 - 编写新代码以添加该能力 - 在自身的一个隔离副本中测试该代码 - 请求人类批准 - 安全地采纳该能力 - 继续运行,现在拥有新能力
查看原文
查看缓存全文

缓存时间: 2026/07/14 18:29

继续完善 @activegraphai 参考包,结果需要强化运行时:

它已经能…

  • 保存代理所做一切操作的完整历史
  • 重放该历史
  • 分叉出替代时间线以尝试不同想法

现在它还能…

  • 意识到自己缺少某项能力
  • 编写新代码来添加该能力
  • 在自身的隔离副本中测试
  • 请求人类批准
  • 安全地采纳
  • 持续使用新能力运行

ActiveGraph 1.2.0 到 1.7.1:强化参考包推动了运行时演进

来源:https://activegraph.ai/blog/activegraph-1-2-to-1-7-1

对两个代码仓库进行了一次协调性的推进:参考包为自主代理的使用而强化,而 ActiveGraph 运行时则在一天内发布了七个版本来支持它们。其主线是一个循环——分叉、在子进程中进行试验、所有者根据固定字节批准、静默提升、观察窗口——整个过程都端到端地记录在事件日志中。

在我们准备在 ActiveGraph 之上构建一个完整的自主代理时,我们希望首先强化参考包。这些包是能力层:内存、工具网关、身份、通道,以及让代理能够编写和采纳自身包的自我修改循环。强化该层意味着要对自主代理实际可能遇到的故障模式进行压力测试,而这种压力测试又不断暴露出底层运行时中的缺口。因此,这变成了两个仓库之间的协调推进:包得到强化,而运行时(PyPI 上的 activegraph)则在一天内发布了七个版本(1.2.0 到 1.7.1)来支持它们。

主线是一个循环。运行时已经有了 fork(at_event=...),它能将一个运行分支到一个携带父级历史的隔离子进程中,以及 rt.diff(fork),它能结构化地比较两者。但它缺少的是将分支的工作成果采纳回父级的方法,因此分叉-测试-提升的过程实际上是分叉-测试-然后手动重做。promote(1.3.0)填补了这个空白。它之后的所有内容,横跨七个版本,都是为了在这个循环由自主代理驱动时使其值得信赖而进行的安全、可移植性和溯源工作:一份清单和一个包哈希,使得批准可以固定到确切的字节(1.4.0);子进程隔离,使得候选者的首次执行发生在父进程之外(1.5.0);禁用和保留机制,使得采纳可以回滚,且其溯源不能被垃圾回收(1.4.0 至 1.6.0);以及沙箱可移植性修复,使得试验子进程可以在父进程能运行的任何地方运行(1.7.0, 1.7.1)。

promote 本身是定下基调的设计决策。它将分支的净状态增量作为普通的新事件应用于父级,因此父级的日志记录了实际发生在它身上的事情(“在这一点上,我从那个分支采纳了这个增量”),而不是将分支中从未执行的 llm.requestedtool.requested 对复制到父级的历史中。复制它们会伪造父级从未经历过的历史,而每次重放和审计原语都信任日志记录了那次运行实际发生的事情。冲突是实体级别的,并且失败即关闭。应用是静默的:增量事件不会重新触发行为,因为分支已经运行了增量所暗示的每一个级联,因此父级只对单个 promote.applied 标记做出一次反应。

这篇文章首先详细说明了哪些变化,然后用简单的语言解释了这些变化所启用的内容,最后介绍了我们是如何构建的以及学到的经验。

从顶层看是什么样子

这些包获得了一个受管控的循环:代理可以起草一项新能力,在隔离环境中针对自身历史进行试验,并且只有在所有者批准了确切审查过的字节后才能采纳,每一步都记录为可重放的事件。能力差距变成了一个提案,该提案通过静态门控,然后在子进程中进行分支试验,接着是强制性的身份验证所有者批准,然后是静默提升,最后是一个观察窗口。这些阶段都不需要信任模型是安全的。静态门控决定草稿是否格式良好。无法访问父级的子进程决定它是否能干净运行。经过验证的人类根据哈希固定的字节决定是否采纳。安全性在于结构本身,这使得当模型错误或输入具有敌意时,它仍然有效。

详细变化

运行时

Promote 的引用完整性。 除了上述的状态应用和静默应用决策外,冲突检查是双向执行的。提升的关系永远不能指向在提升后父级中不存在的对象,而提升的删除也永远不能通过级联来静默删除父级自己的工作。整个计划在任何突变之前计算完成,因此一个冲突会导致整个提升失败,并且不应用任何内容。没有部分提升,也没有 force 标志:对于冲突采纳的逃生舱是从父级的当前顶端重新分叉并重新试验,这针对父级的实际当前状态重放更改,而不是假装合并是安全的。

清单和包哈希。 包获得了机器可读的清单:身份、依赖项、声明的能力表面以及固定字节的内容哈希。运行时拥有验证器,因此加载器、CI 和自我修改门控都针对同一个实现进行检查。审查期间出现了一个微妙的漏洞:内容哈希排除了 manifest.toml(哈希不能覆盖自身),这意味着攻击者可以只交换清单(重新标记风险类别、清空出站访问列表、翻转作者标志),而内容哈希仍然匹配。修复方法是使用覆盖清单的包哈希。外部固定使用包哈希;清单的内部哈希保持自排除。所有者审查的文档在批准后不能再被交换。

禁用和保留。 disable_pack 从活动注册表中删除包的行为、工具和验证器,因此之后不会触发任何内容,它会发出一个 pack.disabled 事件,保持状态不变,并且是幂等的。真正卸载已导入的 Python 代码是无法诚实实现的,所以我们这样说:禁用会立即停止触发,重启会将代码从内存中驱逐。保留机制增加了快照和归档层,它们永远不会删除,其固定集主导保留策略:任何活动 promote.applied 标记引用的分叉都被整体固定,因此采纳背后的溯源不能被垃圾回收掉。

子进程试验隔离。 候选代码的试验现在在一个全新的子解释器中运行,该解释器由包哈希固定的工件物化,在三个独立的网络(地址空间限制、挂钟杀死、事件预算)下运行。子进程是清单链的第一个端到端消费者:包哈希验证,然后加载清单,然后进行双向表面检查,全部在任何导入之前。我们在文档中对边界坦诚:这是崩溃和状态隔离,而不是安全沙箱。系统调用和网络限制仍然是主机领域。

提供者、DX 和可诊断性。 在提供者线路边界处进行工具名称清理(带点的包作用域名称破坏了两个提供者的函数调用);错误分类使得被撤销的密钥是终端性的,而不是作为网络故障重试;按系列模型参数处理;所有装饰器的注册时签名验证;以及跟踪表面上的事件和失败访问器,使包开发不再是考古学。后来,沙箱停止丢弃试验子进程的 stderr,因此崩溃的子进程在报告之前携带其真实错误,而不是一个不透明的“崩溃“。

自我修改循环。 能力差距变成了一个提案,该提案通过静态门控(清单有效性、哈希完整性、声明与实际表面、导入允许列表、禁止构造、保留命名空间、大小限制和注入扫描),然后是一个采用样本内和保留集纪律的分支试验(借鉴自我们的评估工作),然后是强制性的身份验证所有者批准(采纳拒绝在未验证身份的部署中注册),然后是静默提升,最后是一个观察窗口。每个阶段都是图状态。这个循环必须赢得的差异化主张是带有溯源的自修改:试验是可以区分的分支,采纳是可以追溯的标记,回滚是可以重建的状态。

基于来源的上下文隔离(针对作者)。 风险最高的组件是读取差距并使用模型起草代码的部分,因为这是提示注入与代码生成相遇的地方。设计通过根据来源而非内容对作者的上下文进行分类来封闭这一点。作者框架由包代码、四个固定部分(哈希固定的章程、仅结构化差距字段、来自真相源的目标表面以及验证过的所有者文本)以及没有其他内容组装而成。内存、个人资料目标、工具输出、网络文本、未经验证的发送者消息以及先前提案的理由被永久排除,因为排除不需要正确性证明,而中和需要。作者在起草期间持有零工具,因为每个工具调用都是框架排除的文本的通道。起草记录在模型运行之前被密封,并且在提交时根据已接受的 ID 重新计算污点,因此没有模型输出可以洗掉标志。一个测试装置在图中植入了有毒的内存、个人资料、工具输出和先前理由,并证明它们都没有到达框架。

参考层所需的一切其他内容。 双向 MCP,使代理可以在网关下使用外部工具,并在相同的信任层下作为服务器被使用;具有可插拔后端的混合内存检索;凭据解析器下的托管认证;能力目录,使发现取代了硬编码的允许列表;以及一个决策表面,呈现提案(作者读了什么和写了什么,带有完整差异、门控裁决、试验证据和任何注入标志),因此批准代码意味着阅读它。

这启用了什么

所有这一切的重点是一个基础,自主代理可以在不需要任何步骤信任模型的情况下扩展自己的能力。它可以提出更改,但只有静态门控决定更改是否格式良好。它可以运行更改,但仅在无法访问父级的子进程中。它可以请求采纳,但只有经过验证的人类才能解析保留,并且只能针对人类审查过的确切字节。而且由于运行时是事件源的,整个历史(差距、草稿、门控裁决、试验、批准、采纳)是一个可以在事后重放和审计的日志。安全性在于结构本身,这使得当模型错误或输入具有敌意时,它仍然有效。

我们是如何构建的,以及学到的经验

我们并行运行了两个构建会话,每个仓库一个,在它们之间传递信使消息,所有七个运行时版本都手动标记和发布。一些事情使其有效,一些事情教会了我们。

先设计后代码,先审查后构建。 两个最难的工件,提升语义和 LLM 作者,每个都有书面设计并在实现之前进行独立审查。提升的设计在构建之前修改了三次(静默应用、引用完整性冲突、计划与应用重新计算),每次修改在纸上都很便宜,但在代码中会很昂贵。作者设计的审查产生了四个必需的更改,每一个都将断言的信任边界转变为带有测试装置的强制边界。两个设计的明确目标是让构建变得枯燥。这是一个好目标。枯燥的构建是能够正确交付的构建。

一个实现,多个消费者。 内容哈希和包哈希由加载器、CI 和自我修改门控重新计算。不是三个实现逐渐偏离,而是运行时拥有一个,其他导入它。一旦你在三个地方强制执行相同的规则,就把它放在一个地方。

诚实的边界胜过方便的边界。 沙箱是崩溃隔离,而不是安全沙箱,文档中这样说。在 macOS 上,试验的内存网络无法设置,因此它宣布关闭而不是伪造一个上限,因为静默不做任何事情的内存网络比说关闭的更糟。每次我们想要掩盖一个限制时,直说都是更好的选择,因为知道网络关闭的消费者可以据此计划,而认为网络打开但实际上关闭的消费者则不能。

耐力测试通过失败证明了其价值。 我们构建了一个耐力框架,每隔几分钟排练整个自我修改循环,并检查每个部分是否正常:采纳、冲突停放、重启后禁用仍然有效、所有三个预算网络、注入拒绝。它变红了三次,每一次红色都是一个真正的问题,会在开发者自己的机器上击中代理,在第一次轮换的几分钟内就被发现,而不是在生产中。试验子进程无法在沙箱的最小环境下导入运行时,在所有非 CI 的机器上,不是一个平台。然后地址空间限制在 macOS 上导致子进程崩溃。然后一个预算场景在内存网络诚实地变为平台条件后断言了一个仅限 Linux 的结果。这些都不是回归。每一个都是前一个清除后出现的下一个真实问题,这正是耐力测试的用途。我们不断重新学习的教训:在你信任系统之前框架发现的失败是你将得到的最便宜的失败。

修复失败指向的问题,而不是症状。 当一个预算场景在 macOS 上出现问题时,便宜的修复是跳过它。那将停止测试代理运行平台上的失控内存容器。诚实的修复使场景通过保护平台的任何网络(Linux 上的地址空间限制,macOS 上的挂钟杀死)来测试实际重要的不变性(失控被遏制)。红色标志是真实问题的指针。要回答问题本身。

运行时在这一切中保持领域中立。没有助手意见,没有产品特定逻辑。它是物理学:事件日志、图投影、行为、分叉、差异、提升,现在还有使其安全的沙箱和保留机制。意见存在于包中,产品位于包之上。保持这些层清晰是让两个仓库如此快速移动而不互相干扰的原因。

相似文章