Skip to content

feat: RunnerQ Cloud commands (cancel, retry, run now, reschedule, priority, delete, signal) - #16

Merged
alob-mtc merged 3 commits into
mainfrom
feat/commands
Sep 30, 2026
Merged

alob-mtc merged 3 commits into
mainfrom
feat/commands

Conversation

@alob-mtc

Copy link
Copy Markdown
Collaborator

RunnerQ Cloud commands for the TypeScript SDK, at parity with the Go SDK. With allowControl: true, a worker's agent advertises and serves the seven activities.* commands, so the console no longer shows it as read-only.

What's in it

  • Storage contract: CommandStorage / isCommandStorage and the command types, in runnerq/storage.
  • PostgresStorage.applyCommand: a port of Go's SQL covering cancel (optionally cascading to descendants), retry (optionally resetting attempts), run_now, reschedule, set_priority, delete and signal.
    • A command is idempotent by its id: the runnerq_commands ledger replays its result.
    • The same id with a different fingerprint is a conflict.
    • Problems with individual targets are reported per item and don't fail the command.
    • Dry run is supported.
    • The ledger is pruned the way Go prunes it (rows older than 7 days).
  • Schema: runnerq_commands, column for column Go's, including idx_runnerq_commands_created.
    • A fresh database gets it.
    • On an existing one, initialize creates the table with IF NOT EXISTS, under the advisory lock shared with Go.
    • verifySchema accepts a database without the table. Commands with an id then fail and ask you to run initialize.
  • Worker: a cancelled activity that is running stops.
    • At its next heartbeat, when it finds its claim gone. There is now a test for this.
    • At once, if the cancel came through this worker's own agent. This uses an internal method keyed by a symbol the package doesn't export.
    • Either way it counts as a lost claim, not a failure or retry.
    • No hot-path cost: running attempts are now a Map keyed by activity id instead of a Set.
  • Agent:
    • A new option, allowControl, default false, matching Go's AllowControl. Metadata-only mode doesn't affect commands, as in Go.
    • Capabilities are the same as Go's: {v: 1, targets}, with idempotency_key for signal only.
    • Also as in Go: the fingerprint, the queue-mismatch field error, ids from another store reported as not_found, and the per-item error codes with a status detail.
  • Retention: cancelled trees are swept under the failed-retention setting.

Differences from Go

  • Ids: only standard dashed UUIDs are accepted, the same choice the existing TS query handlers make.
  • Fingerprints: match Go byte for byte on test vectors, except for a number too large for a double (e.g. 1e400).
  • Timestamps: result and event created_at use the database clock, as TS's existing helpers do.

Behaviour notes

  • ActivityStatus gains "cancelled". It's an addition, but exhaustive switch statements in user code may need a new case.
  • allowReuseOnFailure now also reuses a cancelled activity's idempotency key, as Go does.
  • A stale attempt still running on the worker that reclaims the same activity is now aborted at once.

Tests

  • New test/commands.test.mjs: Go's command conformance cases ported.
  • New test/conductor-commands.test.mjs: capabilities, round trips and the immediate interrupt.
  • Worker test for a cancel found at heartbeat.
  • npm run build, typecheck and format:check pass. npm test passes against Postgres (104 tests).

Attempts are now tracked by activity id, so the agent can stop a
cancelled activity at once instead of at its next heartbeat. The
attempt aborts with claim_lost, as a heartbeat that finds its claim
gone does, and counts as a lost claim, not a failure or retry. The
method is keyed by a symbol the package doesn't export.

A reclaim of an activity still running here supersedes the stale
attempt, which no longer removes the new attempt's entries.
A port of runnerq-go's command contract and Postgres implementation:
cancel (with cascade), retry and redrive, run now, reschedule, set
priority, whole-tree delete and signal, on ids, a bounded filter or an
idempotency key. Commands are idempotent through the runnerq_commands
ledger, shared with Go: same table, advisory lock key, result JSON and
pruning (a week; replay is promised for 24 hours).

initialize creates the ledger in an existing database; connect accepts
a database without it and checks it when present. Retention now sweeps
cancelled trees and treats cancelled as terminal, through tree helpers
shared with the delete command. Ports Go's command conformance cases.
With AgentConfig.allowControl (off by default, as in Go) and a
CommandStorage, the agent advertises and serves the activities.*
commands: per-command field checks, the queue check, foreign ids as
skipped not_found, per-item error codes, conflict on a reused command
id, and Go's fingerprint (sha256 of the re-marshalled request). An
applied cancel interrupts the activity if it runs on this worker.
@alob-mtc
alob-mtc merged commit 836280a into main Sep 30, 2026
12 checks passed
@alob-mtc
alob-mtc deleted the feat/commands branch September 30, 2026 08:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant