Skip to main content

Quay

A local-first event ledger for coding agents. Append-only SQLite, a typed envelope with real state machines, a daemon that speaks a framed socket protocol, a CLI, and a live terminal cockpit. Python standard library only.

Quay is a competitor to Changi: it keeps the "one small ledger in your workspace" idea and fixes the parts that make Changi hard to run for long periods — quadratic status reads, no crash recovery story, no backpressure, no way to tell whether your history is intact, and no upgrade path.

  • Integrity you can check. Every event is SHA-256 chained. quay verify recomputes the whole chain and names the first divergence.
  • Status that does not get slower. Plane counts are maintained transactionally, so quay status is O(1) at 10 rows or 10 million.
  • A daemon that survives clients. Per-connection reader/writer threads, a bounded outbox, and a lagged frame that tells a slow subscriber exactly how much it missed.
  • Real state machines. Signals and work records follow explicit transitions, enforced on write, so the ledger cannot fill with impossible history.
  • An upgrade path. quay import reads an existing Changi ledger, maps it onto Quay's envelope, and reports every adjustment it made.

Status: complete and tested. 381 tests, no skips, no mocks of storage or transport. See COMPETITIVE_ANALYSIS.md for the requirements this implements and docs/ARCHITECTURE.md for the design.

Install

Quay runs on Linux and macOS. The recommended installer bootstraps an isolated Python runtime automatically, so it works on a fresh machine and never writes into an OS-managed Python installation.

curl --proto '=https' --tlsv1.2 -LsSf https://raw.githubusercontent.com/onicarps/quay/main/install.sh | bash

The installer verifies quay before it exits. It also adds its command directory to new interactive shells (bash, zsh, and fish). In the current shell, use the direct command the installer prints (normally ~/.local/bin/quay) if quay is not already on your PATH.

Via NPM (Global Terminal Binary)

If you already have Node.js 18+ and Python 3.11+:

npm install --global @agentbus/quay
quay --version
quayd --version

Via Pip (existing virtual environment)

python3 -m venv .venv
. .venv/bin/activate
python -m pip install oquay
quay --version
quayd --version

Do not use a bare system pip install on Ubuntu/Debian: current releases correctly protect their managed Python environment. Use the one-command installer above when you do not already have a virtual environment.

From Local Checkout

cd projects/quay
python3 -m pip install .        # or: pip install --user .
quay --version
quayd --version

To run from a checkout without installing:

export PYTHONPATH="$PWD/src"
python3 -m quay.cli --help

Quickstart

cd ~/my-project
quay                            # create .quay/, start the daemon, open the cockpit
quay init                       # explicit setup; only checks the Git advisory

quay emit build/log '{"text": "compiled ok", "tags": ["build"]}' --kind note
quay emit build/alert '{"summary": "disk almost full"}' --kind signal
quay log --limit 5
quay status

Point Quay at a folder and it does the setup itself: quay [PATH], bare quay, and quay monitor [PATH] create .quay/, start (or reuse) the daemon, append .quay/ to the enclosing worktree's .gitignore, and write a single genesis note. Nothing else changes outside that folder. quay init stays the explicit, preview-only path.

Payloads are validated per kind. A note carries text (plus optional tags), a signal carries summary and a state, a metric carries name and a numeric value, and generic accepts any JSON object. quay kinds prints the exact contract for every kind.

Start work, keep it alive, finish it with evidence:

quay work claim job-42 --executor builder --lease-seconds 120
quay work heartbeat job-42 --executor builder --lease-seconds 120
quay work complete job-42 --executor builder --evidence '{"artifact": "dist/app.tar.gz"}'
quay work list --state done

Watch the ledger live in the cockpit, or stream JSON lines:

quay watch
quay watch --plain --kind signal

Verify and search:

quay verify
quay query --topic 'build/*' --kind signal --limit 10
quay query --text "almost full" --count-only
quay query --kind work --state done --export csv

Everything prints JSON automatically when piped, so no flag is needed in a pipeline:

quay status | jq '.planes'

CLI reference

Common flags on every command: --workspace PATH (default: the current directory), --json, --no-json, --timeout SECONDS. Output is JSON when stdout is not a terminal; --no-json forces the human rendering.

