---
title: "Connect to the proxy"
description: "Make a Guardian, point it at your local Viper proxy, and read what the proxy supports before your agents use it."
source: "Guardian Agent SDK"
---
Everything the SDK does goes through `ga.Guardian`, your connection to the local Viper proxy. From it you make each agent, upload skills and instructions, and read what the proxy supports.

{/* generated file sdks/python/snippets/connect.py python */}

```python
import asyncio

import guardian_agent as ga

guardian = ga.Guardian(timeout=5)  # the address from VIPER_PROXY_URL

capabilities = guardian.capabilities()  # checked once, then remembered
print(f"agent API {capabilities.api}, Viper {capabilities.viper}")
print(f"enrolled: {', '.join(capabilities.agents)}")
print(f"refuses requests that name no agent: {capabilities.strict}")
print(f"largest definition: {capabilities.limits.definition_bytes} bytes")


async def main() -> None:
    # Ask again, after the proxy restarts with another agent enrolled
    refreshed = await guardian.acapabilities(refresh=True)
    print(f"enrolled now: {', '.join(refreshed.agents)}")


asyncio.run(main())
```

{/* end generated */}

## The proxy's address

`ga.Guardian()` reads the proxy's address from `VIPER_PROXY_URL`, or takes it as its first argument: the base URL `viper proxy` printed when it started, such as `http://127.0.0.1:8080`. Start the proxy with `--port` to keep the address fixed. There is no default address, so the SDK never sends anything to a service it only guessed at: with neither set, `Guardian()` raises `ValueError`, as it does for an address that isn't an `http://` or `https://` host and port, or that has a path, query or fragment (a trailing `/` is fine).

The proxy listens on `127.0.0.1` only, so only processes on its own machine reach it. Its agent API, which registers definitions and stores files, also refuses a call that addresses it by anything but `localhost` or a loopback IP. It refuses any call from a web page (one with an `Origin` header), as the proxy does for a model request that names an agent. A model request's address isn't checked. The SDK's calls to the proxy ignore `HTTP_PROXY` and similar settings, so a proxy configured for the rest of your traffic doesn't get in the way.

`timeout` is how many seconds a call to the proxy may take. An async call ends within it. A sync call waits at most that long for each step (connecting, the answer, each read of its body), and stops reading once it has passed overall.

## What the proxy supports

`guardian.capabilities()` asks the proxy what it supports, on first use, and remembers the answer:

- `api`: the agent API version, `"v1"`.
- `viper`: the proxy's Viper version, such as `"0.3.0"`, for messages and support only. What the proxy does is in `features`.
- `features`: what the proxy does beyond the API version, by name.
- `schemas`: the definition schemas it accepts.
- `strict`: whether it refuses model requests that name no agent, as `viper proxy --require-agent-header` does.
- `agents`: the agents enrolled on it, sorted. `guardian.agents()` returns the same list.
- `limits`: the bounds a definition must stay within on this proxy, such as `definition_bytes`, `tools` and `sub_agent_depth` ([Limits](../api/capabilities.mdx#limits)).

The proxy reads its enrolled agents when it starts, so after a restart with an agent added, ask again with `capabilities(refresh=True)` or `agents(refresh=True)`. From async code, use `await guardian.acapabilities()`.

### An incompatible proxy

Before relying on the proxy, `Guardian` checks that it serves this SDK's agent API, accepts its definition schema, and does everything this SDK relies on. If not, it raises `ga.ProxyIncompatible`, naming what's missing, and sends no definition. It goes by the features the proxy lists, not its Viper version number. Viper 0.2.1 and earlier releases, and development builds made before this check, list none of the features the SDK needs, so they're refused: upgrade to Viper 0.3.0 or later. A development build made after it may still report 0.2.1, and is accepted, since it lists the features.

The errors a connection can raise, and what to do about each:

- **`ga.ProxyUnreachable`**: nothing answered at the address in time. Start `viper proxy`, or set `VIPER_PROXY_URL` to the address it printed.
- **`ga.ProxyIncompatible`**: the proxy lacks the agent API, schema or features this SDK uses. Follow the hint: upgrade Viper, or install the SDK release that matches it.
- **`ga.NotLocal`**: the proxy refused a call that didn't come from its own machine. Call it from its own machine, as `localhost` or `127.0.0.1`.
- **`ga.UnexpectedResponse`**: something answered that isn't a Viper proxy, or its answer couldn't be read. Check `VIPER_PROXY_URL` points at `viper proxy`, and that the releases match.

## Sharing a Guardian

A `Guardian` holds no open connections, so there is nothing to close, and one can be shared between threads and async tasks. Use one per proxy. An agent that uses two model providers runs through two proxies, one for each provider's endpoint on the Agent Gateway, so it uses a `Guardian` for each ([Run an agent](./run-an-agent.mdx#one-proxy-per-provider)).