Guardian and Agent
Guardian, the connection to the local Viper proxy, and Agent, an enrolled agent you route model clients through.
A Guardian is your connection to the local Viper proxy. From it you make each Agent: an enrolled agent, described by its definition, whose model clients you route through the proxy so that every request is signed as that agent.
Guardian
Your connection to the local Viper proxy.
The proxy holds each enrolled agent’s key and signs every model request as
the agent that made it. A Guardian talks to its local agent API: it
checks the proxy is compatible, and creates the agents you route model
clients through.
It holds no open connections, so there is nothing to close, and one
Guardian can be shared freely between threads and async tasks. Use
one per proxy.
Example:
import guardian_agent as ga
guardian = ga.Guardian() # reads VIPER_PROXY_URL
print(guardian.capabilities().agents) # ('analyst', 'researcher')Parameters:
- proxy_url (
str | None) – The proxy’s base URL, asviper proxyprinted it, such as"http://127.0.0.1:8080". Defaults toVIPER_PROXY_URL. The proxy answers only callers on its own machine that address it aslocalhostor a loopback IP. - timeout (
float) – Seconds a call to the proxy may take. An async call ends within it. A sync call waits at most this long for each step (connecting, the answer, each read of its body) and stops reading once it has passed overall.
Raises:
ValueError– Neitherproxy_urlnorVIPER_PROXY_URLis set, the one given isn’t anhttp(s)://host[:port]base URL, ortimeoutisn’t a positive number of seconds.
Functions:
- capabilities – What the proxy supports, checked on first use and then remembered.
- acapabilities – What the proxy supports; the async form of
capabilities. - agent – An enrolled agent, described here, to route model clients through.
- agents – The agents enrolled on the proxy, sorted.
- skill – A skill for a definition’s
skills, its files stored on the proxy. - askill – A skill, its files stored on the proxy; the async form of
skill. - instructions – Memory the agent reads but can’t change, its files stored on the proxy.
- ainstructions – Fixed memory, its files stored; the async form of
instructions.
Attributes:
- proxy_url (
str) – The proxy’s base URL, without a trailing slash.
Guardian.proxy_url
proxy_url: strThe proxy’s base URL, without a trailing slash.
Guardian.capabilities
capabilities(*, refresh: bool = False) -> CapabilitiesWhat the proxy supports, checked on first use and then remembered.
Calls made at the same moment, before any answer is remembered, may each ask the proxy; they get the same answer.
Parameters:
- refresh (
bool) – Ask the proxy again. The proxy reads its enrolled agents when it starts, so this matters after the proxy restarts.
Returns:
Capabilities– The proxy’s API version, Viper version, features, schemas, enrolled agents and limits.
Raises:
ProxyUnreachable– Nothing answered at the proxy’s address in time.ProxyIncompatible– The proxy doesn’t serve this SDK’s agent API, accept its definition schema, or offer a feature it relies on.UnexpectedResponse– Something answered that isn’t a Viper proxy, or its answer couldn’t be read.
Guardian.acapabilities
acapabilities(*, refresh: bool = False) -> CapabilitiesWhat the proxy supports; the async form of capabilities.
Parameters:
- refresh (
bool) – Ask the proxy again, as forcapabilities.
Returns:
Capabilities– The proxy’s API version, Viper version, features, schemas, enrolled agents and limits.
Raises:
ProxyUnreachable– Nothing answered at the proxy’s address in time.ProxyIncompatible– The proxy doesn’t serve this SDK’s agent API, accept its definition schema, or offer a feature it relies on.UnexpectedResponse– Something answered that isn’t a Viper proxy, or its answer couldn’t be read.
Guardian.agent
agent(name: str, *, definition: Definition | None = None, **description: Any) -> AgentAn enrolled agent, described here, to route model clients through.
Takes the same keyword arguments as Definition (model,
instructions, tools, exclude and the rest), or a
definition built already. The definition is checked now; nothing
is sent until the agent’s first request, or Agent.register.
Example:
researcher = guardian.agent(
"researcher",
model=ga.Model("openai", "gpt-4.1"),
instructions="You research.",
tools=[search_docs],
)
client = researcher.bind(OpenAI())Parameters:
- name (
str) – The agent’s name, as enrolled withviper agent add. - definition (
Definition | None) – The agent’s definition, instead of keyword arguments. - description (
Any) – The definition’s keyword arguments.
Raises:
ValueError–nameisn’t a valid agent name.TypeError– Bothdefinitionand keyword arguments were given.DefinitionInvalid– The definition breaks a rule.DefinitionContainsSecret– It holds what looks like a credential.
Guardian.agents
agents(*, refresh: bool = False) -> tuple[str, ...]The agents enrolled on the proxy, sorted.
Parameters:
- refresh (
bool) – Ask the proxy again; it reads its agents when it starts.
Returns:
tuple[str, ...]– The enrolled agents’ names.
Raises:
ProxyUnreachable– Nothing answered at the proxy’s address in time.ProxyIncompatible– The proxy doesn’t serve this SDK’s agent API.UnexpectedResponse– Something answered that isn’t a Viper proxy, or its answer couldn’t be read.
Guardian.skill
skill(name: str, directory: str | os.PathLike[str]) -> SkillA skill for a definition’s skills, its files stored on the proxy.
Reads every regular file in the skill’s directory, hidden ones included, and uploads them. The proxy stores them and returns their CID, the one Viper gives a coding agent’s skill of the same files, so the Agent Gateway checks both alike and editing any file gives the agent a new definition. Symlinks inside the directory are skipped; the directory itself may be a link.
Example:
triage = guardian.skill("triage", "skills/triage")
agent = guardian.agent("planner", model=..., skills=[triage])Parameters:
- name (
str) – The skill’s name, as the agent knows it. - directory (
str | PathLike[str]) – The skill’s directory, the one holding itsSKILL.md.
Returns:
Skill– The skill, with the CID of its stored files.
Raises:
CollectionInvalid–directoryisn’t a skill (a directory with aSKILL.md), holds no files, or has one the proxy can’t take.CollectionTooLarge– The files are over the proxy’s limits (capabilities().limits).DefinitionContainsSecret– A file, or a file’s path, looks like it holds a credential.OSError– A file or directory couldn’t be read.ProxyUnreachable– Nothing answered at the proxy’s address in time.
Guardian.askill
askill(name: str, directory: str | os.PathLike[str]) -> SkillA skill, its files stored on the proxy; the async form of skill.
Parameters:
- name (
str) – The skill’s name, as the agent knows it. - directory (
str | PathLike[str]) – The skill’s directory, the one holding itsSKILL.md.
Returns:
Skill– The skill, with the CID of its stored files.
Raises:
CollectionInvalid– As forskill.CollectionTooLarge– As forskill.DefinitionContainsSecret– As forskill.OSError– A file or directory couldn’t be read.ProxyUnreachable– Nothing answered at the proxy’s address in time.
Guardian.instructions
instructions(path: str, local_path: str | os.PathLike[str], *, backend: str) -> MemoryMemory the agent reads but can’t change, its files stored on the proxy.
For a definition’s memory: an AGENTS.md, say, or a directory
of notes. They are configuration, so they are recorded by their
files’ CID, and the Agent Gateway checks them as it checks a coding agent’s
CLAUDE.md. A file is stored under the name the agent reads it by,
the last part of path, whatever it’s called here; a directory is
read as a skill’s is.
Example:
agents_md = guardian.instructions(
"/AGENTS.md", "docs/agent-notes.md", backend="filesystem"
)
agent = guardian.agent("planner", model=..., memory=[agents_md])Parameters:
- path (
str) – Where the agent reads it, as the agent sees it. - local_path (
str | PathLike[str]) – The file or directory to read it from. - backend (
str) – The kind of storage it’s in, as the framework names it.
Returns:
Memory– The memory, with the CID of its stored files.
Raises:
CollectionInvalid–local_pathisn’t a file or directory, holds no files, or has one the proxy can’t take.CollectionTooLarge– The files are over the proxy’s limits (capabilities().limits).DefinitionContainsSecret– A file, or a file’s path, looks like it holds a credential.OSError– A file or directory couldn’t be read.ProxyUnreachable– Nothing answered at the proxy’s address in time.
Guardian.ainstructions
ainstructions(path: str, local_path: str | os.PathLike[str], *, backend: str) -> MemoryFixed memory, its files stored; the async form of instructions.
Parameters:
- path (
str) – Where the agent reads it, as the agent sees it. - local_path (
str | PathLike[str]) – The file or directory to read it from. - backend (
str) – The kind of storage it’s in, as the framework names it.
Returns:
Memory– The memory, with the CID of its stored files.
Raises:
CollectionInvalid– As forinstructions.CollectionTooLarge– As forinstructions.DefinitionContainsSecret– As forinstructions.OSError– A file or directory couldn’t be read.ProxyUnreachable– Nothing answered at the proxy’s address in time.
Agent
An enrolled agent, ready to route model clients through the proxy.
Made by Guardian.agent. Nothing is sent until the first request
(or register); then the definition is registered once, and every
request names the agent so the proxy signs it with the agent’s key.
An Agent is safe to share between threads and async tasks, and holds
no open connections of its own.
Attributes:
- name – The agent’s name, as enrolled with
viper agent add. - definition – What the agent is.
- guardian – The connection to the proxy it routes through.
- subagent_path – For a route made by
subagent, the sub-agent’s path, such as"critic/fact-checker";Nonefor the agent.
Functions:
- register – Register the definition now, rather than on the first request.
- aregister – Register the definition now; the async form of
register. - subagent – A route through one of the agent’s sub-agents, which signs as the agent.
- bind – A copy of a model client that routes through the proxy as this agent.
- httpx_client – An
httpx.Clientwhose requests go through the proxy as this agent. - httpx_async_client – An
httpx.AsyncClient; the async form ofhttpx_client.
Agent.definition_cid
definition_cid: str | NoneThe registered definition’s content ID, or None before registration.
Agent.register
register() -> strRegister the definition now, rather than on the first request.
Call it at start-up so a misconfigured deployment fails there, with a typed error, rather than on its first user. Once registered, calling it again returns the content ID without contacting the proxy; after a proxy restart, the agent’s next model request registers it again.
Returns:
str– The definition’s content ID, as the proxy stored it.
Raises:
ProxyUnreachable– Nothing answered at the proxy’s address.ProxyIncompatible– The proxy doesn’t speak this SDK’s agent API.AgentNotEnrolled– The proxy has no agent of this name; runviper agent addandviper agent register, then restart the proxy.DefinitionError– The proxy refused the definition.UnexpectedResponse– Something answered that isn’t a Viper proxy, or its answer couldn’t be read.
Agent.aregister
aregister() -> strRegister the definition now; the async form of register.
However many threads and tasks ask at once, the proxy sees one
registration and they all share its outcome. Waiting holds no worker
thread, and cancelling the wait (anyio.move_on_after,
asyncio.wait_for) works.
Returns:
str– The definition’s content ID, as the proxy stored it.
Raises:
ProxyUnreachable– As forregister.ProxyIncompatible– As forregister.AgentNotEnrolled– As forregister.DefinitionError– As forregister.UnexpectedResponse– As forregister.
Agent.subagent
subagent(path: str) -> AgentA route through one of the agent’s sub-agents, which signs as the agent.
Example:
reviewing = writer.subagent("critic").bind(OpenAI())
checking = writer.subagent("critic/fact-checker")Parameters:
- path (
str) – The sub-agent’s name, or a/-separated path to a nested one, relative to this route.
Returns:
Agent– A route that shares this agent’s registration.
Raises:
SubagentUnknown– The definition declares no sub-agent atpath.
Agent.bind
bind(client: Any) -> AnyA copy of a model client that routes through the proxy as this agent.
Your client is left as it was. A client still aimed at the provider’s own API is pointed at the proxy; one aimed anywhere else is refused.
Example:
client = researcher.bind(OpenAI())
client.chat.completions.create(model="gpt-4.1", messages=[...])
llm = researcher.bind(ChatOpenAI(model="gpt-4.1"))
llm.bind_tools([search_docs]).invoke("...")A LangChain chat model’s copy routes every call it makes, sync or
async, streamed or not, and through bind_tools() and
with_structured_output(); bind the model before those.
Parameters:
- client (
Any) –OpenAI,AsyncOpenAI,AnthropicorAsyncAnthropic; or LangChain’sChatOpenAIorChatAnthropic.
Returns:
Any– A client or chat model of the same type.
Raises:
ModelNotRoutedThroughProxy– The client is aimed somewhere other than the proxy or the provider’s own API, or setswebsocket_base_url.ValueError– The client is aimed at the proxy with the wrong path (OpenAI’s clients need/v1, Anthropic’s none); or a LangChain model isn’t one the definition describes exactly (for a sub-agent route, one the sub-agent’s does), or sends a setting no definition can record, as its requests would be signed under a definition that doesn’t describe them.TypeError– It isn’t a clientbind()knows (usehttpx_clientfor others), or is a LangChain model that can’t be routed: a model name, one wrapped bybind_tools(),with_structured_output(),with_retry(),with_fallbacks()orwith_config(), one made withconfigurable_fields, one given its ownclientorasync_client, or another kind of chat model.
Agent.httpx_client
httpx_client(**options: Any) -> httpx.ClientAn httpx.Client whose requests go through the proxy as this agent.
For any SDK that takes an httpx client. Its base URL is the
proxy’s, and a request anywhere else is refused rather than sent.
Parameters:
- options (
Any) –httpx.Clientoptions:headers,timeout,auth,follow_redirects,event_hooksand the like, and connection options (verify,cert,http2,limits,retries,local_address). Options that could send requests elsewhere (transport,mounts,proxy) are refused, as istrust_env, which wouldn’t apply. OpenAI’s Realtime (WebSocket) connections don’t use this client, so an SDK given it routes only its HTTP requests.
Raises:
TypeError– An option isn’t one the agent’s client takes.
Agent.httpx_async_client
httpx_async_client(**options: Any) -> httpx.AsyncClientAn httpx.AsyncClient; the async form of httpx_client.