Open source · MIT

Local queues
for agents.

contextq is a tiny local CLI for coordinating agent work: named queues with built-in context, FIFO keys, and a durable journal on disk. No daemon, no server — just files and a small binary. Built for narrow, agent-facing tooling that stays predictable and out of the context window.

go install github.com/norlinga/contextq/cmd/contextq@latest

~/projects/myrepo  —  contextq
contextq queue create "Triage stale GitHub issues" --name triage ✓ queue created 4f8b2c…c2e1 contextq item push triage issue-142 ✓ queued issue-142 contextq --json item pop triage { "key": "issue-142", "state": "CLAIMED", "item_id": "d5ce2a29-…01c9" } contextq item update triage issue-142 DONE ✓ DONE in 1.2s

The premise

Modern CLIs answer to two readers: humans and agents.

Once an agent is the one invoking your tool, the shape of its output, its error model, and the protocol it enforces all become first-class design surfaces. Modern command-line tooling has to consider agent-native workflows, not just human ergonomics.

Core thesis

"Good agent-facing tools are narrow, predictable, and deterministic. They give agents structured answers instead of ambiguous prose — keeping context windows focused, response times short, and behavior on a known path."

contextq is one small experiment in that direction: a CLI shaped for how an agent reads a queue, claims FIFO work, and reports outcomes — paired with a tightly-coupled Agent Skill the agent reads first.

Focused context

Structured answers, not free-form prose

A CLI returns one shape, every time. --json with stable error codes means the agent doesn't need to read paragraphs to know what happened — it branches on a field.

Predictable cost

Short, bounded tool calls

Agent-facing tools should be cheap to call and small in their output. A queue read is a few lines, not a Markdown wall the agent has to re-parse on every turn.

On the rails

Tools that enforce a protocol

contextq rejects races, duplicate work, and invalid state transitions at the CLI boundary — so the agent's plan can't drift into shapes the system was never built to handle.

What contextq is

A small, local coordination primitive.

contextq keeps project-specific named queues on disk to coordinate multi-agent work — or single-agent work split across time — without each turn rebuilding context from Markdown sprawl.

Named queues with context

Every queue carries a required block of guidance text written for the agent. The first thing any consumer does is read the queue and follow its instructions.

FIFO over string keys

Work items are bare string keys — file paths, issue IDs, task IDs. item pop is the only way to take work: it atomically claims the oldest available item (FIFO). There is no cherry-picking from item list or counts from queue read. A key can be re-enqueued for a fresh lifecycle.

Durable event journal

Everything is appended to a per-queue events.jsonl under ./contextq/. Inspect it with cat, recover state from disk, and trust the history.

Local-first

No daemon, no server, no network. Just files on disk under ./contextq/.

Inspectable

Open events.jsonl in any editor. Replay the journal to derive current state.

Concurrency-safe

Per-queue file lock serializes mutations. item pop runs under the lock — the sole dequeue/claim path, with no races.

Quickstart

Five commands and you're coordinating.

Install, create a queue with agent-facing context, push some work, take the next item with item pop, and complete it.

01

Install

Build from source. contextq is a single Go binary with no runtime dependencies.

install
# clone and build git clone https://github.com/norlinga/contextq cd contextq make install installed ~/.local/bin/contextq
02

Create a queue

The context string is required — it's the agent-facing brief every consumer reads first.

create
contextq queue create \ "Refactor these files carefully. Preserve public behavior and update tests." \ --name refactor ✓ queue created id 9f8b6d35-3d7d-46fb-8d4b-7d770a9718f1 name refactor
03

Push work

Items are stable string keys. File paths work well; so do issue IDs or task IDs.

push
contextq item push refactor internal/cli/root.go contextq item push refactor internal/fsstore/store.go contextq item push refactor internal/domain/queue.go contextq queue read refactor # counts are informational — FIFO order is enforced by pop, not by picking a key queue refactor items 3 available · 0 claimed · 0 done
04

Pop & complete

item pop is the only way to take work from the queue. It atomically claims the oldest available item under the queue's file lock — no races, no manual CLAIMED.

pop
contextq --json item pop refactor # CLAIMED in output means pop succeeded — never pass CLAIMED to update { "key": "internal/cli/root.go", "state": "CLAIMED", "item_id": "d5ce2a29-f818-4af4-a9ef-5bb7ab7f01c9" } # …agent does the work… contextq item update refactor internal/cli/root.go DONE ✓ DONE
05

Inspect history

Every transition is in the event journal. history shows every lifecycle for a key, including retries.

history
contextq item history refactor internal/cli/root.go 2026-05-16T21:00:00Z ITEM_PUSHED item d5ce2a29… 2026-05-16T21:04:11Z ITEM_CLAIMED item d5ce2a29… 2026-05-16T21:09:43Z ITEM_FAILED item d5ce2a29… 2026-05-16T21:12:08Z ITEM_PUSHED item a14b9e02… (retry) 2026-05-16T21:12:55Z ITEM_CLAIMED item a14b9e02… 2026-05-16T21:18:32Z ITEM_DONE item a14b9e02…

Core concepts

A small model, deliberately.

Three nouns, five states. Everything else falls out of those.

Noun · 1

Queue

A durable container with a required context string for agents. Optionally named (names don't have to be unique; UUIDs disambiguate).

Noun · 2

Key

A stable string identifier — file path, issue ID, anything meaningful. Unique per queue at any moment, but the same key can be re-enqueued for a fresh lifecycle.

Noun · 3

Item lifecycle

One pass through the state machine for one enqueue of a key. Each lifecycle gets its own item_id and its own history.

State machine

Five states, five legal transitions. AVAILABLECLAIMED happens only via item pop — you cannot set CLAIMED with item update. Terminal states are terminal — to retry, push the key again.

AVAILABLE CLAIMED DONE · FAILED · CANCELED
AVAILABLE CLAIMED DONE FAILED CANCELED pop update DONE update FAILED update CANCELED update CANCELED (before claim)

AVAILABLE · eligible to be claimed via item pop only

CLAIMED · owned by a consumer after a successful pop

DONE · completed successfully (terminal)

FAILED · attempted, didn't succeed (terminal)

CANCELED · intentionally abandoned (terminal)

FIFO over lifecycles, not keys

Order is determined by enqueue time of each item lifecycle, not by first appearance of a key. Re-pushing an old key puts a new lifecycle at the back of the queue.

No duplicate available work

A push is rejected (duplicate_available_key) if the key already has an AVAILABLE lifecycle. Once that lifecycle terminates, the key can be pushed again.

CLI reference

The whole surface, on one page.

Everything contextq can do in v1. Add --json to any command for machine-readable output and stable error codes.

Taking work: use item pop only. item list, item read, and queue read may show available keys or counts, but they do not reserve or claim work — another agent may pop between your inspection and your next command.

Q Queue commands

contextq queue list

List all queues, most-recently-modified first.

contextq queue create "<context>" --name <name>

Create a queue. The context string is required and immutable.

contextq queue read <queue>

Show context, counts, and last-modified info. Counts are informational only — refresh before item pop, not to pick a key.

contextq queue destroy <queue> -f

Hard delete the queue and its journal. Non-interactive; requires -f.

I Item commands

contextq item push <queue> <key>

Enqueue a key as a new AVAILABLE lifecycle.

contextq item pop <queue>

Only way to take work. Atomically pops the oldest available item (FIFO) under the queue lock. Returns CLAIMED — that state is not set via update.

contextq item list <queue> [--state S] [--key K] [--limit N]

Observation only — inspect backlog with optional filters. Do not use to choose work to claim; always item pop.

contextq item read <queue> <key>

Show the most recent lifecycle state and metadata for a key. For debugging or after pop — not a substitute for claiming.

contextq item update <queue> <key> DONE|FAILED|CANCELED

Transition the active lifecycle for a key to a terminal state. Only after you own the key via pop (CLAIMED is never an update target).

contextq item history <queue> <key>

Show every lifecycle and transition recorded for a key.

Global flags

--json
Emit structured output for parsing.
--root <path>
Override filesystem storage root.
--config <path>
Use a specific contextq.config file.

Stable error codes

duplicate_available_key
Key already has an AVAILABLE lifecycle.
no_available_items
Nothing to pop right now.
invalid_state_transition
Requested transition is disallowed.
no_active_lifecycle
Key has no non-terminal lifecycle to update.

Agent skill

Tightly coupled with the agent.

contextq ships with a canonical AGENT_SKILL.md — the contract every agent reads before invoking the CLI. The skill is the missing link between a generic LLM and a piece of project-aware tooling.

  • Take work only with item pop. Listing, reading keys, and queue counts are inspection only — never choose a key from item list or queue read and treat it as yours. CLAIMED is set by pop, not update.
  • Read the queue before acting. Queue context is task guidance.
  • Re-read immediately before pop. Other agents may have moved on.
  • Update as soon as the outcome is known. DONE, FAILED, or CANCELED.
  • Parse --json. Branch on the code field, not the prose.
Read the full skill
recommended agent loop
# 1. discover queues contextq queue list # 2. read context and follow it contextq queue read refactor # 3. (optional) inspect backlog — do not claim from this output contextq item list refactor --state AVAILABLE # 4. refresh context before pop (not to pick a key) contextq queue read refactor # 5. only way to take work — pop assigns CLAIMED contextq --json item pop refactor { "key": "internal/cli/root.go", "state": "CLAIMED" } # 6. …do the work… # 7. update the outcome contextq item update refactor internal/cli/root.go DONE

Why this matters: the skill plus the CLI plus the project-local contextq.config form a tight loop. The agent doesn't need to invent a coordination protocol every session — the tool teaches it the protocol on first read, including the rule that item pop is the sole path from available work to owned work.