Agents
The Agents module (apps/agents) is Constellation's agent control plane — the future home of unified agent-run observability and the agent problem-report inbox, per ADR-026's control-plane boundary.
Current state: zone scaffold
The module is currently a registered but empty fifth zone (PLT-739). What exists:
- A Next.js sub-zone served at
/agentsthrough the Directory shell (production) and standalone on port3030(local dev / preview), following the platform's multi-zone conventions. - The canonical tenant-auth wiring (
authedRoute/authedRouteWithParams), the shared/api/auth/*surface (mock login, logout, me, memberships), and auth middleware with the shared principal-liveness check. - The
agentsapp entitlement:identity.tenant_appsaccepts it, and theapp.agents.readaccess permission is seeded by migration 048. The migration grants it to no one — a role grant is an audit-critical authority change — so admin-shaped operator roles acquire it through the audited default-role reconciliation, which for existing tenants means running the backfill workflow (see Operations step 3). - The shared cross-module navbar lists Agents in every app's switcher.
- A required CI smoke (
E2E · agents) proving the authenticated shell serves.
What lands next
| Work | Scope |
|---|---|
| PLT-837 | Run-report ingest (coordinator.run_reports, agent registry, ingest bindings), the unified run feed, and reporter adapters |
| PLT-694 | Agent problem-report inbox — agents report their own capability gaps |
| PLT-839 | Retention worker for capability-gap occurrences |
| PLT-840 | MCP problem-report retry ledger and receipt contract |
The module owns no database schema: control-plane tables live in the coordinator schema owned by packages/platform/coordinator, which is the sole access path for app code (ADR-026's transitional-ownership rule).
Operations
A fresh Vercel project inherits no environment from the sibling zones, so provisioning constellation-agents has two halves:
-
Mirror the standard sub-zone environment the other zones already carry (the
constellation-wikiproject is the closest template): the database DSNs (DATABASE_URL,DIRECT_URL— the runtime DSN must be the non-bypassconstellation_approle, or theassertNonBypassRolestartup guard refuses to boot),AUTH_PROVIDERplus the selected provider's credentials (the middleware 503s without a provider),NEXT_PUBLIC_AUTH_PROVIDER(the production client bundle resolves tounknownand fails closed without it), andJWT_SECRET.NEXT_PUBLIC_DIRECTORY_URLis optional: in production the zone redirects an unauthenticated page request to the Directory sign-in page relative to the shared origin (/login?redirect=/agents/…), and it is only needed when the Directory zone lives on another origin (local dev:http://localhost:3001) — without it a standalone preview serves a public sign-in-required page instead of looping (PLT-992). -
Add the four agents-specific settings:
-
NEXT_PUBLIC_BASE_PATH=/agentson the agents Vercel project (Production only — previews serve standalone at/). -
AGENTS_ZONE_URLon the Directory shell's Vercel project, pointing at the agents deployment. -
API_KEY_JWT_SECRETon the agents project, rotated in lockstep with Directory's copy — API-key JWTs are rejected without it, and a divergent rotation 401s every bearer caller. -
NEXT_PUBLIC_AGENTS_ZONE_ENABLED=1on every zone's Vercel project — directory, projects, catalog, wiki and agents — because each renders its own copy of the shared app switcher. This is the last step of the two halves, not part of them: until it is set, the switcher does not offer Agents at all, which is what keeps a registered-but-undeployed zone from advertising a link whose upstream does not exist. Setting it beforeAGENTS_ZONE_URLreinstates exactly that broken link, so set it after, and set it everywhere — a zone that misses it silently drops the entry only for users who happen to be in that zone.Setting the variable is not enough — every zone needs a FRESH git-based deployment afterwards, and
vercel redeployis not one. Next inlinesNEXT_PUBLIC_*at build time, so the bundles already serving each zone were built while this flag was absent and will keep hiding Agents no matter what the Vercel project settings say.vercel redeployre-serves an existing build and has been observed not to pick a new value up at all — see.ai/lessons/platform-build-and-deploy.md(2026-07-18). Push a commit, or run a CLI deploy from a real clone checked out on the target branch; never from a per-task worktree, which carries no branch attribution and so applies no branch-scoped variables. The flag is declared inturbo.json'stasks.build.envalongsideNEXT_PUBLIC_BASE_PATHandNEXT_PUBLIC_AUTH_PROVIDER, so the Turborepo cache treats a change to it as build-invalidating rather than relying on framework inference to notice.The gate is an explicit flag rather than an inference from
NEXT_PUBLIC_AGENTS_URL, which is a local-dev variable that production deliberately leaves unset (the multi-zone shell serves relative paths), or fromAGENTS_ZONE_URL, which is server-only and unreadable from the switcher. The zone the user is already inside is exempt: the menu always lists the module it is rendered in.
-
-
Activate the permission for existing tenants. Migration 048 seeds
app.agents.readbut deliberately grants it to no role (a grant must co-commit its audit entry, domain event and authority-cache invalidation, which migration SQL cannot do). Newly provisioned tenants are covered — provisioning reconciles their default roles — but existing tenants' canonicalAdminroles acquire the permission only when role reconciliation next runs. So after the migration applies, run theDefault role backfillworkflow once per environment, staging first.Follow the canonical runbook —
.ai/runbooks/default-role-production-repair.md— for the procedure itself, rather than the summary below: it owns the staging rehearsal, the production canary, and the evidence requirements, and a second copy here would drift from it.Two properties of the workflow are worth stating anyway, because getting either wrong looks like success:
- A run dispatched with the default inputs changes nothing.
writedefaults tofalse, so the repair job is skipped and the run still reports green. Read the plan artifact and confirm theRepair (<Environment>)job actually ran — not the tick. all-activeis never the first production write. The runbook requires starting withtarget_mode=tenantfor a single canary tenant, confirming a clean and idempotent result, and only then planning and writingall-active. Dispatchingall-activefirst would put every active tenant behind one unreviewed blast radius.
For this activation the inputs are
environment(Staging, thenProduction),target_mode(tenantwith atenant_idfor the canary,all-activeafterwards), andwrite(falsefor each plan pass,truefor each repair pass). A Production dispatch must come frommain— theverify-refgate rejects any other ref — and each repair pass needs an approval on the protected<Environment>environment from a second operator (self-approval, and an approval on the separate… Planenvironment, are both rejected). Skipping the activation is silent today and becomes a lockout for those tenants' admins the day PLT-440's fail-closed resolver starts enforcing app entitlement. - A run dispatched with the default inputs changes nothing.