@alma-harness/schedule

中文文档
0.41.0发布于 2026/11/04Apache-2.0TypeScriptES Module近 30 天下载 2004

Alma's clock: what is due, and the tick an external scheduler calls to run it — no process kept alive.

原始使用文档
文档目录9 节
  1. What it owns
  2. Wiring
  3. Claims and recovery
  4. What it must never do
  5. Financial warning policy
  6. Documentation
  7. Governed batch routines
  8. Governed conversational routines
  9. Canonical routine invocation

@alma-harness/schedule

The clock of Alma, as a tick: what is due, and the call an external scheduler makes to run it. No process is kept alive (spec: clock-tick).

Status: pre-1.0. The API is still moving; see the roadmap for where it stands.

What it owns

  • createSchedule({ routines, runs, runner }) — tick({ now? }) reads the routine store, decides what is due, runs it through the routine runner with the FIRE's instant as the run id, and reports { at, due, runs, errors }. plan(now?) answers without running. runner may be a resolver, (routine) => RoutineRunner, for a deployment with an agent per tenant — list crosses every scope. A routine that cannot be planned (a zone typo, a bad cron) is one entry in errors; the others run.
  • register / cancel — register THROUGH the schedule: it asks the runner to validate and reads the schedule once, so what the tick could not run is refused at registration, not at 8h with nobody watching.
  • What is due: the latest fire at or before now — a cron's previous occurrence in its tz (UTC when absent), every anchored on the routine's registration, at once — when it is later than the routine's last run. A job routine whose submission is still open is collected on every tick. Missed fires are not replayed: after downtime a daily routine runs once.
  • latestFire, nextFire, validateSchedule — pure schedule calculations. nextFire(schedule, registeredAt, after) returns the first ISO occurrence strictly after the cursor and at or after registration, or undefined when exhausted. Intervals stay anchored on registration. Hosts may enqueue an external wake-up at that time, then revalidate current configuration and use native planning/claims on wake. This helper grants no execution authority; hosts must also arrange wake-ups for missed-fire recovery and pending native batch collection.
  • createGovernedConversationRoutineRunner runs a triggered conversation; createGovernedBatchRoutineRunner submits and collects background batches. Both implement RoutineRunner, claim before work, enforce the routine's narrowed cap and daily ceiling, and make at most one delivery attempt per retained fire. The legacy createRoutineRunner and RoutineRunnerConfig are removed (spec: remove-legacy-routine-runner).

Wiring

One endpoint the scheduler calls on a cadence. Cloud Scheduler every minute against a Cloud Run service at zero minimum instances is the shape it was written for; anything that can call a URL on a cron is the clock.

import { createSchedule } from "@alma-harness/schedule";

const schedule = createSchedule({ routines, runs, runner: (routine) => runnerFor(routine.scope) });
await schedule.register(briefing);

// Node's http, Hono, Express — the product's choice. A shared secret, or
// the platform's OIDC, decides who may tick.
app.post("/tick", async (req, res) => {
  if (req.headers["x-tick-secret"] !== process.env.TICK_SECRET) return res.status(401).end();
  res.json(await schedule.tick());
});

Claims and recovery

The governed runners require atomic RoutineRunStore.claim. A retained (org, uid, routineId, runId) has one execution owner across instances, including batch submission and collection. Pending tick plans carry collectRunId, which custom runner wrappers must forward: it collects only that existing run, never a new submission if the plan becomes stale. Losing callers return its current record; running can mean active work or an interrupted execution. A turn may take tens of seconds, so the host request timeout must allow it.

A claim is persisted before work starts. Before calling the sink, the runner also records the known session, turn, cost and delivery hash. If a process dies or persistence fails, running is never expired or automatically retried, even when an execution deadline passes. This deliberately favors avoiding duplicates over automatic recovery. Sink errors become failed and are also not automatically retried: an error may follow a successful external action.

Reconcile unresolved runs against the session, provider batch and destination before recording a final outcome through record. Age alone cannot prove that the original worker has stopped or that a delivery did not happen. Recording a final outcome does not resend that run. Any replacement action requires an explicit operator decision after reconciliation; do not delete claims to enable retries. External exactly-once delivery would require an idempotent destination and is not guaranteed here.

Text suppression compares only with the last completed delivery. It does not serialize different fire ids or enforce a global once-ever rule for equal text. Daily ceilings likewise do not reserve capacity atomically across different fire ids. Retention preserves running and submitted; removing completed records ends their replay protection. Use a durable store in production; the in-memory reference survives only its process.

Upgrade: drain old runners, run migrateRoutineRunStore to expand the outcome constraint (existing records are preserved), and deploy matching core, schedule and Postgres packages. Custom stores must implement the atomic claim contract and callers must accept running. Run the shared describeRoutineRunStoreContract against each custom backend. Do not mix old runners that write only after delivery with the new claim protocol.

What it must never do

  • Keep a timer. The clock is outside; this package only answers it.
  • Replay missed fires, or run routines in parallel.
  • Hold a vendor scheduler's SDK. The tick IS the adapter.

Financial warning policy

A routine can use budget: { perTurnUsd: { usd: 0.5, onExceeded: "warn" } }. Turn routines require a matching warn policy and an amount no greater than the host threshold (or no per-turn cap on the host); validation rejects widening before claiming a run. A trusted conversation factory composes the exact narrowed caps. Numeric routine caps still block.

Known crossings are persisted in RoutineRun.capsCrossed before delivery and forwarded to the sink. Both per-run and persistent batch warnings come from original governed receipts. A replay returns the metadata without another call, charge or delivery. Deduplicate host alerts by scope, run and cap. Frequency and duplicate guards still apply.

Run migrateRoutineRunStore before deploying these matching core, loop, schedule and Postgres packages; it adds caps_crossed idempotently to existing rows.

Documentation

Docs index · Architecture §8 · Routine runner spec

Apache-2.0

Governed batch routines

createGovernedBatchRoutineRunner implements RoutineRunner for job routines, so pass it directly to createSchedule. Configure a GovernedBatchConfig, host caps, runs, sinks, a synchronous metadata-only plan, runTimeoutMs, submissionDeadlineMs and onBackgroundError. The plan returns model/prices, policy/price/output versions, controls, consumers, resultRetentionMs and lazy loadRequest. The request must match the plan at batch tier without tools; its messages are replaced with the original routine goal. Context loads only after all governed reservations and audit. The routine per-run cap narrows host policy.

Keep batch.configRevision immutable and change it whenever plan, provider account, system/output policy or sink resolution changes. Pending collection requires the original revision and full routine definition. governedRoutineBatchKey(scope, routineId, runId) exposes correlation for accounting-only repair through the batch runner, including when delivery remains unresolved.

A scoped collection may repeat GET and repair finance. It never writes waiting metadata over a completed run. Only the permanent submitted-to-running claim winner may attempt delivery; interrupted submission or delivery is not retried. Retain unresolved records and reconcile externally. Read output again before egress to respect erasure; concurrent erasure during external delivery remains a host boundary. Warnings come from governed financial receipts and retain the answer. Closed failure reasons never contain raw provider or sink errors. Dedupe compares only the last successful text hash; duplicate replies retain their cost. Distinct fire ceilings remain non-atomic. Governed conversational routines are not part of this runner.

Governed conversational routines

createGovernedConversationRoutineRunner handles turn routines through a trusted synchronous conversation(caps) factory returning createConversationRunner with those exact narrowed caps. Supply matching policyVersion, configRevision, resultContractVersion, resultRetentionMs, maxCalls, rootDeadlineMs, runTimeoutMs, host caps, runs, sinks and onBackgroundError. Configuration revision covers the factory's provider account, prompts, prices, tools/profiles, result policy and sink mapping. The factory composes configuration only; it cannot read context or perform effects. Ordinary mutable data is copied by each runner; store, factory and sink implementations are trusted host capabilities.

The permanent routine claim precedes factory and conversation invocation. The conversation's admission/root/output reservations precede the lazy routine goal and all context/effects. An absent profile grants no unattended tools. Main and child costs come only from governed receipts; warnings accompany the single sink attempt. governedRoutineRootKey(scope, routineId, runId) exposes accounting correlation.

Uncertain conversations and late completions retain routine ownership for explicit reconciliation. Replaying a root after routine metadata loss does not grant delivery. Final content is rechecked after writing delivery metadata and immediately before sink access. Interrupted, failed or unacknowledged delivery never retries itself. Register all conversation result namespaces and sessions in erasure/retention; an external destination and concurrent egress still require host reconciliation.

The executable compositions live in the schedule tests on memory/PostgreSQL and in Prumo P1. Session/root IDs use governed hashes for correlation. Invalid routine data can throw synchronously before admission; operational errors expose fixed metadata and do not release uncertain work.

Canonical routine invocation

The conversational adapter now invokes ConversationRunner.runTurn with the existing explicit descriptor (spec: canonical-routine-adoption). The legacy run alias is not called. A canonical not_admitted/routing_unavailable response finishes the fire as refused, with zero cost and no conversation root. Its metadata replay does not retry that fire; repaired configuration can serve a new fire. Uncertain invocation, lost refusal acknowledgement and interrupted delivery still cannot grant new execution or sink authority.

Prumo P1 now composes both governed routine adapters over the canonical runtime's financial stores and spec 128's separate batch-result namespace. Its clock/inbox remain local fixtures; no process, real recipient or production scheduler is added.

版本与发布记录

@alma-harness/schedule 共发布 50 个版本,首次发布于 2026/09/04;过去 12 个月发布了 50 个版本。

版本发布日期距今
0.41.0latest2026/11/043 天前
0.40.02026/10/308 天前
0.39.22026/10/309 天前
0.39.12026/10/299 天前
0.39.02026/10/299 天前
0.38.02026/10/2910 天前

关于 @alma-harness/schedule 的常见问题

@alma-harness/schedule 是什么?

Alma's clock: what is due, and the tick an external scheduler calls to run it — no process kept alive.

如何安装 @alma-harness/schedule?

在项目目录运行 npm install @alma-harness/schedule,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:@alma-harness/core(^0.41.0)、@alma-harness/loop(^0.41.0)、@alma-harness/batch(^0.41.0)、@alma-harness/execution(^0.41.0)、@alma-harness/conversation(^0.41.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@alma-harness/schedule 支持 TypeScript 吗?

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

@alma-harness/schedule 支持 ESM 还是 CommonJS?

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

@alma-harness/schedule 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@alma-harness/schedule 可以商用吗?

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

@alma-harness/schedule 的下载量有多少?

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

@alma-harness/schedule 还在维护吗?

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