Skip to content
Version: Current

Onboarding

2026-11-05. repository reference/snapshot reviewed 2026-11-05; verify current behavior before using as live guidance.

The consumer surface is a browser application mounted ahead of KeyAuth: /, /i/{token}, /signin, /signout, /app/* and /account. It exists because a person signing in has no key yet. It is invite-only: there is no self-registration, no payment flow and no email delivery on this path.

Two facts drive it, both from the code.

The public host must be in ACUMEN_ALLOWED_HOSTS. Guard checks Host before any handler and answers 403 forbidden host otherwise; the same set gates a browser Origin.

The listener must stay loopback with no process bearer. main reads ACUMEN_BEARER_TOKEN only for a non-loopback bind, and Guard then demands it on every path except /healthz. A browser cannot send that header, so a non-loopback consumer deployment answers 401 unauthorized on /, /signin and every page.

Do not embed the token in a page and do not add a consumer exemption: either would hand the process token to every visitor. The supported shape is loopback plus TLS and policy at the edge:

browser ──TLS──► Caddy (public host) ──► 127.0.0.1:4020
├─ /console*, /metrics, /health, /readyz,
│ /v1/router/*, /debug/* → 404
└─ /, /i/*, /signin, /app/*, /account → acumen

Caddy must send X-Forwarded-Proto: https; the session cookie is Secure only when the request arrived over TLS or carries that header. It must not cache /app/* or /account; acumen already sends Cache-Control: no-store.

No session exists yet, so bootstrap from the CLI with the same binary and database:

Terminal window
acumen invite create -db /var/lib/acumen/acumen.db \
-email person@example.com -base https://ai.tcp.systems

The token is printed once and stored only as a hash. -ttl defaults to 72h and must be greater than zero and at most 720h; -note is optional. Run it as the account that owns the database file.

The invite URL is a credential. Print it once to the person who needs it, never into a shared log, a ticket, or this documentation.

  1. Open https://<public-host>/i/{token}. An unknown, expired or already-used token answers 410; the page does not say which.
  2. Set a recovery code and a display name, and accept the invite (POST /i/{token}). Accepting consumes the token and creates the account, the first session grant and the acumen_session cookie.
  3. Sign in afterwards at /signin with the email and recovery code. There is no password reset by email: mail is not configured on this surface.
  4. From /app, create a key (POST /app/grants/new), read the ledger, and revoke what is no longer needed. The key’s plaintext is shown once.
Path Purpose
/ public landing page; shadows the operator HTML index when mounted
/i/{token} invite acceptance; needs no session
/signin, /signout session sign-in and teardown
/app the account’s grants, keys and events
/app/grants/new create a key (kind=api grant plus its virtual_keys row)
/app/grants/{id}/revoke revoke a key or session
/account account state, including freeze
Terminal window
tools/edge-check.sh https://<public-host>

For a complete migration guide covering base URLs, key handling, model aliases, streaming, error handling, and failover, see Migrating a client to acumen.

The script asserts every operator path is 401/403/404 and /healthz is 200. Run it from the deploy host after a release. Model doors must answer 401/403 so a missing door cannot pass as a closed one.

  • POST /v1/router/keys writes virtual_keys without the matching api grant. Enforcement reads virtual_keys, so the key works; the ledger is incomplete for keys minted on the operator path.
  • Operator DELETE /v1/router/keys deletes the key row but leaves the grant row untouched. Both gaps are recorded, not hidden.
  • OIDC and passkeys are deferred; sign-in is invite plus recovery code.