EQTY LabDocs

Guardian and Agent

Guardian, the connection to the local Viper proxy, and Agent, an enrolled agent you route model clients through.

View as Markdown

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:

python
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, as viper proxy printed it, such as "http://127.0.0.1:8080". Defaults to VIPER_PROXY_URL. The proxy answers only callers on its own machine that address it as localhost or 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 – Neither proxy_url nor VIPER_PROXY_URL is set, the one given isn’t an http(s)://host[:port] base URL, or timeout isn’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

python
proxy_url: str

The proxy’s base URL, without a trailing slash.

Guardian.capabilities

python
capabilities(*, refresh: bool = False) -> Capabilities

What 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

python
acapabilities(*, refresh: bool = False) -> Capabilities

What the proxy supports; the async form of capabilities.

Parameters:

  • refresh (bool) – Ask the proxy again, as for capabilities.

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

python
agent(name: str, *, definition: Definition | None = None, **description: Any) -> Agent

An 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:

python
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 with viper agent add.
  • definition (Definition | None) – The agent’s definition, instead of keyword arguments.
  • description (Any) – The definition’s keyword arguments.

Raises:

  • ValueError – name isn’t a valid agent name.
  • TypeError – Both definition and keyword arguments were given.
  • DefinitionInvalid – The definition breaks a rule.
  • DefinitionContainsSecret – It holds what looks like a credential.

Guardian.agents

python
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

python
skill(name: str, directory: str | os.PathLike[str]) -> Skill

A 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:

python
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 its SKILL.md.

Returns:

  • Skill – The skill, with the CID of its stored files.

Raises:

  • CollectionInvalid – directory isn’t a skill (a directory with a SKILL.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

python
askill(name: str, directory: str | os.PathLike[str]) -> Skill

A 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 its SKILL.md.

Returns:

  • Skill – The skill, with the CID of its stored files.

Raises:

  • CollectionInvalid – As for skill.
  • CollectionTooLarge – As for skill.
  • DefinitionContainsSecret – As for skill.
  • OSError – A file or directory couldn’t be read.
  • ProxyUnreachable – Nothing answered at the proxy’s address in time.

Guardian.instructions

python
instructions(path: str, local_path: str | os.PathLike[str], *, backend: str) -> Memory

Memory 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:

python
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_path isn’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

python
ainstructions(path: str, local_path: str | os.PathLike[str], *, backend: str) -> Memory

Fixed 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 for instructions.
  • CollectionTooLarge – As for instructions.
  • DefinitionContainsSecret – As for instructions.
  • 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"; None for 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.Client whose requests go through the proxy as this agent.
  • httpx_async_client – An httpx.AsyncClient; the async form of httpx_client.

Agent.definition_cid

python
definition_cid: str | None

The registered definition’s content ID, or None before registration.

Agent.register

python
register() -> str

Register 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; run viper agent add and viper 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

python
aregister() -> str

Register 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 for register.
  • ProxyIncompatible – As for register.
  • AgentNotEnrolled – As for register.
  • DefinitionError – As for register.
  • UnexpectedResponse – As for register.

Agent.subagent

python
subagent(path: str) -> Agent

A route through one of the agent’s sub-agents, which signs as the agent.

Example:

python
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 at path.

Agent.bind

python
bind(client: Any) -> Any

A 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:

python
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, Anthropic or AsyncAnthropic; or LangChain’s ChatOpenAI or ChatAnthropic.

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 sets websocket_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 client bind() knows (use httpx_client for others), or is a LangChain model that can’t be routed: a model name, one wrapped by bind_tools(), with_structured_output(), with_retry(), with_fallbacks() or with_config(), one made with configurable_fields, one given its own client or async_client, or another kind of chat model.

Agent.httpx_client

python
httpx_client(**options: Any) -> httpx.Client

An 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.Client options: headers, timeout, auth, follow_redirects, event_hooks and the like, and connection options (verify, cert, http2, limits, retries, local_address). Options that could send requests elsewhere (transport, mounts, proxy) are refused, as is trust_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

python
httpx_async_client(**options: Any) -> httpx.AsyncClient

An httpx.AsyncClient; the async form of httpx_client.