Skip to content

1 · Develop

The develop stage has one principle: build against the gateway from the first commit. Not because governance matters on your laptop — it mostly doesn’t — but because everything you get later (attribution, approvals, the run ledger, drift baselines) works only for traffic the gateway saw. An agent developed against api.openai.com and pointed at the gateway on release day works, but arrives with no history, untested approval handling, and tool calls that were never policy-checked.

The good news: building against the gateway costs you one base URL.

from openai import OpenAI
client = OpenAI(
api_key="sk_brutor_api_...", # your AI System's key, not a provider key
base_url="http://localhost:8100/v1/proxy/llm",
)
resp = client.chat.completions.create(
model="gpt-5.2",
messages=[{"role": "user", "content": "Summarize order #4711's status."}],
)

Your OpenAI or Anthropic SDK code does not change otherwise. Streaming, tool calling, embeddings, images and audio all pass through; see Call LLMs through the gateway for the full surface and error semantics.

During development you need an AI System to develop in — a scratch one is fine and takes a minute:

  1. In the Admin UI, go to Resource Groups → create group → name it (support-copilot-dev), set Type to AI System, and leave Inherit resources from parent on.

  2. On the group’s AI Models tab, bind the models you want to develop against.

  3. On the API Keys tab, mint a key. The plaintext (sk_brutor_api_...) is shown once — that key is your system’s identity and scope during development.

In Deploy you’ll create the production system properly — via API, with declared intent and a lifecycle gate. The dev system can stay around; separate dev and prod systems mean separate budgets, separate ledgers, and no dev traffic polluting production baselines.

Add capabilities — through the gateway, not around it

Section titled “Add capabilities — through the gateway, not around it”

Whatever your agent needs, register it once and call it through the governed surface. Each capability you route through the gateway is one more thing that later stages can scope, meter, approve and audit:

Your agent needs Governed surface Guide
Tools (CRM, Slack, databases, SaaS APIs) /v1/proxy/mcp/{server_id} — JSON-RPC to any registered MCP server Use MCP tools · Register your own server
Many tool servers behind one endpoint /v1/proxy/vmcp/{id} — Virtual MCP aggregation MCP clients
Reusable, versioned automation with approval gates Agent Skills over MCP (progressive disclosure) Agent Skills
Grounding in your documents Knowledge bases — retrieval wired per resource group Knowledge bases & RAG
To call another team’s agent A2A v1.0 — signed cards, tasks, messaging A2A agents
Per-user OAuth to SaaS tools The OAuth proxy — tokens never enter your process OAuth for MCP tools

This is the step developers skip and regret. An agent that drives its own loop (LangGraph, CrewAI, a hand-rolled while-loop) makes many gateway calls to do one job — and the gateway can’t know which calls belong together unless you tell it. One header does it:

import uuid
run_id = str(uuid.uuid4()) # new id per task, any opaque string
resp = client.chat.completions.create(
model="gpt-5.2",
messages=messages,
extra_headers={"x-brutor-run-id": run_id}, # same id on EVERY call of this task
)

Send the same id on every LLM, MCP, skill and A2A call the task makes. Optionally close the run explicitly on the last call:

extra_headers={
"x-brutor-run-id": run_id,
"X-Brutor-Run-End": "completed", # or errored / exhausted / cancelled
}

What this buys you, permanently:

  • The run ledger — outcomes, action counts, tool sets and cost per completed task instead of cost smeared across requests.
  • Drift baselines — “normal” is learned per run shape; without run grouping there is nothing to baseline.
  • Debugging — a run expands into a step-by-step timeline in the Admin UI.

The id is keyed to your credential server-side, so you can’t collide with (or write into) another principal’s runs. Hops delegated by the gateway itself (an LLM call that triggers an MCP call that triggers A2A) are chained cryptographically without your help — the header is for the loop only you can see. exhausted (your framework hit its iteration cap) is worth wiring up: the gateway cannot observe your framework’s cap from outside, so this signal is the only way the ledger ever learns it.

Handle 202 Accepted — approvals will happen to you

Section titled “Handle 202 Accepted — approvals will happen to you”

In production, some of your agent’s tool calls will require a human sign-off (tool approvals — you’ll configure which in Govern). The gateway parks such a call and returns 202 with a poll URL instead of a result. Build the wait-and-resume into your loop now, while it’s cheap:

if response.status_code == 202:
poll_url = response.json()["poll_url"]
# poll until approved/denied, then continue the task

Build an agent §5 shows the complete polling pattern. An agent that treats 202 as an error will break the first time a governance team tightens a tool — which is to say, it will break in production.

A working agent whose every LLM call, tool call and delegation flows through the gateway, tagged with run ids, tolerant of approvals — and a dev AI System with a ledger already accumulating. Time to ship it properly: