Skip to content

2 · Deploy

Deploying in Brutor is not “get the container running” — that part is yours. Deploying is giving the thing you built its production identity: an AI System entity with exactly the resources it needs, credentials that are scoped to it, a declared owner and intended use, and a lifecycle stage someone accountable signed off on. Everything in Govern, Observe and Assure hangs off what you create here.

All calls in this stage go to the Control Plane Admin API (:5050) with an admin token — or click the same things in the Admin UI. Wrap them in an idempotent setup script so dev, staging and prod are provisioned from code, not memory.

Terminal window
curl -X POST http://localhost:5050/v1/admin/resource-groups \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "support-copilot",
"display_name": "Support Copilot",
"description": "Answers customer questions, looks up orders, prepares refunds",
"group_type": "ai_system",
"system_kind": "agent",
"parent_group_id": "<customer-success-team-group-id>",
"inherit_resources": true
}'
{
"id": "group-01ABC...",
"name": "support-copilot",
"group_type": "ai_system",
"system_kind": "agent",
"inherit_resources": true
}

Three fields deserve thought:

  • system_kindagent is the default; pick honestly (the six kinds), because it selects which lifecycle signals apply. Silence detection pages you usefully for an integration and uselessly for an assistant.
  • parent_group_id — place the system under the team that owns it. The parent’s budgets, limits and policies apply to your system automatically (restrictively — a parent can only ever tighten), and the team’s cost roll-ups will include it.
  • inherit_resourcestrue means your system also sees models and tools bound at the parent. Set false for a strictly least-privilege system that should see only its own bindings.

Bind each resource the agent actually uses — this is the permission set, and later it becomes the contract:

Terminal window
G=group-01ABC... # the AI System's id
# Models (bind a routing group? bind the virtual model AND every member)
curl -X POST http://localhost:5050/v1/admin/resource-groups/$G/llm-models \
-H "Authorization: Bearer $ADMIN_TOKEN" -H "Content-Type: application/json" \
-d '{"llm_model_id": "<model-id>"}'
# MCP servers (the CRM the agent reads orders from)
curl -X POST http://localhost:5050/v1/admin/resource-groups/$G/server-configs \
-H "Authorization: Bearer $ADMIN_TOKEN" -H "Content-Type: application/json" \
-d '{"server_config_id": "<crm-server-config-id>"}'
# A2A peers (the billing agent it delegates to) — PUT replaces the full set
curl -X PUT http://localhost:5050/v1/admin/resource-groups/$G/agent-cards \
-H "Authorization: Bearer $ADMIN_TOKEN" -H "Content-Type: application/json" \
-d '{"agent_card_ids": ["<billing-agent-card-id>"]}'

Skills and knowledge bases bind the same way (Admin UI → the group’s Agent Skills / Knowledge Base tabs). Binding POSTs return 409 on repeat — provisioning scripts treat that as “already there”.

Verify the effective result — what the system actually resolves to, inheritance included:

Terminal window
curl http://localhost:5050/v1/admin/resource-groups/$G/effective-llm-models \
-H "Authorization: Bearer $ADMIN_TOKEN"

Two options, and mature deployments use both:

An API key bound to the system — the simplest production credential. The key is the governance scope: any process holding it executes inside support-copilot, its resources, limits and audit trail.

Terminal window
curl -X POST http://localhost:5050/v1/admin/resource-groups/$G/api-keys \
-H "Authorization: Bearer $ADMIN_TOKEN" -H "Content-Type: application/json" \
-d '{"name": "support-copilot-prod"}'

The plaintext is returned once — put it straight into your secret manager.

An agent identity — when you want the agent to be a first-class principal with per-action grants rather than a bearer of a group key. Identity is anchored on your IdP (Okta, Entra Agent ID, SPIFFE); the gateway maps the workload token to the agent and applies default-deny grants:

Terminal window
curl -X POST http://localhost:5050/v1/admin/agent-identities \
-H "Authorization: Bearer $ADMIN_TOKEN" -H "Content-Type: application/json" \
-d '{"name": "support-copilot", "owner_label": "Customer Success",
"idp_server_id": "<idp-id>", "idp_subject": "spiffe://acme/ns/agents/support-copilot"}'

Then add the identity to the AI System’s memberships so the group’s stack applies to it. You’ll write its grants (allow / deny / approval-required per action) in Govern; start in dry_run mode. Full model: Agent identity & policies.

The fields that make the system governable by people — owner, intended use, autonomy, risk tier — live on the AI System and are required evidence at the gate below. Set them in the Admin UI under AI Estate → AI Systems → Support Copilot, or via PATCH /v1/admin/resource-groups/{id}:

  • Owner — the accountable human, not a team alias someone will rotate out of.
  • Intended use / intended clients — what it’s for and what should be calling it. Conformance later compares these declarations against observed traffic — an undeclared caller on a high-risk system is the finding a risk owner reads first.
  • Autonomy levelautonomous, approval_required, restricted or suspended. Resolves min-wins with the hierarchy; you can tighten it automatically on drift later.
  • EU AI Act risk tierhigh adds a named-human-approver floor to every lifecycle transition.

An AI System moves proposed → approved → active → deprecated → retired, and each forward transition demands evidence. First, mint the contract — the hash-pinned snapshot of everything you just configured, generated from live config (never hand-written):

Terminal window
curl -X POST http://localhost:5050/v1/admin/ai-systems/$G/contracts \
-H "Authorization: Bearer $ADMIN_TOKEN"

Then ask the gate what it needs — every reachable stage with each requirement marked satisfied or missing:

Terminal window
curl http://localhost:5050/v1/admin/ai-systems/$G/lifecycle \
-H "Authorization: Bearer $ADMIN_TOKEN"
Transition Requires
proposed → approved A contract minted for the current config; an owner; intended use
approved → active An approver recorded on the contract; a liveness decision
active → deprecated A successor named, or “no successor” stated explicitly
deprecated → retired No runs for the quiet period (30 days)

Have the approver approve the contract (POST /v1/admin/contracts/{id}/approve or the Admin UI’s Contract panel), make the liveness decision — for support-copilot, on_demand is right; a nightly pipeline would pick scheduled — and transition:

Terminal window
curl -X POST http://localhost:5050/v1/admin/ai-systems/$G/lifecycle \
-H "Authorization: Bearer $ADMIN_TOKEN" -H "Content-Type: application/json" \
-d '{"to_stage": "approved"}' # then: {"to_stage": "active"}

Deploy your agent process wherever it runs, with two things in its environment: the gateway URL and the production credential. If you built Develop faithfully, nothing else changes — same code, new key, and the run ledger starts on day one.

Finally: everything on this page should live in a re-runnable script per environment. The setup-script guide shows the idempotency pattern (create-or-409, capture plaintext keys once, verify effective views at the end).