@hy-sde-org/dsh-graph-wakes

中文文档
0.2.1-rc.5发布于 2026/11/01MITTypeScriptES Module零依赖

Agent Graph supervisor wake delivery: idle-gated host runtime over the durable wake rows.

原始使用文档
文档目录15 节
  1. Summary
  2. Table of Contents
  3. Use this package
  4. Understand the implementation
  5. Idle-gated delivery
  6. Delivery attempts and the durable state machine
  7. Retries, backoff, and terminal failure
  8. Scheduled and recurring wakes
  9. Context-overflow recovery
  10. Stop suppression
  11. Single-flight and idempotency
  12. Restart durability
  13. Further Exploration
  14. Model Experience
  15. Known Limitations and Deferred Work

@hy-sde-org/dsh-graph-wakes

English | 中文

Summary

dsh-graph-wakes is the delivery half of the Agent Graph supervisor wake path. dsh-graph-control already owns the durable wake rows (pending | running | waiting_permission | delivered | superseded | retryable_failed); this package supplies the process-local runtime that carries a due wake into its owning root session at the next idle boundary — never while a turn is running — and settles every attempt durably through the store's own begin/complete CAS.

The runtime is deliberately decoupled. GraphWakeRuntime takes a store seam (GraphControlStore satisfies it structurally), a deliver hook that receives { graphId, wakeId, rootSessionId, snapshotVersion } (host wiring re-drives the AgentGraphCoordinator and enqueues the supervisor checkpoint — the runtime never imports the coordinator), an optional onCompact(sessionId) compaction hook, and an injectable idle observer. Delivery is idle-gated by construction: the only delivery entry point is handleIdle(), and the injected observer forwards agent/status === 'idle' boundaries to it — the same status-observation seam the Schedule package uses — so the runtime starts no delivery on its own. Wakes may be scheduled for a future dueAt and chained with recurMs into recurring jobs (see Scheduled and recurring wakes).

This package contributes no tool, prompt, or plugin row — the host assembly mounts the runtime and supplies the observer and deliver hook.

Table of Contents

Use this package

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.

Understand the implementation

Idle-gated delivery

Delivery runs only from handleIdle(sessionId?). start(rootSessionId?) registers the injected observeIdle observer, which production wiring backs with agent.ctx.on('agent/status', …) guarded to status === 'idle' exactly like the Schedule plugin; the observer merely forwards to handleIdle. Idle signals fire between turns, so a wake is never delivered while a turn is running; the deliver hook itself must run the wake through the owning agent's maintenance/idle seam (host wiring), mirroring how ScheduleRuntime claims the idle phase with runMaintenance before followup(). start never delivers on its own. The observer reports only live roots, so enumeration is naturally scoped to sessions that can actually receive a turn; the re-arm timer is only a re-drive hint and still enters through handleIdle.

Delivery attempts and the durable state machine

For every due wake the runtime calls the store's beginSupervisorWakeAttempt with a deterministic attempt id (graphWakeAttemptId(wakeId, attemptIndex), turnId same value): the store increments attemptCount and moves the wake pending → running atomically under its write chain, refusing once the row is delivered or superseded. The deliver hook result is then settled through completeSupervisorWakeAttempt:

deliver outcomedurable attempt statusre-armed?
delivereddeliveredno (unless recurMs: the next occurrence is scheduled)
waiting_permissionwaiting_permissionno (parked until host resumption)
superseded / stoppedsupersededno
retryable_failedretryable_failedyes, unless exhausted

Retries, backoff, and terminal failure

A retryable_failed outcome re-arms the wake at outcome.nextAttemptAt or now + 30 s × attemptNumber (defaults: DEFAULT_RETRY_BACKOFF_MS, DEFAULT_MAX_DELIVERY_ATTEMPTS = 3); the re-arm is process-local and a segmented timer re-drives handleIdle for the owning root. Once attemptCount reaches maxAttempts the runtime durably exhausts the wake (exhausted, with the last failure reason on the wake row): it leaves the unsettled/retryable listings, is never re-armed, and stays terminal across restarts.

Scheduled and recurring wakes

A wake row may carry an epoch-milliseconds dueAt: the sweep withholds delivery until that time, and a future-due wake gains no attempt row (dueAt === undefined keeps the historical immediately-due behavior). Scheduling is a store-level exactly-once upsert — store.scheduleSupervisorWake keyed by wakeId — so a retried scheduler or a replayed restart cannot rewrite a claimed row. Both dueAt and recurMs are durable wake-row fields.

A wake with recurMs chains: after a delivery (only delivered — a retryable, superseded, or stopped outcome never recurs), the runtime schedules the next occurrence under the deterministic id ${baseWakeId}#occurrence-${n} (occurrence 0 is the base id as scheduled; the id embeds nothing else) with dueAt = delivery time + recurMs, and the same segmented re-arm timer re-drives handleIdle for the new due time. Stop suppression still applies: a graph that is finished, stopped, or closed schedules no next occurrence, and an occurrence already scheduled before a stop is superseded like any due wake.

Context-overflow recovery

When a delivery returns retryable_failed with overflow: true, the runtime calls onCompact(rootSessionId) at most once per wake, then re-arms immediately for one bounded partial delivery (the deliver hook decides the partial shape and reports it with partialResult: true). If the partial attempt itself overflows, or the overflow arrives after the compact without declaring a partial, the runtime refuses a third identical full delivery: the wake is terminal (exhausted recovery state). With no onCompact wired, an overflowing wake terminates after the first attempt. The attempt row has no partialResult column, so these markers stay process-local (see limitations).

Stop suppression

A due wake is superseded without delivery when the graph's schedule log says the graph is stopped or closed: any update with a finish (graph closed — the coordinator's isClosed authority), or a log stop whose targetId is the root session or the graph id. The latter is the graph-level stop convention: one session owns one graph, so a graph stop is recorded as a stop targeting the root identity, while work-item stops (work ids) never cancel wakes. The runtime mirrors the Schedule package's stop-cancels-due-record behavior by folding the durable log at every sweep instead of trusting process state.

Single-flight and idempotency

Overlapping idle signals coalesce into one serial sweep (single-flight, re-requested when a signal lands during settlement), and the store CAS makes one attempt row one delivery: a second runtime or a retried sweep that begins the same attempt id observes acquired: false and delivers nothing. Store failures surface through onError and leave the attempt row running for host recovery; a throwing deliver hook settles as retryable_failed with the hook's message.

Restart durability

All state the runtime needs is in the store: a fresh GraphWakeRuntime over the same store sees the same wake rows — including their dueAt/recurMs schedule — re-arms orphaned retryable_failed wakes at the next idle (like Maka's recover), and durably exhausts any at-cap retryable row rather than leaving it unsettled. Backoff timestamps, overflow markers, and the re-arm timer itself are process-local and are deliberately not persisted.

Further Exploration

Model Experience

No model-facing surface. This package is host-side machinery; the supervisor tools in dsh-tool-graph are what the model sees, and the host wiring turns deliver into the model-visible checkpoint turn.

Known Limitations and Deferred Work

  • The attempt row has no partialResult (or overflow) column: the one-compact/one-partial markers are process-local. A restart clears them, so one more full attempt can occur before the attemptCount cap stops retries; exceeding the cap is still impossible.
  • A crash between a recurring wake's delivery and the next scheduleSupervisorWake write ends the recurrence chain (the delivered occurrence stays delivered; the chain is not re-derived later). Hosts that need guaranteed recurrence re-drive the scheduling themselves.
  • At the attempt cap the runtime exhausts the wake durably (exhausted); a crash between the last retryable completion and the exhaust call leaves the row retryable_failed at the cap, and the next idle sweep exhausts it.
  • running wakes (crash between begin and complete) are not recovered here: whether an interrupted attempt really completed is a runtime fact, and the control store's recoverSupervisorWakes no-op keeps that fact with the host wiring.
  • waiting_permission wakes are parked and never re-attempted; permission-response resumption (Maka's notifyPermissionResponse) is deferred to the host wiring.
  • Cross-process coordination is out of scope: like the coordinator, the runtime is process-local, so another process holding the same store does not wake this runtime.
  • The graph-level stop convention (log stop with targetId equal to the root/graph id) is defined here; a work-item stop never suppresses wakes. dsh-tool-graph must commit graph stops with the root identity for suppression to engage.

版本与发布记录

@hy-sde-org/dsh-graph-wakes 共发布 9 个版本,首次发布于 2026/09/10;过去 12 个月发布了 9 个版本。

版本发布日期距今
0.2.1-rc.5latest2026/11/012 天前
0.2.1-rc.32026/10/259 天前
0.2.1-alpha.42026/10/1618 天前
0.2.1-alpha.22026/10/1321 天前
0.2.0-rc.22026/10/031 个月前
0.1.7-rc.22026/09/291 个月前

关于 @hy-sde-org/dsh-graph-wakes 的常见问题

@hy-sde-org/dsh-graph-wakes 是什么?

代理图管理器唤醒交付:闲置门控主机运行时的持久唤醒行。

如何安装 @hy-sde-org/dsh-graph-wakes?

在项目目录运行 npm install @hy-sde-org/dsh-graph-wakes,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:@deepseek-ai/cordis(~4.0.5)、@hy-sde-org/dsh-graph-control(^0.2.1-rc.5)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@hy-sde-org/dsh-graph-wakes 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@hy-sde-org/dsh-graph-wakes 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@hy-sde-org/dsh-graph-wakes 可以商用吗?

@hy-sde-org/dsh-graph-wakes 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@hy-sde-org/dsh-graph-wakes 还在维护吗?

最新版本 0.2.1-rc.5 发布于 2026/11/01(2 天前);至今共发布 9 个版本,其中过去 12 个月发布了 9 个;首次发布于 2026/09/10。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。