Command Purpose
quay init [--git-ignore --yes] Create .quay/, report a Git advisory, optionally add the ignore line
quay start [--idle-seconds N] Start this workspace's daemon (0 disables idle shutdown)
quay stop [--all --yes] Stop this daemon, or every registered daemon
quay restart Stop and start
quay status Daemon health plus ledger and plane counts
quay daemons Every registered workspace daemon on this machine
quay doctor Install, socket, registry, and ledger integrity checks
quay kinds The kind contracts: states and required fields
quay verify [--no-record] Recompute the hash chain
quay emit TOPIC [PAYLOAD] [--kind K] [--producer P] [--causation SEQ] [--correlation ID] Append one event
quay work claim|heartbeat|complete|abandon KEY --executor E […] Drive the work plane
quay work list [--state S] [--executor E] / quay work reap Inspect work and expire lapsed leases
quay query [filters] Search with kind, topic glob, producer, state, correlation, causation, seq range, time range, and text filters; --count-only, --export jsonl|csv, --limit, --offset, --order
quay log [--limit N] [--follow] [--for S] [--max-events N] Recent events, optionally streaming
quay watch [PATH] [--kind K] [--plain] [--history N] [--for S] [--max-events N] Live TUI cockpit, or JSON lines with --plain
quay monitor [PATH] [--kind K] [--plain] [--history N] [--for S] [--max-events N] Alias for quay watch, and it auto-initializes the workspace
quay [PATH] Implicit cockpit: opens PATH (default: the current directory), auto-initializing it
quay mcp [--workspace PATH] Serve the workspace over a dependency-free stdio MCP bridge
quay prune (--keep-last N | --before TS) [--dry-run] [--yes] Delete an old prefix after a checkpoint
quay bench [--scale quick|full] [--out FILE] Run the benchmark suite
quay import SOURCE [--dry-run] [--max-warnings N] Import a Changi ledger (see docs/MIGRATION.md)

Exit codes: 0 success, 1 a check failed (for example verify), 2 usage or validation error, 3 the daemon is unreachable.

QUAY_PRODUCER sets the default producer for quay emit.

Terminal cockpit

quay watch (or quay monitor, or bare quay) renders a full-screen dashboard: daemon health, ledger counters, plane counts, a live event stream, and a filter bar. A fresh ledger shows an onboarding tip instead of the key hints. Keys:

Key Action
q quit
↑/↓, j/k move the selection
g/G jump to oldest / newest
space pause or resume the stream
f cycle the kind filter
1-5 jump to a kind filter
d toggle the detail pane
s inject a sample agent event
v run verification and show the result
b reap expired leases
? help overlay

The dashboard redraws with minimal ANSI diffs rather than repainting the whole screen, so it stays usable over a slow terminal. quay watch --plain prints JSON lines instead, which is what you want in a pipe or a log.

MCP bridge

quay mcp serves the workspace over stdio as a line-delimited JSON-RPC 2.0 MCP server, using only the Python standard library. It exposes quay_emit, quay_log, quay_status, quay_work_claim, and quay_work_complete, and writes through the same daemon and schema validation as the CLI:

{
  "mcpServers": {
    "quay": {"command": "quay", "args": ["mcp", "--workspace", "/path/to/workspace"]}
  }
}

Python API

from quay.client import QuayClient, SubscriptionStream
from quay.daemon import ensure_daemon

paths, _health = ensure_daemon(".")          # start a daemon if needed

with QuayClient(paths.socket) as client:
    event = client.request("emit", {
        "topic": "agent/build",
        "producer": "my-agent",
        "payload": {"step": "compile"},
        "kind": "note",
    })["event"]
    print(event["seq"], event["hash"][:12])

    client.request("work_claim", {"key": "job-7", "executor": "my-agent", "lease_seconds": 60})
    client.request("work_complete", {"key": "job-7", "executor": "my-agent",
                                     "evidence": {"exit_status": 0}})

Live subscription with backfill:

stream = SubscriptionStream(paths.socket, filters={"topic": "agent/*"}, replay=50)
stream.open()
for event in stream.events(timeout=5.0):
    print(event["seq"], event["topic"])

Using the store directly, with no daemon:

from quay.store import LedgerStore

with LedgerStore(".quay/ledger.db") as store:
    store.append(topic="t", producer="p", payload={"hello": "world"})
    print(store.status()["planes"])
    print(store.verify()["ok"])

Changi migration

quay import /path/to/.changi --dry-run     # report only
quay import /path/to/.changi               # import
quay verify

Changi's flat payload-with-extensions table is mapped onto Quay's typed envelope: signals and work receipts become plane records, everything else stays generic with its payload intact, and causal links are remapped to new sequence numbers. Where Changi stored a terminal record with no history, Quay writes a clearly marked reconstructed root rather than inventing a plausible past. Every adjustment lands in the report. Full details: docs/MIGRATION.md.

Benchmarks

quay bench --scale quick prints the workload profile with the numbers, so results are never quoted without their context. Two of the four benchmarks compare against a reimplemented baseline: the one-transaction-per-event write pattern Changi uses, and the full-table scan that a naive status implementation performs.

Reference machine: Linux 6.6 (WSL2), Python 3.12.3. Quick profile: 2,000 appends, 10,000 rows, 300 events fanned out to 4 subscribers.

