PostgreSQL-backed task backend for coordinating multi-agent autonomous software development
Project description
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.
Relationship to capOS
loopyard is the host-side incarnation of a coordination model that capOS
(../capos) plans to self-host in-system in the long term: durable task
records, dependency-derived runnable sets, conflict-domain checks, and
fenced lock leases as typed capability services instead of an external SQL
server. The capOS design records are
docs/proposals/stateful-task-job-graphs-proposal.md (the durable
work-graph substrate, Stages E/G) and
docs/proposals/hosted-agent-swarm-proposal.md (AgentTask,
ResourceLease, ConflictReport, merge/release queues); both cite loopyard
as the operational precedent. Until that lands, loopyard is the production
task backend for capOS development, and its schema and invariants are the
reference semantics the capOS services are expected to reproduce.
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 optionalX-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 humanactor, all mutations are attributed to that actor, andX-Loopyard-Actoris ignored.GET /api/whoamireports the resolved identity; identities listed inadmin_identitiesmay call admin endpoints (currently lock force-release — 403 for everyone else). The interactive docs and/openapi.jsonare 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.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file loopyard-0.1.0.tar.gz.
File metadata
- Download URL: loopyard-0.1.0.tar.gz
- Upload date:
- Size: 93.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
16621d565266f38d06efca3c7bc64b5912a9ff14fb26b095160d9a5b93bbd671
|
|
| MD5 |
3e772b7cba289746a43a3fa4ccd7626d
|
|
| BLAKE2b-256 |
a03b5adf48e959f42adacaaf20f47d1fa6f9177084f24d16ff7e7c9d278af5ee
|
Provenance
The following attestation bundles were made for loopyard-0.1.0.tar.gz:
Publisher:
release.yml on ei-grad/loopyard
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
loopyard-0.1.0.tar.gz -
Subject digest:
16621d565266f38d06efca3c7bc64b5912a9ff14fb26b095160d9a5b93bbd671 - Sigstore transparency entry: 2175834575
- Sigstore integration time:
-
Permalink:
ei-grad/loopyard@4ae715028535c23d468b94b52bcfd053a190e030 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/ei-grad
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@4ae715028535c23d468b94b52bcfd053a190e030 -
Trigger Event:
release
-
Statement type:
File details
Details for the file loopyard-0.1.0-py3-none-any.whl.
File metadata
- Download URL: loopyard-0.1.0-py3-none-any.whl
- Upload date:
- Size: 120.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
584f23f38ed61f0597e29198bba1aeded6663d3b005f41888daf56a7cd774226
|
|
| MD5 |
d3de299c2c15343b6c427067548a9f84
|
|
| BLAKE2b-256 |
767e0f7c033d45fb02fb17c2e0b4466c832acff0c8a79872fd1359f7e8e3eac0
|
Provenance
The following attestation bundles were made for loopyard-0.1.0-py3-none-any.whl:
Publisher:
release.yml on ei-grad/loopyard
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
loopyard-0.1.0-py3-none-any.whl -
Subject digest:
584f23f38ed61f0597e29198bba1aeded6663d3b005f41888daf56a7cd774226 - Sigstore transparency entry: 2175834603
- Sigstore integration time:
-
Permalink:
ei-grad/loopyard@4ae715028535c23d468b94b52bcfd053a190e030 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/ei-grad
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@4ae715028535c23d468b94b52bcfd053a190e030 -
Trigger Event:
release
-
Statement type: