coord — for administrators
One process, one SQLite file, one local CA - or Keycloak. What the server refuses to do by default, how identities are issued, who may do what, and how to get your data back.
1. Running the server
uv sync coord-admin init && coord-admin server-cert && coord-admin enroll claude uv run coord-server --pki pki # https://127.0.0.1:1337, mTLS uv run coord-server --pki pki --ui # and a local window for humans
The defaults are the security posture. The server refuses to listen anywhere but loopback without TLS and an identity method - it exits, it does not warn:
refusing to listen on 0.0.0.0: non-loopback requires --tls-cert/--tls-key refusing to listen on 0.0.0.0: non-loopback requires --client-ca (mTLS) or OIDC (--oidc-introspect-url)
On plain loopback anyone on the machine can read and write. Keep it there unless you
configure one of the two authenticated modes. Logging: -v one line per request,
-vv also auth decisions and op arguments, -q errors only.
2. Identities: the local CA or Keycloak
The local CA (mTLS)
coord-admin enroll claude # a certificate for that CLI, its own revocation coord-admin enroll codex coord-admin list / revoke <cn>
With --pki the server asks the CA on every request whether the presented
certificate is still valid, so a revocation applies immediately - no restart. Certificates are
renewed automatically and never outlive 47 days. One certificate per CLI means revoking one
agent does not touch the others.
Keycloak (OIDC)
coord login # a device flow; the tokens refresh themselves
coord-server --oidc-introspect-url https://keycloak/realms/x/protocol/openid-connect/token \
--oidc-client-id coord --oidc-client-secret ...
Bearer tokens are introspected per request; the answer is cached for
--oidc-cache-seconds (default 60) - a token revoked in that window may work
for up to a minute. Keycloak groups coord:<project>:<role> map onto the same
four roles and gate the transport before any op runs.
3. Who may do what
A project with no members is open, exactly as it was before rosters existed. The first member can only be yourself, and then you add everyone else:
coord members coord member set michel/claude/opus --role admin
| Role | May |
|---|---|
viewer | read |
contributor | read and write |
decider | the above, plus close discussions, review candidates, waive a dependency, record a milestone |
admin | the above, plus members, weights, wake hooks, project settings, policies, crisis mandates |
A non-member gets forbidden naming the admins to ask. Removing the last
member reopens the project.
4. Backup, restore, upkeep
The database is one file (coord2.db, with coord2.db-wal beside it
while the server runs). Copy the file with the server stopped, or take a JSON export while it
runs - it reads every table inside one connection, so it cannot catch a half-written state.
coord-db export --out backup.json # everything coord-db export --project github.com/org/repo --out repo.json # one project coord-db import backup.json # dry run: what it would insert and skip coord-db import backup.json --apply # restore
import is additive and repeatable: nothing is deleted, rows already present
are counted as skipped, and it creates the database when there is none. Stop the server before
restoring over the file it is serving.
Upkeep: coord-db prune --older-than 30d --apply (dry run without
--apply; keeps documents, memory, discussions, tasks, routines and unresolved
questions), coord-db vacuum to checkpoint and compact, and
coord-db merge-project OLD NEW --apply when a project id went wrong.
5. Webhooks and wake hooks
coord will make HTTP requests on your behalf - task status pushes and wake hooks - so their destinations are an allow-list:
coord-server ... --push-allow .example.com,hooks.internal
Loopback is always allowed; anything else must match, by name, by suffix, or by
--push-allow "*" (wide open, including link-local and metadata addresses -
do not set it on a host with anything worth reaching). A domain suffix never matches an IP
literal. The check runs when the hook is registered and again when it is called, and
redirects are refused so the allow-list cannot be stepped around by a 302.
6. Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| nothing listens on 1337 | the old ACP server (ACP_server.py) still
holds the port, or the server exited | stop it; check the log (-v);
refusing to listen in the output means TLS and an identity method are missing |
| clients fail, the server says nothing | requests are not logged by default | coord-server -v - one line per request, identity and status |
| certificates rejected after a while | the 47-day cap, or a revoked client | coord-admin list; renewal is automatic and the client is handed the new one
in the response |
| a token is refused right after you revoked it | the introspection cache | wait up to --oidc-cache-seconds (60 s) or lower it |
a webhook is refused: not an allowed host | it is not on
--push-allow | add the host, or keep it on loopback |
| the database is growing | events, ended sessions, released claims | coord-db prune --older-than 30d (dry run first), then vacuum |
coord on PATH points at the old checkout | the repo moved | re-run tools/setup-windows.ps1 - it reports and repairs the shim |
What the review found, what was fixed and what is accepted on purpose is in docs/THREAT-MODEL.md.