Skip to main content

loopyard

loopyard is a PostgreSQL-backed task backend for coordinating multi-agent autonomous software development. All business logic — runnable-set computation, dependency and status-transition validation, conflict-domain checks, lock leases with generation fencing — lives in PL/pgSQL functions, constraints, and triggers, so state stays consistent no matter which client touches it. The CLI, MCP server, and REST API are thin transports over the same api.* SQL surface; the CLI also speaks vibe-loop's task-source and lock command contracts directly.

Quickstart

cp .env.example .env               # adjust credentials if you like
docker compose up -d               # PostgreSQL 18 on 127.0.0.1:5439
export LOOPYARD_DSN=postgresql://loopyard:loopyard@127.0.0.1:5439/loopyard
uv run loopyard db migrate         # apply migrations + function definitions

loopyard db reset --yes drops and recreates the public schema and re-migrates (development only — it destroys all data).

Authentication

The REST API has two modes (auth_mode in .loopyard.toml, or LOOPYARD_AUTH_MODE):

  • none (default) — no authentication; intended for localhost deployments. Mutating endpoints accept an optional X-Loopyard-Actor: <actor-id> header for event attribution.
  • oidc — every /api/* request must carry a JWT minted by an OIDC-style identity provider or authenticating proxy in front of loopyard. The token is validated offline against the provider's JWKS (RS256/ES256 only; issuer, audience and expiry checked), the identity is mapped to an auto-provisioned human actor, all mutations are attributed to that actor, and X-Loopyard-Actor is ignored. GET /api/whoami reports the resolved identity; identities listed in admin_identities may call admin endpoints (currently lock force-release — 403 for everyone else). The interactive docs and /openapi.json are disabled in this mode, and the SSE stream validates the token at connect time only (an already-open stream is not torn down when the token expires; reconnects re-validate).

Identities are namespaced with the claim they came from — email:alice@example.com, sub:7d5a… — so values from different claims can never collide; admin_identities entries use the same form. The email claim is trusted only when the token carries email_verified: true; an explicit email_verified: false is never accepted, and a missing email_verified counts as unverified unless oidc_allow_unverified_email is enabled (needed for authenticating proxies that assert the email themselves — see the Cloudflare preset). An unusable email falls back to sub.

Config keys (each also settable via the matching LOOPYARD_-prefixed upper-case env var; list-valued keys take a comma-separated string and booleans a true/false string in the env form). In oidc mode oidc_issuer and oidc_jwks_url must be https URLs — plain http is allowed for localhost only (dev):

key default meaning
auth_mode "none" none or oidc
oidc_issuer — required in oidc mode; exact iss match
oidc_audience — required in oidc mode; aud check
oidc_jwks_url discovered JWKS endpoint; default from <issuer>/.well-known/openid-configuration
oidc_token_sources ["bearer"] ordered lookup: bearer, header:<Name>, cookie:<name>
oidc_identity_claim "email" identity claim; sub is always the fallback
oidc_allow_unverified_email false accept email when email_verified is ABSENT (explicit false never passes)
oidc_http_timeout_ms 5000 JWKS/discovery HTTP fetch timeout
admin_identities [] namespaced identities allowed on admin endpoints, e.g. "email:you@example.com"

MCP and CLI transports connect to PostgreSQL directly and are unaffected.

Cloudflare Zero Trust Access preset

Access authenticates the user and forwards a JWT in a header and a cookie; loopyard just validates it:

auth_mode = "oidc"
oidc_issuer = "https://<team>.cloudflareaccess.com"
oidc_audience = "<Access application AUD tag>"
oidc_jwks_url = "https://<team>.cloudflareaccess.com/cdn-cgi/access/certs"
oidc_token_sources = ["header:Cf-Access-Jwt-Assertion", "cookie:CF_Authorization"]
oidc_allow_unverified_email = true
admin_identities = ["email:you@example.com"]

oidc_allow_unverified_email is needed because Access JWTs carry the email Cloudflare authenticated but no email_verified claim — Access asserts the email itself. Without the opt-in every Access user would be identified by the opaque sub:<uuid> instead of their email.

The cookie source is what lets the browser's EventSource authenticate against the SSE live-events endpoint — EventSource cannot set request headers, but Access sets the CF_Authorization cookie on the domain.

oauth2-proxy / Keycloak / Authentik and other standard OIDC setups work with plain bearer tokens: keep the default oidc_token_sources = ["bearer"] and set oidc_issuer + oidc_audience (plus oidc_jwks_url only when issuer discovery is unavailable).

SQL conventions

SQL ships as package data: src/loopyard/db/migrations/*.sql is applied exactly once per file, src/loopyard/db/functions/*.sql holds idempotent CREATE OR REPLACE definitions that are all reapplied on every migrate. Both are processed in lexicographic filename order — that order is the contract, so zero-pad migration prefixes (0001_...) and encode inter-function dependencies via filename prefixes. Every file runs in its own transaction; statements that refuse to run in a transaction block (e.g. CREATE INDEX CONCURRENTLY) are unsupported for now.

Tests

uv run -m pytest

Tests start a throwaway postgres:18 testcontainer and need a working Docker daemon. To use an existing PostgreSQL server instead (e.g. in CI), set LOOPYARD_TEST_DSN to a server the suite may create and drop databases on:

LOOPYARD_TEST_DSN=postgresql://loopyard:loopyard@127.0.0.1:5439/loopyard uv run -m pytest

The harness migrates a template database once per session and clones a fresh database per test, so each test gets an isolated schema and may open multiple concurrent connections.

Running vibe-loop against this board

This repo self-hosts: its own development dispatch runs through vibe-loop against loopyard's own PostgreSQL-backed board (see .vibe-loop.toml). Project selection is via an environment variable, never a committed file or baked CLI flag:

export LOOPYARD_PROJECT=loopyard
vibe-loop run-until-done

(vibe-loop autopilot run additionally needs an [autopilot] block in .vibe-loop.toml, which this wiring does not yet configure.)

Task worktrees for this project live under /home/ei-grad/loopyard-worktrees/. Workers move task status through the loopyard CLI as they progress — loopyard task transition <id> active|review|done — since vibe-loop only reads the task source and never writes status itself.

Metadata

Release files for loopyard 0.1.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 loopyard 0.1.1
File Size Uploaded
loopyard-0.1.1.tar.gz 93.1 kB Details

Built distribution (wheel)

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

Total release size: 213.9 kB

Release files / loopyard-0.1.1.tar.gz

Download URL loopyard-0.1.1.tar.gz
Size 93.1 kB
Tags Source
SHA-256 checksum
How to use checksums
5586ef366a5f3aa6a433b4b87a3390844451d3c5091e6dead4b30d77d7b7376b
BLAKE2b-256 checksum
How to use checksums
797d64b094434daaa38bfae74d216e90cc4c24cfb7caf69649d0ad2ce5349dd5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.13

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 Jul 15, 2026.

Transparency log

Release files / loopyard-0.1.1-py3-none-any.whl

Download URL loopyard-0.1.1-py3-none-any.whl
Size 120.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ee689a558759595af0755160d89309fb883008901b51edadd766ca03f49fcb40
BLAKE2b-256 checksum
How to use checksums
b7b2cda6162fe22af397c7304d8592813d28cd132e1e404dfb87b7cc6e2a8443
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.13

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 Jul 15, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.1 This release

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