Skip to main content

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 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 fromemail: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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

loopyard-0.1.0.tar.gz (93.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

loopyard-0.1.0-py3-none-any.whl (120.6 kB view details)

Uploaded Python 3

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

Hashes for loopyard-0.1.0.tar.gz
Algorithm Hash digest
SHA256 16621d565266f38d06efca3c7bc64b5912a9ff14fb26b095160d9a5b93bbd671
MD5 3e772b7cba289746a43a3fa4ccd7626d
BLAKE2b-256 a03b5adf48e959f42adacaaf20f47d1fa6f9177084f24d16ff7e7c9d278af5ee

See more details on using hashes here.

Provenance

The following attestation bundles were made for loopyard-0.1.0.tar.gz:

Publisher: release.yml on ei-grad/loopyard

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for loopyard-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 584f23f38ed61f0597e29198bba1aeded6663d3b005f41888daf56a7cd774226
MD5 d3de299c2c15343b6c427067548a9f84
BLAKE2b-256 767e0f7c033d45fb02fb17c2e0b4466c832acff0c8a79872fd1359f7e8e3eac0

See more details on using hashes here.

Provenance

The following attestation bundles were made for loopyard-0.1.0-py3-none-any.whl:

Publisher: release.yml on ei-grad/loopyard

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page