Open source · MIT

contextq,
for agents everywhere.

contextq-server makes local contextq queues available over HTTPS — the same append-only journals, the same FIFO claims, the same item pop protocol. Reachable from CI pipelines, remote workers, and agents on any machine.

New to contextq? Learn the queue model first →

~/contextq-release — contextq-server
contextq-server remote-init -t production \ --apply --label my-laptop → checking remote architecture … linux/amd64 → uploading binaries via SCP … → converging systemd + caddy … ✓ initialized https://q.example.com contextq-server exec -t production \ queue create "Triage pull requests" --name triage ✓ queue created triage contextq-server exec -t production \ --json item pop triage { "key": "pr-4721", "state": "CLAIMED" }

The extension

The queue lives on one machine.
Your agents don't have to.

Local contextq coordinates agents sharing a filesystem. contextq-server adds a minimal network boundary for the cases where that isn't possible — CI runners, temporary workers, and agents on other machines that need the same FIFO protocol without direct filesystem access.

CI & automation

Push work from a webhook or script on any machine. Claim it from a CI runner. Agents in ephemeral environments participate in the same namespace without shared storage.

Remote workers

Temporary or disposable workers — cloud functions, spot instances, scheduled jobs — can pop and complete queue items over HTTPS without a persistent session or filesystem mount.

Cross-machine coordination

Coordinate work across multiple development machines or teams. One namespace, multiple labeled API keys — one per machine or service, each independently revocable without a restart.

What doesn't change

"contextq-server is a transport layer, not a new queue engine. The underlying queue remains ordinary contextq: append-only journals, filesystem locks, FIFO claims, and files an operator can inspect directly."

The item pop protocol, the lifecycle state machine, the agent-facing queue context — all of it is defined by contextq. contextq-server adds only the network and operational boundary.

How it works

A thin transport layer.

contextq-server doesn't implement a new queue engine. It validates a bearer key, allowlists the command arguments, executes contextq as a subprocess, and returns the result. The filesystem journals are the only state.

REMOTE AGENT

CI runner · remote worker · any machine

HTTPS · Bearer key

CADDY

TLS termination · reverse proxy

loopback 127.0.0.1:8787

CONTEXTQ-SERVER

validate key · allowlist args · exec subprocess

exec.CommandContext

CONTEXTQ CLI

filesystem journals · FIFO locks · JSONL events

/var/contextq/{namespace}/contextq/

No new state

There is no database, daemon queue implementation, or server-side cache. All queue state lives in contextq's events.jsonl files on the server's filesystem — the same files an operator can cat and inspect directly.

No shell execution

Remote arguments pass through an explicit allowlist before reaching exec.CommandContext. There is no shell interpolation. Server-controlled flags like --root and --json cannot be overridden by the caller.

TLS via Caddy

contextq-server binds to 127.0.0.1:8787 by default. Caddy handles TLS and reverse proxying. The bootstrap refuses to configure a public listen address.

Concurrency preserved

contextq's per-queue filesystem lock remains authoritative. item pop retains its atomic FIFO claim behavior even when multiple remote agents call the server concurrently.

Quickstart

Deploy in five steps.

The controller runs locally. It builds the Linux binaries, uploads them to your VPS over SSH, and bootstraps the service — systemd unit, Caddy config, namespace, and key included.

Prerequisites: a Linux VPS with root SSH access, Caddy installed and running, and a DNS record pointing at it. The bootstrap has been exercised on standard systemd distributions (Debian, Ubuntu, Arch).

01

Build

Build a local controller binary and a Linux deployment bundle matching your VPS architecture. Requires Go 1.24+.

build
git clone https://github.com/norlinga/contextq-server make release TARGET_GOARCH=amd64 dist/contextq-server (controller) dist/linux-amd64/contextq-server (server binary) dist/linux-amd64/contextq (contextq runtime)
02

Add a target

Save the public URL, namespace, and SSH destination locally. The target file (~/.contextq-server) is mode 0600 because it stores the API key.

target add
dist/contextq-server target add \ --url https://q.example.com \ --namespace personal \ --ssh-host example.com \ --ssh-user root \ --use production ✓ target saved production url https://q.example.com namespace personal ssh-host example.com
03

Bootstrap the server

Upload the binaries, converge the service identity, systemd unit, and Caddy config, issue an API key, and store it in the local target file.

remote-init
dist/contextq-server remote-init \ -t production --apply \ --label my-laptop → checking remote architecture … linux/amd64 → uploading binaries via SCP … → converging service identity … → converging systemd unit … → converging caddy snippet … → issuing namespace key … ✓ initialized label: my-laptop
04

Verify

Run the diagnostic check to confirm HTTPS, SSH, systemd, Caddy validity, and authenticated API access are all healthy.

doctor
dist/contextq-server doctor -t production https endpoint ✓ ok ssh ✓ ok systemd ✓ active (running) binaries ✓ present caddyfile ✓ valid namespace key ✓ authenticated
05

Execute contextq commands

Use exec to run any contextq queue or item command against the remote namespace. The full contextq vocabulary is available — create queues, push work, pop and complete items.

exec
contextq-server exec -t production queue create \ "Claim work only with item pop. Update every claimed item promptly." \ --name release ✓ queue created release contextq-server exec -t production item push release issue-123 contextq-server exec -t production item push release issue-124 contextq-server exec -t production --json item pop release { "key": "issue-123", "state": "CLAIMED" } contextq-server exec -t production item update release issue-123 DONE ✓ DONE

HTTP API

One endpoint. The full contextq vocabulary.

The HTTP surface is intentionally small: a health check and one command RPC. Responses are contextq's own JSON output — no server envelope.

Command RPC

POST /v1/{namespace}/exec
Authorization
Bearer <namespace-key>
Content-Type
application/json

Request body:

{"args": ["item", "pop", "release"]}

Exposed command families (body limited to 64 KiB and 64 args):

queue  create | list | read | destroy
item   push | pop | list | read | update | history

Health

GET /healthz

No authentication required. Returns {"status":"ok"} when the HTTP process is serving requests.

Status codes

200
contextq command succeeded
400
Invalid request, protected argument, or CLI validation error
401
Missing or invalid namespace key
404
Queue not found in this namespace
409
Coordination conflict — no available item, invalid state transition
502
contextq could not execute or returned invalid output
504
Command exceeded its request timeout

Expected failures preserve contextq's JSON shape: {"code":"no_available_items","error":"..."}

curl example
# read the namespace key from the local target file KEY="$(jq -r \ '.targets.production.key' \ "$HOME/.contextq-server")" curl -sS https://q.example.com/v1/personal/exec \ -H "Authorization: Bearer $KEY" \ -H 'Content-Type: application/json' \ --data '{"args":["item","pop","release"]}' { "key": "issue-123", "state": "CLAIMED", "item_id": "a14b9e02-…-01c9" }
error response (409)
# contextq coordination errors preserve the JSON shape curl -sS -w '\nHTTP %{http_code}' \ https://q.example.com/v1/personal/exec \ -H "Authorization: Bearer $KEY" \ -H 'Content-Type: application/json' \ --data '{"args":["item","pop","empty-queue"]}' { "code": "no_available_items", "error": "no available items" } HTTP 409

Looping agents: the server returns no_available_items immediately rather than holding the connection open. A looping agent should pop, work, update, then use bounded backoff before trying again.

Security model

Namespace-isolated.
Immediately revocable.

Each API key grants broad access to one namespace. Multiple labeled keys make it straightforward to add an agent — and later identify exactly which credential to revoke.

Namespace isolation

Each namespace has independent queue journals and API keys. A key from one namespace cannot authenticate to another. Authentication failures do not reveal whether a namespace exists.

Bearer keys

256-bit random secrets with immutable IDs and required labels. Only SHA-256 digests are stored remotely. Constant-time comparison prevents timing attacks. Revocation is effective immediately — no restart required.

TLS via Caddy

contextq-server binds to 127.0.0.1:8787 by default. Caddy handles TLS and certificate provisioning. The bootstrap refuses to configure a public listen address. Credentials are never forwarded on redirect.

Key management

# issue a key for a CI runner (don't overwrite local key) contextq-server remote key add -t production \ --label github-actions --no-save cqk_k_4d172bc1…_[secret printed once] # revoke by the key's immutable ID contextq-server remote key revoke \ -t production k_4d172bc156d1 ✓ revoked effective immediately, no restart needed

Deliberately absent

  • ·Per-command key scopes
  • ·Public namespace or key administration API
  • ·Long polling or WebSockets
  • ·Leases, heartbeats, or visibility timeouts
  • ·Multi-host consensus or failover
  • ·Web dashboard

These omissions keep the service inspectable and small. Add only when a concrete workflow requires it.