Skip to content

Operations

Day-two tasks for operators: tokens for the admin API, the human-approval queue, the kill switch, and reviewing MCP tool baselines.

Operator tokens, approvals and the kill switch

/admin/* on the operator listener takes operator tokens only: audience ai-control-layer-operator, no agent and no session. Agent tokens (even an admin's) get 401 wrong_audience there, and operator tokens get the same on the agent listener. The demo issuer mints one for identities holding admin or an approver role (olga@demo, root@demo):

TOKEN=$(curl -s -X POST 127.0.0.1:9090/auth/demo-token -H 'content-type: application/json' \
  -d '{"sub": "olga@demo", "kind": "operator"}' | jq -r .access_token)
export ACL_OPERATOR_TOKEN=$TOKEN
uv run acl approvals list                       # pending approvals olga may decide
uv run acl approvals show apr-...               # tool, resources, reasons, digests: no arguments
uv run acl approvals approve apr-... --note ok  # or: deny
uv run acl kill nightly_etl --reason incident   # admin only; `acl unkill nightly_etl`

An approver sees and decides the approvals of agents whose approvers name one of their roles (admin: all), never one of their own sessions. A held MCP call answers a tool error carrying _meta["ai-control-layer/approval_id"]; once approved, the agent retries the same tools/call with _meta: {"ai-control-layer/approval_id": "<id>"} in its params (LLM: the X-ACL-Approval-Id header). The retry is re-authorized under the current policy and runs at most once. acl is the same CLI as python -m gateway.cli.

Approving MCP tool baselines (tool_pinning)

Every MCP server needs a pin file in pins/ (require_pin: true in config/policy.yaml); a tool that differs from its baseline (description, input schema, annotations) or is not in it is hidden from tools/list and refused on tools/call until it is re-approved. Review and approve on the host, against the running stack (admin token; pins/ is mounted read-only into the gateway and a new file applies on the next call):

TOKEN=$(curl -s -X POST 127.0.0.1:9090/auth/demo-token -H 'content-type: application/json' \
  -d '{"sub": "root@demo", "kind": "operator"}' | jq -r .access_token)
ACL_OPERATOR_TOKEN=$TOKEN uv run python -m gateway.cli pin sales_db          # diff, exit 3 if changed
ACL_OPERATOR_TOKEN=$TOKEN uv run python -m gateway.cli pin sales_db --write  # approve

A tool caught drifting is quarantined for every session and gateway (listed by the review) until an operator re-approves a new baseline with --write, or confirms the server is back to the approved definition and runs pin <server> --clear-quarantine <tool>.

The same review runs inside the gateway container, which sees the upstreams directly: docker compose exec -e ACL_OPERATOR_TOKEN gateway sh -c 'python -m gateway.cli --url "http://$ACL_OPERATOR_HOST:$ACL_OPERATOR_PORT" pin sales_db' (read-only there: no --write).