> ## Documentation Index
> Fetch the complete documentation index at: https://gateway.forceaisecurity.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Build your own agent on ForceAI's smart-router (A2A)

> Wrap ForceAI's smart-router in a thin A2A server so you get a governed agent whose brain is ForceAI's own model routing.

ForceAI's A2A agents proxy to an upstream A2A server; the gateway does not run a model *for* an A2A agent. So to build an agent whose brain is ForceAI's own smart-router, you put a small A2A server in the middle. It receives the A2A message, calls the gateway's OpenAI-compatible `/v1/chat/completions` with `model: smart-router`, and returns the answer as an A2A artifact

```
client ──A2A──▶ ForceAI Gateway (key, RBAC, guardrails, system-knowledge, audit)
                      │  forwards message/send
                      ▼
                Your A2A wrapper  ──▶ POST /v1/chat/completions (model: smart-router)
                      ▲                         │
                      └───── answer ◀───────────┘  gateway routes to the best LLM
```

The outer A2A hop is fully governed, and the inner chat-completions call is routed by smart-router and is itself subject to your guardrails. A reference implementation ships in `deploy/smart-router-agent/`

## The wrapper

A minimal FastAPI A2A server. On each `message/send` it joins the text parts (a ForceAI system-knowledge document, if attached, arrives as the leading part), calls the gateway, and returns a completed A2A task

```python theme={null}
GATEWAY_URL = os.environ["FORCEAI_GATEWAY_URL"]   # e.g. http://forceai-gateway:4000
API_KEY = os.environ["FORCEAI_API_KEY"]           # a ForceAI virtual key
MODEL = os.environ.get("FORCEAI_MODEL", "smart-router")

async def ask(prompt: str) -> str:
    async with httpx.AsyncClient(timeout=60) as c:
        r = await c.post(f"{GATEWAY_URL}/v1/chat/completions",
            headers={"Authorization": f"Bearer {API_KEY}"},
            json={"model": MODEL, "messages": [{"role": "user", "content": prompt}]})
    r.raise_for_status()
    return r.json()["choices"][0]["message"]["content"]
```

The A2A `result` must be a valid Task (or Message); returning a bare `{"artifacts": [...]}` makes the gateway hand the caller an empty result

<Warning>
  Give the wrapper a virtual key scoped to the models it may call, not your admin key. The wrapper authenticates to the gateway as any other API client
</Warning>

## Run it

The reference agent is wired into the compose stack and reaches the gateway over the compose network

```bash theme={null}
docker compose up -d --build smart-router-agent
curl -s http://localhost:9100/.well-known/agent-card.json   # sanity check the card
```

## Onboard it

Register the wrapper as an A2A Standard agent, pointing at its URL (compose DNS `http://smart-router-agent:9100/` from the gateway's perspective)

```bash theme={null}
curl -X POST https://YOUR_GATEWAY_DOMAIN/v1/agents \
  -H "Authorization: Bearer $ADMIN_KEY" -H "Content-Type: application/json" \
  -d '{
        "agent_name": "forceai-smart-router-agent",
        "agent_card_params": {
          "protocolVersion": "0.3",
          "name": "ForceAI Smart-Router Agent",
          "description": "Brain is ForceAI smart-router",
          "url": "http://smart-router-agent:9100/",
          "version": "1.0.0",
          "defaultInputModes": ["text"],
          "defaultOutputModes": ["text"],
          "capabilities": { "streaming": false },
          "skills": [{ "id": "chat", "name": "Chat", "description": "smart-router chat", "tags": ["llm"] }]
        }
      }'
```

New agents start `pending`; approve it before it can be invoked

```bash theme={null}
curl -X POST https://YOUR_GATEWAY_DOMAIN/v1/agents/$AGENT_ID/approve -H "Authorization: Bearer $ADMIN_KEY"
```

In the dashboard you can do the same under Agents -> Add New Agent -> A2A Standard, and optionally attach a **System knowledge** document on the Configure step; it is injected as leading instructions on every call

## Invoke it

```bash theme={null}
curl -X POST https://YOUR_GATEWAY_DOMAIN/a2a/$AGENT_ID/message/send \
  -H "Authorization: Bearer $VIRTUAL_KEY" -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":"1","method":"message/send",
       "params":{"message":{"role":"user","parts":[{"kind":"text","text":"Write a friendly one-sentence greeting."}],"messageId":"m1"}}}'
```

The answer is produced by smart-router, and the Logs page records the inner hop as `model: smart-router`. Because the guardrail runs on that inner call, a prompt the guardrail blocks returns a `400` from the agent, exactly as a direct chat completion would

## Exposing it to external users

Everything is the gateway, one surface, two protocols, all authenticated with a virtual key:

* Models and the smart-router: `POST /v1/chat/completions` (OpenAI-compatible)
* Agents: `POST /a2a/{agent_id}/message/send` and `/message/stream` (A2A JSON-RPC), plus `GET /a2a/{agent_id}` for the card

Mint a virtual key, scope it to the specific models and agents the user may reach, and hand them the key. They call the same endpoints you do
