Skip to main content

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 /agents through the Directory shell (production) and standalone on port 3030 (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 agents app entitlement: identity.tenant_apps accepts it, and the app.agents.read access 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

WorkScope
PLT-837Run-report ingest (coordinator.run_reports, agent registry, ingest bindings), the unified run feed, and reporter adapters
PLT-694Agent problem-report inbox — agents report their own capability gaps
PLT-839Retention worker for capability-gap occurrences
PLT-840MCP 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:

  1. Mirror the standard sub-zone environment the other zones already carry (the constellation-wiki project is the closest template): the database DSNs (DATABASE_URL, DIRECT_URL — the runtime DSN must be the non-bypass constellation_app role, or the assertNonBypassRole startup guard refuses to boot), AUTH_PROVIDER plus the selected provider's credentials (the middleware 503s without a provider), NEXT_PUBLIC_AUTH_PROVIDER (the production client bundle resolves to unknown and fails closed without it), and JWT_SECRET. NEXT_PUBLIC_DIRECTORY_URL is 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).

  2. Add the four agents-specific settings:

    • NEXT_PUBLIC_BASE_PATH=/agents on the agents Vercel project (Production only — previews serve standalone at /).

    • AGENTS_ZONE_URL on the Directory shell's Vercel project, pointing at the agents deployment.

    • API_KEY_JWT_SECRET on 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=1 on 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 before AGENTS_ZONE_URL reinstates 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 redeploy is not one. Next inlines NEXT_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 redeploy re-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 in turbo.json's tasks.build.env alongside NEXT_PUBLIC_BASE_PATH and NEXT_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 from AGENTS_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.

  3. Activate the permission for existing tenants. Migration 048 seeds app.agents.read but 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' canonical Admin roles acquire the permission only when role reconciliation next runs. So after the migration applies, run the Default role backfill workflow 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. write defaults to false, so the repair job is skipped and the run still reports green. Read the plan artifact and confirm the Repair (<Environment>) job actually ran — not the tick.
    • all-active is never the first production write. The runbook requires starting with target_mode=tenant for a single canary tenant, confirming a clean and idempotent result, and only then planning and writing all-active. Dispatching all-active first would put every active tenant behind one unreviewed blast radius.

    For this activation the inputs are environment (Staging, then Production), target_mode (tenant with a tenant_id for the canary, all-active afterwards), and write (false for each plan pass, true for each repair pass). A Production dispatch must come from main — the verify-ref gate 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 … Plan environment, 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.