Measurement Quay Baseline
Append throughput (batched, 500/txn) 3,008 events/s 893 events/s single-append
status() at 10,000 rows 0.24 ms 49.5 ms full-table scan
Query, topic glob / producer filter 2.7 ms / 2.7 ms –
Query, payload text 11.2 ms –
Push fan-out p50 / p99 0.0 ms / 8.4 ms –
Fan-out end-to-end p50 / p99 10.7 ms / 22.2 ms –
Fan-out deliveries 1,200 of 1,200, 0 dropped –

Reading these honestly:

  • status() cost does not grow with ledger size; the scan baseline does. The reported growth factor between the half-size and full-size runs is noise (the smaller run measured slower), not a trend.
  • The fan-out benchmark runs four concurrent collector threads, so its emit round trip (10.5 ms p50) is higher than an idle daemon's (~2.9 ms p50). That contention is part of what the number measures, and it is why the benchmark reports push latency and end-to-end latency separately.
  • The durable write dominates end-to-end latency. Batched writes amortize it.

Run the full profile with quay bench --scale full --out bench.json.

Demo

./run_demo.sh          # unattended end-to-end walkthrough, exits 0

The demo creates a scratch workspace in a temporary directory, starts a daemon, writes notes, signals, and work records, exports JSON and CSV, streams live events, runs verification, tampers with the ledger to prove verification fails, imports a synthetic Changi ledger, runs the benchmark suite, and cleans up. It prints each step and never touches your real workspace or registry.

Testing

python3 -m pip install pytest
python3 -m pytest -q              # 381 tests, ~100s
python3 -m pytest -q tests/test_store.py tests/test_server.py

The suite uses real SQLite files, real AF_UNIX sockets, real daemon processes, and real multi-process writes. Nothing mocks storage or transport. Coverage includes: canonical encoding and hash chaining, schema validation and state machines, tamper detection, crash and recovery, work leases and expiry, backpressure and lagged accounting, daemon lifecycle and stale-state reclaim, live TUI rendering and key handling, every CLI command, the benchmark suite, and Changi import fidelity.

Documentation

Document Contents
COMPETITIVE_ANALYSIS.md The target, the weaknesses addressed, requirements F1–F20 / N1–N7, and the milestone gates
docs/ARCHITECTURE.md Modules, data model, concurrency, durability, teardown ordering, benchmark method
docs/PROTOCOL.md Framing, frame shapes, every operation, filters, backpressure, error codes
docs/MIGRATION.md Changi import mapping, reconstructed roots, report format, limitations

Design limits

These are deliberate, and each one is documented rather than hidden:

  • One writer per workspace. SQLite gives one write transaction at a time. Batched writes make that a throughput question, not a correctness one.
  • No authentication. The socket is 0600 and local-only. Multi-user isolation is the filesystem's job, not Quay's.
  • No replication or federation. This is a local ledger, not a cluster.
  • Payload text search scans. Topic, producer, and sequence filters use indexes; --text reads payloads by design.
  • Imports are not deduplicated. Import into a fresh workspace, or expect skip warnings for plane records that already reached a terminal state.

Layout

projects/quay/
  pyproject.toml            packaging (hatchling, no runtime deps)
  README.md
  COMPETITIVE_ANALYSIS.md
  docs/                     architecture, protocol, migration
  run_demo.sh               unattended end-to-end demo
  src/quay/                 canonical, schemas, store, protocol, server,
                            client, daemon, workspace, cli, tui, bench, migrate
  tests/                    pytest suite (381 tests)

License

MIT. See LICENSE.

Metadata

Release files for oquay 0.2.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for oquay 0.2.1
File Size Uploaded
oquay-0.2.1.tar.gz 133.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for oquay 0.2.1
File Interpreter ABI Platform
oquay-0.2.1-py3-none-any.whl Python 3 none any Details

Total release size: 218.7 kB

Release files / oquay-0.2.1.tar.gz

Download URL oquay-0.2.1.tar.gz
Size 133.3 kB
Tags Source
SHA-256 checksum
How to use checksums
6f97fdaab474b1b284e979c0208c247ad400238204eb56e0f65d6c0469fccebd
BLAKE2b-256 checksum
How to use checksums
42fe4c4eb2eb3bd63b29fa4e3be50c459f7bdc087f294ac8544e1ace4e22e74d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.

Transparency log

Release files / oquay-0.2.1-py3-none-any.whl

Download URL oquay-0.2.1-py3-none-any.whl
Size 85.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
7433e177f2507a4c9d9144db37da09dab810881ca685745b8e185ddad18ddd6d
BLAKE2b-256 checksum
How to use checksums
970b2dcac9a07327f71a537a984a07c8c179f9ff912f4ff12c76a33b38245a16
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.

Transparency log

Release history Release notifications | RSS feed

0.2.2

2 release files

This release

0.2.1 This release

2 release files

0.2.0

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page