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. |
ACTIONQ_SCHEMA |
No | actionq |
Schema name for queue tables and events. |
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.
Initialize the queue schema:
export ACTIONQ_URL='postgresql://user:password@localhost:5432/app'
export ACTIONQ_SCHEMA='actionq'
actionctl migrateEnqueue 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 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. |
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:
export ACTIONQ_TEST_URL='postgresql://user:password@localhost:5432/testdb'
uv run pytest tests/test_integration_postgres.py -qIntegration tests create a fresh schema per test run.
- The queue schema is created lazily by
actionctl migrate. - 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.