actionq is a Postgres-backed action queue for deterministic agent and operator dispatch. It gives you a small, explicit CLI contract for enqueuing work, claiming one item at a time, recording lifecycle transitions, and reading the queue event log without exposing direct SQL writes to consumers.
actionctl is the public contract. Consumers should not import the package directly or write to the database outside the queue interface.
- Stores actions in a Postgres schema.
- Supports a strict action lifecycle:
pending,claimed,completed,failed,rejected,cancelled. - Records append-only queue events for auditability and coordination.
- Enforces chain-depth limits for child actions.
- Applies per-source enqueue rate limiting for automated producers.
- Lets dispatchers emit coordinator events without broad database access.
uv syncuv tool install /path/to/actionquv sync --extra devactionq uses environment variables for connection and schema selection.
| Variable | Required | Default | Purpose |
|---|---|---|---|
ACTIONQ_URL |
Yes | None | Postgres connection string used by actionctl; deployment migration Jobs and runtime processes use separate role-specific values. |
ACTIONQ_SCHEMA |
No | actionq |
Schema name for queue tables and events. |
ACTIONQ_RUNTIME_ROLE |
Migration Job | None | Simple PostgreSQL role name that actionctl migrate grants queue DML, sequence use, and migration-ledger read access. It never grants schema CREATE or ledger writes. |
ACTIONQ_MAX_CHAIN_DEPTH |
No | 3 |
Maximum allowed parent-child depth for enqueued actions. |
ACTIONQ_RATE_LIMIT_PER_HOUR |
No | 20 |
Hourly enqueue cap for agent: and script: producers. |
ACTIONQ_TEST_URL |
Test-only | None | Separate Postgres URL used by integration tests. |
ACTIONQ_SCHEMA must be a simple Postgres identifier: letters, digits, and underscores, not starting with a digit.
ActionQApplication is the adapter-safe application core shared by
actionctl, the legacy Actionq HTTP façade, and the Vuoro execution adapter.
The owner-provided actionq.vuoro module publishes domain-qualified operation
metadata, JSON Schemas, handlers, and the execution compatibility record for
Vuoro composition. It does not add migrations or database access to the
transport-only Vuoro client.
Served mutation actors come from authenticated identity, require idempotency keys, and return durable accepted/rejected Actionq decision references. Runner effects remain machine-local; the service accepts session evidence and exposes queue/dispatch/session state. See the Vuoro execution adapter contract for the catalog and preserved claimant-proof limitation.
Initialize the queue schema with a deployment/migration identity:
export ACTIONQ_URL='postgresql://user:password@localhost:5432/app'
export ACTIONQ_SCHEMA='actionq'
export ACTIONQ_RUNTIME_ROLE='actionq_runtime'
actionctl migrateCheck the same schema with the runtime identity before starting service:
export ACTIONQ_URL='postgresql://actionq_runtime:password@localhost:5432/app'
actionctl check-compatibilityNormal commands and actionq-server fail closed when this check is not
compatible. They never apply migrations as a startup side effect.
Enqueue one action:
actionctl add \
--type scope-iterate \
--project sprintctl \
--target 42 \
--source doc:plan \
--created-by human:cliClaim the next pending action:
actionctl claim --worker worker:dispatcher-1Complete the claimed action:
actionctl complete 1 --result branch=agent/scope-iterate/1Inspect the action and its event history:
actionctl show 1The normal action flow is:
addinserts apendingaction and writesaction_enqueued.claimatomically marks the oldest highest-priority pending action asclaimedand writesaction_claimed.- A worker finishes with one of
complete,fail, orreject. - Operators may
cancelapendingorclaimedaction. sweeprequeues expired claims by clearing claim ownership and writingclaim_timed_out.
Priority is ascending, so smaller numbers are claimed first.
| Command | Purpose |
|---|---|
actionctl migrate |
Create or upgrade the queue schema. |
actionctl check-compatibility |
Report the read-only execution API/schema compatibility record. |
actionctl add |
Enqueue a new action. |
actionctl ls |
List actions with optional status, type, and project filters. |
actionctl show ACTION_ID |
Show one action plus all recorded events. |
actionctl claim --worker NAME |
Claim the next pending action. Exits with code 2 if none are available. |
actionctl complete ACTION_ID --result REF |
Mark a claimed action completed. |
actionctl fail ACTION_ID --reason TEXT |
Mark a claimed action failed. |
actionctl reject ACTION_ID --reason TEXT --validator NAME |
Reject a claimed action after validation. |
actionctl cancel ACTION_ID --reason TEXT |
Cancel a pending or claimed action. |
actionctl sweep |
Requeue timed-out claims. |
actionctl events |
Read the event log, optionally filtered or tailed. |
actionctl emit |
Emit coordinator events without direct SQL writes. |
actionq-daemon is the actionq-owned, long-running coordinator. It uses only
the public actionctl commands to claim work, emit session.* events, and
settle actions. The current minimum is deliberately single-session and supports
the fake runner for disposable verification; it does not import
actionq-dispatch at runtime.
Start from examples/actionq-daemon.toml, then:
mkdir -p ~/.config/actionq
cp examples/actionq-daemon.toml ~/.config/actionq/config.toml
actionq-daemon --config ~/.config/actionq/config.tomlFor user-systemd operation, install
ops/systemd/actionq-daemon.service, reload the user manager, and start the
unit. SIGTERM and SIGINT stop after a bounded child grace period; SIGHUP
reloads configuration between child sessions. The daemon checks the legacy
~/.config/actionq-dispatcher/config.toml only when no explicit --config is
provided, so existing operators retain a visible migration path.
All state-changing commands return JSON records that are designed to be machine-consumable.
The queue stores two tables inside the selected schema:
actions: the current state of each action.events: append-only lifecycle and coordination events.
Action records include:
- identity:
id,action_type,project,target_ref,source_refs - scheduling:
priority,status,claimed_by,claim_deadline - lineage:
parent_id,chain_depth,created_by - outcome:
result_ref,failure_reason,completed_at
This model is intentionally narrow: mutable action state lives in actions, while history and coordination signals live in events.
actionctl emit supports coordinator-level event types:
coordinator_cyclecoordinator_paused
The payload must be a JSON object.
Example:
actionctl emit \
--type coordinator_cycle \
--actor dispatcher:main \
--payload '{"claimed": false, "backlog": 3}'Run the unit test suite:
uv run pytest -qRun integration tests against a disposable Postgres database:
uv run pytest tests/test_integration_postgres.py -qThe PostgreSQL integration modules start one temporary cluster on a private
Unix socket and create distinct migration and runtime roles before test
collection. The harness requires local initdb and pg_ctl, creates a fresh
schema per test, refuses to silently skip live database coverage, and never
uses an ambient queue DSN.
- The queue schema is created only by the deployment-owned
actionctl migrateentrypoint. It uses a transaction-scoped advisory lock, a version ledger, packaged migration checksums, and idempotent retry behavior. - Runtime identities must not own schema objects, assume an owner role, or
receive schema
CREATEor migration-ledger write authority; see the migration and compatibility runbook. actionq-serverchecks compatibility before binding its socket. Its read-onlyGET /compatibilityendpoint publishes the same record for the Vuoro execution adapter.- Claims use
FOR UPDATE SKIP LOCKED, so multiple workers can contend safely. - Automated producers are rate limited when
created_bystarts withagent:orscript:. - Child actions cannot exceed the configured chain depth.
- Timestamps are emitted as UTC JSON strings.