@agentproto/routine

中文文档
0.4.1发布于 2026/11/01Apache-2.0TypeScriptES Module构建来源已验证近 30 天下载 6251

@agentproto/routine — AIP-41 ROUTINE.md reference implementation. A markdown + frontmatter format for declaring a recurring or event-driven invocation of an action, workflow, or tool. Decouples "when" (the schedule) from "what" (the target). Supports cron

原始使用文档
文档目录6 节
  1. Usage
  2. Runtime bridge (SPEC)
  3. Decisions
  4. /routines/* naming collision (resolved)
  5. Specs-repo follow-up (not applied here — separate repo, per…
  6. License

@agentproto/routine

AIP-41 ROUTINE.md reference implementation. A markdown + frontmatter format for declaring a recurring or event-driven invocation of an action, workflow, or tool. Decouples "when" (the schedule) from "what" (the target). Supports cron / interval / calendar / manual / event-driven schedules, with retry, jitter, catchup policy, identity attribution, and failure routing.

Status: 0.1.0-alpha. Generated by scripts/scaffold-aip.mjs — build() and validate() bodies are TODOs.

Spec: https://agentproto.sh/docs/aip-41

Usage

import { defineRoutine } from "@agentproto/routine"

const x = defineRoutine({
  id: "my-routine",
  description: "Short purpose.",
  // ...
})

Runtime bridge (SPEC)

defineRoutine/parseRoutineManifest above only validate a ROUTINE.md. Until this section, nothing in @agentproto/runtime read .routines/*/ ROUTINE.md and turned it into a live scheduled job — packages/worktree/ routines/worktree-gc/ROUTINE.md (shipped enabled:false in PR #626) was inert. This section is the design for the bridge that makes it real, scouted from source before writing any code:

  • routineFrontmatterSchema.target and .schedule were z.any() (packages/routine/src/schema.ts:16) — no validation at all.
  • The daemon's only live scheduling primitive is CronScheduler (packages/runtime/src/cron-scheduler.ts): a single 20s tick loop over jobs persisted at ~/.agentproto/cron-jobs.json, with action kinds command | agent | prompt-session (cron-scheduler.ts:50-72) and a proven agent-kind path (mirrors worktree-investigator-weekly in cron_list). cron_run (orchestration-tools.ts:1750) fires a job immediately, bypassing its schedule — the pattern routine_trigger mirrors.
  • There is no persistent, addressable "call any daemon tool by name" registry. McpServer (@modelcontextprotocol/sdk) is not a gateway singleton here — mcpServerFactory in packages/runtime/src/index.ts constructs a fresh McpServer and re-runs every registerXTools(...) pass per /mcp connection (index.ts:1211), so no long-lived server reference survives across cron ticks. Its registered tools live on the underscore-prefixed, undeclared _registeredTools map — reaching into it is already precedented in-tree, not invented here: packages/runtime/src/__tests__/tool-subset.test.ts:20-26 casts server as unknown as { _registeredTools?: ... } to read tool names back out for a unit test. The bridge reuses the exact same cast to call a handler, not just list one.

Decisions

  1. Declarative surface stays AIP-41 — no new manifest primitive.
  2. Target kinds — target.tool is the universal path (registrar → daemon tool by name, ANY tool). target.agent and target.workflow are sugar that lower to a tool call:
    • target.tool = { tool, inputs? } → dispatched as-is.
    • target.agent = { adapter, prompt, model?, cwd?, ...any other agent_start field } → dispatched as tool:"agent_start" with those fields as inputs, validated against agent_start's own input schema (packages/runtime/src/agent-start-schema.ts; wait is dropped). New target kind — TargetAgent does not exist in the current AIP-41 draft's target: TargetAction | TargetWorkflow | TargetTool union (packages/routine/src/types.ts:46). Flagged as a specs-repo follow-up below; this repo's schema is intentionally ahead of the draft.
    • target.workflow = { workflow: string | { file, ref, inline }, inputs? } (the existing AIP-41 shape) → workflow.file (or a bare string, treated as a file path) dispatches as tool:"workflow_run_file" with { path, input }. ref/inline sub-forms are not implemented — there is no "run a saved workflow by id" registry in this runtime (workflow_start takes an inline stages array, it doesn't resolve a saved definition) — reconcile()/trigger() report a clear unsupported-shape error for those rather than silently no-op'ing.
    • target.action (AIP-39 ACTION ref) is validated but not dispatched — no action resolver exists in this runtime. Same "clear error, not silent" treatment.
    • All three dispatched kinds fold to the same { tool, inputs } pair, so the registrar has exactly one execution path (routineTargetToToolCall), not three.
  3. Schema tightening — target moves from z.any() to a z.union of four object schemas (tool/agent/workflow/action — see schema.ts), .strict() except target.agent's inner object, which passes extra agent_start fields through to the runtime's validation. z.union rather than z.discriminatedUnion because the variants are told apart by which key is present (tool vs agent vs workflow vs action), not a shared literal discriminator field — adding one would itself be a spec change. schedule is deliberately left z.any(): only target was in-scope for tightening per the brief, and a ScheduleCron-only zod schema would reject the four other schedule kinds the type already documents. Instead, routine-registrar.ts runtime-guards schedule.kind === "cron" at the point of use for cron jobs and resolves schedule.kind === "event" to a reaction binding; the remaining kinds are reported as a clear "not yet supported" skip, not a validation failure at parse time.
  4. Dispatch mechanism — one shared dispatchTool, not four bespoke wrappers. createGateway (index.ts) builds ONE lazily-constructed, cached internal McpServer via the existing mcpServerFactory() (same registration pass a real /mcp connection gets — worktree_gc, agent_start, workflow_start, workflow_run_file, and every other daemon tool land on it), and a dispatchTool(name, inputs) closure that reaches into its _registeredTools[name].handler(inputs, {}) — same cast as tool-subset.test.ts, now load-bearing rather than test-only. Built lazily (first "tool"-kind cron/trigger fire, not at boot) so it adds no boot latency when no routine ever fires. This single function is handed to both CronScheduler (new kind:"tool" action, for real scheduled fires) and RoutineRegistrar.trigger() (for on-demand fires) — one dispatch implementation, two callers.
  5. Registrar (packages/runtime/src/routine-registrar.ts) — reconcile() scans <workspace>/.routines/*/ROUTINE.md, validates each via parseRoutineManifest, and for every enabled:true + schedule.kind:"cron" routine creates a CronScheduler job tagged label:"routine:<id>"; a routine that disappears or turns enabled:false has its tagged job deleted; a routine whose resolved {schedule, action} changed gets its old job deleted and a new one created (delete+recreate, CronScheduler has no update()). An enabled:true + schedule.kind:"event" routine is resolved to a reaction binding instead, and the reaction wiring reloads the runner's binding set on every pass. Called once at gateway boot; re-callable on demand (no file-watcher in this pass, noted as a follow-up, not silently dropped). Per-file parse/target errors are collected and skip only that file, not the whole scan. A target.workflow target is bound to that same workspace: a relative file resolves under it, the dispatched workflow_run_file call runs with cwd set to it, and input.repoRoot defaults to it unless the routine already sets one (an explicit repoRoot always wins). This keeps a workspace's routines from silently targeting the daemon's active workspace.
  6. routine_trigger (MCP tool, mirrors cron_run) + POST /routine-defs/:id/trigger (HTTP, mirrors POST /cron/:id/run) — re-parses the routine fresh, resolves its target to a tool call, and calls dispatchTool directly (not via a registered cron job): works even for a disabled routine or one reconcile() hasn't seen yet, and bypasses both the schedule AND the enabled flag, same as cron_run bypasses schedule for an existing job (cron-scheduler.ts:514-519 has no active check either).

/routines/* naming collision (resolved)

This bridge's manual-fire route lives at /routine-defs/:id/trigger, not /routines/:id/trigger — originally to avoid confusing an AIP-41 routine id with a RoutineRunner run id on the same /routines/* prefix. That collision is now moot: routine_start/routine_status/ routine_cancel/routine_escalation_resolve and their /routines/* run routes (the unrelated ad-hoc RoutineRunner primitive) were removed entirely (PLAN.md Phase B3). GET /routines now serves only this registrar's list(), and /routine-defs/* keeps its own prefix regardless.

Specs-repo follow-up (not applied here — separate repo, per brief)

  • Document target.agent ({ adapter, prompt, model?, cwd? }) as a first-class AIP-41 target kind in resources/aip-41/draft/ROUTINE.schema.json upstream — this repo's types.ts/schema.ts are ahead of the generated draft for this one shape.
  • Consider documenting schedule.kind values beyond cron as MAY-be-partial in v1 runtimes, since this bridge only implements cron (interval / calendar / manual / event are parsed-but-unscheduled here).

License

MIT — see LICENSE.

版本与发布记录

@agentproto/routine 共发布 14 个版本,首次发布于 2026/06/16;过去 12 个月发布了 14 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.4.1latest2026/11/014 天前
0.4.02026/10/279 天前
0.3.32026/10/2314 天前
0.3.22026/10/1918 天前
0.3.12026/09/301 个月前
0.3.02026/09/271 个月前

关于 @agentproto/routine 的常见问题

@agentproto/routine 是什么?

@agentproto/routine — AIP-41 ROUTINE.md reference implementation. A markdown + frontmatter format for declaring a recurring or event-driven invocation of an action, workflow, or tool. Decouples "when" (the schedule) from "what" (the target). Supports cron

如何安装 @agentproto/routine?

在项目目录运行 npm install @agentproto/routine,或使用 pnpm add / yarn add / bun add。

@agentproto/routine 支持 TypeScript 吗?

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

@agentproto/routine 支持 ESM 还是 CommonJS?

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

@agentproto/routine 可以商用吗?

@agentproto/routine 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@agentproto/routine 的下载量有多少?

根据 npm 官方下载统计,@agentproto/routine 在 2026/10/05 – 2026/11/03 这 30 天内被下载 6,251 次,日均约 208 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@agentproto/routine 还在维护吗?

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