Connect to the proxy
Make a Guardian, point it at your local Viper proxy, and read what the proxy supports before your agents use it.
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.
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())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 infeatures.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, asviper proxy --require-agent-headerdoes.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 asdefinition_bytes,toolsandsub_agent_depth(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. Startviper proxy, or setVIPER_PROXY_URLto 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, aslocalhostor127.0.0.1.ga.UnexpectedResponse: something answered that isn’t a Viper proxy, or its answer couldn’t be read. CheckVIPER_PROXY_URLpoints atviper 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).