@hy-sde-org dsh-graph-wakes
英语| 中文
总结
dsh-graph-wakes 是Agent Graph管理器唤醒路径的交付一半。 dsh-graph-control 已拥有持久尾流行(pending | running | waiting_permission | delivered | superseded | retryable_failed); 此包提供进程本地运行时,该运行时将适当的唤醒传递到其所属的根会话中 在下一个空闲边界处 -从不在一个回合运行时-并通过商店自己的开始/完成CAS持久地解决每个尝试。
运行时是有意解耦的。 GraphWakeRuntime 取一个存储缝(GraphControlStore 在结构上满足它) deliver 接收的钩子 { graphId, wakeId, rootSessionId, snapshotVersion } (主机接线重新驱动 AgentGraphCoordinator 并排队管理检查点(运行时从不导入协调器),这是可选的 onCompact(sessionId) 压缩钩子,以及一个可注入的空闲观察者。交付是由构造闲置控制的:唯一的交付入口点是 handleIdle(),注入的观察者向前移动 agent/status === 'idle' 它的边界——与Schedule包使用的状态观察缝相同——因此运行时不会自行启动交付。守灵可能会安排在未来 dueAt 被束缚着 recurMs 变成循环的工作(参见 定期和经常性的觉醒)。
这个包不提供任何工具、提示符或插件行——宿主程序集挂载运行时,并提供观察者和交付钩子。
目录表
使用这个包
import { GraphWakeRuntime } from '@hy-sde-org/dsh-graph-wakes'
const runtime = new GraphWakeRuntime({
store, // GraphControlStore (structural seam)
deliver: async ({ graphId, wakeId, rootSessionId, snapshotVersion }) => {
await coordinator.wake() // host wiring: re-drive the graph, enqueue the checkpoint
return { kind: 'delivered' }
},
onCompact: sessionId => ctx.compaction.compactIfNeeded({ session }, 'context-overflow', signal).then(() => {}),
observeIdle: onIdle => {
return ctx.on('agent/status', ({ agent, status }) => {
if (status === 'idle') onIdle(agent.id)
})
},
})
runtime.start('session-root') // scope: one root; observe every root when omitted
await runtime.handleIdle('session-root') // the delivery entry point (and test hook)
await runtime.stop() // unsubscribe, cancel timers, await the in-flight sweep
// Accessors
await runtime.pendingWakes('graph_g1') // pending + retryable wakes (terminal statuses excluded)
await runtime.wakeStatus('graph_wake_abc') // durable row, any status
import type { ScheduleAgentGraphSupervisorWakeRequest } from '@hy-sde-org/dsh-graph-control'
// A future or recurring wake: durable, exactly-once per wakeId.
const driftSweep: ScheduleAgentGraphSupervisorWakeRequest = {
graphId: 'graph_g1',
wakeId: 'graph_wake_drift_sweep', // chains as `${wakeId}#occurrence-1`, -2, …
snapshotVersion: 'rev-3',
rootSessionId: 'session-root',
dueAt: Date.now() + 60_000, // epoch ms; withheld until then (undefined = immediately due)
recurMs: 60_000, // optional: next occurrence scheduled after each delivery
}
// Hand `driftSweep` to the durable store's scheduleSupervisorWake.
理解实现
Idle-gated交付
交付只从 handleIdle(sessionId?). start(rootSessionId?) 注册注入的 observeIdle 观察者,它的生产布线返回 agent.ctx.on('agent/status', …) 谨慎, status === 'idle' 完全像时间表插件;观察者仅仅转发到 handleIdle. 空闲信号在转弯之间发射,所以转弯运行时不会产生尾流;交付钩子本身必须通过拥有代理的维护/空闲接缝(主机布线)运行尾流,镜像如何运行 ScheduleRuntime 声明空闲阶段 runMaintenance 之前 followup(). start 永远不会自己送达。观察者只报告活的根,因此枚举的范围自然局限于可以实际接收到一个回合的会话;重新武装的计时器只是一个重新驱动的提示,仍然可以进入 handleIdle.
交付尝试和持久状态机
对于每个到期唤醒,运行时调用存储的 beginSupervisorWakeAttempt 带有确定的尝试id (graphWakeAttemptId(wakeId, attemptIndex), turnId 相同的值):存储增量 attemptCount 并移动尾流 pending → running 在它的写链下原子地,一旦行被交付或取代就拒绝。然后,通过解决交付钩子的结果 completeSupervisorWakeAttempt:
| 交付结果 | 持久尝试状态 | 武装? |
|---|---|---|
delivered | delivered | 没有(除非 recurMs:计划下一次发生) |
waiting_permission | waiting_permission | 否(停至主机恢复) |
superseded / stopped | superseded | 否 |
retryable_failed | retryable_failed | 是的,除非筋疲力尽 |
重试、退出和终端失败
一、 retryable_failed 结果重新武装了守夜人 outcome.nextAttemptAt 或者 now + 30 s × attemptNumber (默认值: DEFAULT_RETRY_BACKOFF_MS, DEFAULT_MAX_DELIVERY_ATTEMPTS = 3); 重新手臂是进程本地和分段计时器重新驱动 handleIdle 为所属根。一次 attemptCount 到达 maxAttempts 运行时持久地耗尽尾流(exhausted(最后一个失败原因在尾流行):它离开未处理/可重试的列表,永远不会重新武装,并且在重启期间保持终端状态。
定期和经常性的觉醒
尾流行可以携带一个epoch-milliseconds dueAt:在此之前,扫描将保留交付,并且未来到期的尾流不会获得任何尝试行(dueAt === undefined 保留历史上立即到期的行为)。调度是一个存储级的只发生一次的中断 store.scheduleSupervisorWake 键控的 wakeId -因此重试调度程序或重放重启不能重写已声明的行。两个 dueAt 和 recurMs 是持久的尾流行字段。
守夜 recurMs 链条:发货后(仅限 delivered (可重试的、被取代的或停止的结果永远不会重复出现),运行时在确定性id下调度下一个事件 ${baseWakeId}#occurrence-${n} (occurrence 0是预定的基本id;该id不嵌入任何其他内容) dueAt = delivery time + recurMs,同样的分段重臂定时器重新驱动 handleIdle 为新的到期时间。停止抑制仍然适用:已完成、已停止或已关闭的图不会安排下一次发生,而在停止之前已经安排的事件将像任何应有的唤醒一样被取代。
Context-overflow复苏
当快递返回时 retryable_failed 与 overflow: true,运行时调用 onCompact(rootSessionId) 每个尾流最多一次,然后立即为一个有界的部分交付重新武装(交付钩子决定部分形状并报告它) partialResult: true). 如果局部尝试本身溢出,或者在压缩之后到达溢出而没有声明局部,则运行时拒绝第三个相同的完整交付:尾流终止(exhausted 恢复状态)。没有 onCompact 连线,溢出的尾流在第一次尝试后终止。尝试行没有 partialResult 列,因此这些标记保持进程本地(请参阅限制)。
停止镇压
应有的尾流被取代 没有交付 当图的调度日志显示图已停止或关闭时:任何带有 finish (图闭合-协调器的 isClosed 权限),或日志停止的权限 targetId 是根会话或图形id。后者是图形级停止约定:一个会话拥有一个图形,因此图形停止被记录为针对根标识的停止,而工作项停止(工作id)永远不会取消唤醒。运行时通过在每次扫描时折叠持久日志而不是信任进程状态来镜像Schedule包的停止-取消-到期记录行为。
单次飞行和幂等
重叠的空闲信号合并成一个串行扫描(单次飞行,在结算期间信号着陆时重新请求),存储CAS在第一次交付时进行一次尝试:第二次运行时或重试扫描,开始相同的尝试id观察 acquired: false 却什么也没提供。存储故障通过 onError 离开尝试区 running 用于主机恢复;一个投掷的钩子固定为 retryable_failed 带着钩子的信息。
重启耐久性
运行时所需的所有状态都在存储中:一个新鲜的 GraphWakeRuntime 在同一商店看到相同的尾流行-包括他们的 dueAt/recurMs 时间表-重新武装孤儿 retryable_failed 在下一个空闲时间醒来(像玛卡一样) recover),并持久地耗尽任何at-cap可重试的行,而不是使其不稳定。后退时间戳、溢出标记和重新武装计时器本身是进程本地的,并且故意不持久化。
进一步的探索
@hy-sde-org/dsh-graph-control:持久尾流行和此运行时设置的开始/完成CAS。@hy-sde-org/dsh-graph-stream:AgentGraphCoordinator,由主机重新驱动送钩。@deepseek-ai/dsh-schedule-观测模式(agent/status === 'idle'+agent.whenIdle)这个运行时镜像。- 马卡参考:
agent-graph-supervisor-wake.ts(权威尾流语义)。 tests/graph-wakes.spec.ts:真正的sqlite支持的商店,带有一个假的空闲观察者和交付钩子。
模型经验
没有面向模型的表面。这个包是主机端机器;主管工具 dsh-tool-graph 模型看到的是什么,主机接线是什么 deliver 转到模型可见的检查点。
已知的限制和延迟的工作
- 尝试行没有
partialResult(或溢出)列:one-compact/one-partial标记是进程本地的。重新启动将清除它们,因此可以在重新启动之前再进行一次完整的尝试attemptCount封顶停止重试;超过上限仍然是不可能的。 - 一次又一次的守夜之间的碰撞
scheduleSupervisorWakeWrite结束递归链(交付的事件保持交付;稍后不会重新派生链)。需要保证循环的主机自己重新驱动调度。 - 在尝试上限时,运行时将持久地耗尽尾流(
exhausted); 最后一个可重试的完成和耗尽调用之间的崩溃离开行retryable_failed在封顶处,下一个空转扫将其排出。 running这里不恢复唤醒(开始和完成之间的崩溃):中断的尝试是否真正完成是一个运行时事实,而控制存储的recoverSupervisorWakesNo-op将事实与主机连线保持一致。waiting_permission守灵的人停在那里,再也没有尝试过;权限响应恢复(Maka’s)notifyPermissionResponse)被延迟到主机布线。- 跨进程协调不在范围之内:与协调器一样,运行时是进程本地的,因此持有相同存储的另一个进程不会唤醒此运行时。
- 图级停止约定(使用日志停止)
targetId等于根/图id)在这里定义;工作项停止不会抑制唤醒。dsh-tool-graph必须提交图形停止与根身份的抑制参与。