Agent governance
Rollout runbook for turning on the agent governance plane of a deployment with Terraform, behind two flags.
Terraform wiring for the Agent Governance plane: the MCP/Agent
token-validation middleware, its telemetry/GC background workers, and
agent-key authentication on the backend. Two flags gate everything and both
default to false — terraform plan is a no-op until an operator opts in:
| Variable | Default | Effect when true |
|---|---|---|
enable_agent_governance |
false |
Provisions the agent-mcp-token-signing-key-<env> secret + backend-SA secretAccessor grant (via module.core), and wires AGENT_GOVERNANCE_ENABLED=true + AGENT_MCP_TOKEN_SIGNING_KEY (secret ref) onto the backend Cloud Run revision. |
agent_governance_enforce |
false |
Once enable_agent_governance is on: false = monitor (record the would-be decision, always proceed); true = enforce (403 deny_tool / 429 rate_limited / over_budget before the handler runs). Ignored while enable_agent_governance is false. |
This is the same additive, code-gated pattern already used for Sifflet
(sifflet_base_url) and Castor (enable_castor at the module level): a
disabled flag means the secret + IAM resource are never created (count = 0)
and the env vars are never emitted.
Rollout sequence#
With generate_secrets = true (a first install), there is no hand step. module.core writes the
key's first version itself (48 random bytes, base64) and its secret-id output waits for that version and the reader
grant, so one apply creates the secret, the version and the backend revision in order: skip steps 1 and 2 below. The
key is then in the Terraform state (marked sensitive): the state bucket is as secret as the key. Turn
generate_secrets on only at the first install; on a deployment whose key was added by hand, the generated version
would become latest and invalidate every issued jma_ token.
The steps below are the hand path, for generate_secrets = false (the default, today's deployments):
- Provision the (empty) secret first — two-phase apply.
AGENT_MCP_TOKEN_SIGNING_KEYis a Secret Manager reference withversion = "latest"; Cloud Run resolveslatestat revision-deploy time. If step 1 and the backend's env wiring land in the same apply, the secret has zero versions when Cloud Run tries to deploy the new revision, and the deploy fails (a real outage risk on an already-running backend). Sequence it as two applies, same as the Sifflet/Castor rollout:
This creates only the emptyterraform apply -target=module.core -var enable_agent_governance=true ...agent-mcp-token-signing-key-<env>secret + the backend SA'ssecretAccessorgrant — the backend Cloud Run service is untouched at this point. - Populate the secret (out-of-band — with
generate_secrets = falsethe value never enters tfvars or state):openssl rand -base64 48 | gcloud secrets versions add \ agent-mcp-token-signing-key-<env> \ --project <project-id> --data-file=- - Full apply / redeploy. Now that a version exists, apply without
-target(or let CI apply normally):
This deploys a new backend revision withterraform apply -var enable_agent_governance=true -var agent_governance_enforce=false ...AGENT_GOVERNANCE_ENABLED=true,AGENT_GOVERNANCE_ENFORCE=false(monitor mode), and the now-resolvableAGENT_MCP_TOKEN_SIGNING_KEYsecret ref. - Register agents. Once the revision is live, mint per-agent
jma_tokens from the Agent Governance console (frontendAgent Governancepage →Agentstab, backed byPOST /api/v1/agent-governance/agents). Before this step, "Register agent" 500s with "No signing key configured for agent tokens". - Flip to enforce after a monitor soak. Watch the Agent Governance
dashboard / telemetry for false-positive blocks in monitor mode, then set
agent_governance_enforce = trueand re-apply:
Rollback is the same apply withterraform apply -var enable_agent_governance=true -var agent_governance_enforce=true ...agent_governance_enforce = false.
What turns on#
- Middleware (
services/backend/app/main.py) —AgentGovernanceMiddlewareis only added to the ASGI stack whenAGENT_GOVERNANCE_ENABLEDis true; off is a pure pass-through with zero overhead. - Workers (
services/backend/app/startup/orchestrator.py) — the telemetry-drain and retention-GC background tasks (start_agent_governance_workers/stop_agent_governance_workers) start and stop with the backend process, gated on the same flag. - Agent-key auth (
services/backend/app/api/iam_deps.py) — bearer tokens that look like agent tokens (jma_...) are only accepted and resolved to the fixed, read-onlyMCP_AGENT_PRINCIPAL_EMAILprincipal when the flag is on; otherwise they fail normal auth like any other unrecognized token.
Not included#
This wiring does not build or deploy the hosted MCP gateway
(nistor-mcp). That is a separate, additive capability
(mcp_gateway_image / MCP_GATEWAY_SERVICE_ACCOUNT) blocked on a container
image, CI pipeline, and Cloud Run service that do not exist yet. Until that
ships, the Connect tab's remote-MCP blocks in the frontend stay placeholder,
and only first-party, directly-registered agent tokens work — there is no
public agent-facing endpoint.
Operator notes#
- Run it from your deployment repository.
deployments/gcp/is the reference composition: it deploys nothing by itself; a deployment repository sources it as a module pinned to a commit or tag (//deployments/gcp?ref=<tag>, asexamples/deployment-repositorydoes) and CI applies that repository'sterraform/. Run this rollout sequence there: setenable_agent_governance(and lateragent_governance_enforce) in that repository's tfvars, and check its current values before assuming a fresh rollout. agent_governance_enforceis new. Before this change, the backend module hardcodedAGENT_GOVERNANCE_ENFORCE=falsein Terraform regardless of any variable — there was no supported way to turn enforce mode on via Terraform in either root.infra/terraform/modules/services/backendnow exposesagent_governance_enforce(defaultfalse, so existing applies are unaffected); the deployment repository'sterraform/needs the same two-line variable + pass-through added before step 5 above can be applied there.terraform -chdir=deployments/gcp init -backend=falseandterraform testrun on this root in every pipeline (jobterraform-fmt-validate, tests indeployments/gcp/tests/).