EQTY LabDocs

Definition fields

Every field of an agent's definition: models and their settings, prompts, sub-agents, middleware, MCP servers, memory, graph, sources and the rest.

View as Markdown

ga.Definition and guardian.agent() take a keyword argument for every field of the definition schema, in Python spelling (mcp_servers for mcpServers). Describe an agent covers tools, skills, instructions files, credentials and exclude. This page covers the rest, using one definition with every field set. It reads the files Before you start shows, and registers as the planner agent:

python
from pathlib import Path
from typing import Any

import guardian_agent as ga

guardian = ga.Guardian()


@ga.tool
def search_docs(query: str) -> list[str]:
    """Search the internal documentation."""
    return []


def by_user(config: dict[str, Any]) -> str:
    """Whose learned memory loads: one per user."""
    return str(config["configurable"]["user_id"])


# Stored on the proxy and recorded by their files' content ID
triage = guardian.skill("triage", "skills/triage")
agents_md = guardian.instructions(
    "/AGENTS.md", "docs/agent-notes.md", backend="filesystem"
)

definition = ga.Definition(
    description="Plans support work and hands each step to a tool or the critic.",
    version="2.3.0",
    framework=ga.Framework("custom", packages=["httpx"]),  # installed version looked up
    model=[
        ga.Model("openai", "gpt-4.1", temperature=0, max_tokens=2048),
        ga.Model(
            "anthropic",
            "claude-sonnet-5",
            max_tokens=4096,
            thinking=ga.Thinking("enabled", budget_tokens=2048),
        ),
    ],
    instructions=[
        ga.System("You plan support work."),
        ga.Developer(Path("prompts/planner.md")),  # read now, as UTF-8
    ],
    tools=[search_docs],
    subagents=[
        ga.SubAgent(
            "critic",
            description="Reviews plans for gaps.",
            model=ga.Model("openai", "gpt-4.1-mini"),
            instructions="You review plans.",
        ),
    ],
    middleware=[ga.Middleware("RetryOnRefusal", config={"attempts": 2})],
    mcp_servers=[
        ga.McpServer.http("docs", "https://mcp.internal/docs"),
        ga.McpServer.stdio(
            "files", "npx", "-y", "@modelcontextprotocol/server-filesystem", "/data"
        ),
    ],
    skills=[triage],
    memory=[
        agents_md,
        ga.Memory.learned("/learned.md", backend="store", namespace=by_user),
    ],
    graph=ga.Graph(edges=[("model", "tools", True), ("tools", "model")]),
    permissions=["read:docs"],
    guardrails=["pii-filter"],
    sources=[ga.Source(Path("lib/routing.py"))],  # code the tools read, hashed now
    extensions={"custom": {"region": "eu"}},
)

planner = guardian.agent("planner", definition=definition)
print(f"planner is registered as {planner.register()}")
print(sorted(definition.document("planner")))

Only model is required. Lists whose order means nothing (tools, sub-agents, skills, memory, MCP servers, sources, permissions, guardrails, the graph) are sorted for you, so the same agent always gives the same definition. Models (in fallback order), instructions and middleware keep the order you give, since that order is behaviour. definition.document(name) returns the definition as the proxy receives it for an agent of that name.

Models

model takes one ga.Model, or a list in fallback order. A ga.Model names the provider and the model as the provider’s API takes it (ga.Model("openai", "gpt-4.1")), with the settings it’s called with.

Settings are an allowlist, so a client’s headers, keys or other options can’t end up in a definition: temperature, top_p, top_k, max_tokens, max_completion_tokens, seed, stop, frequency_penalty, presence_penalty, reasoning_effort, effort, service_tier, and:

  • betas=[...], the beta features the model’s requests may carry, as Anthropic’s API names them ("context-management-2025-06-27"): lowercase letters and digits joined by single hyphens, up to 64 bytes each. They’re held sorted, once each. A request may carry fewer of them, since a client adds some only with the tools that need them, but no others.
  • reasoning=ga.Reasoning(effort=..., summary=...), as OpenAI’s reasoning models take it.
  • thinking=ga.Thinking("enabled", budget_tokens=2048), as Anthropic’s models take it. The type is "enabled", "disabled" or "adaptive".

Leave a setting out unless your requests set it, and record the ones they set, including a library default your client sends. With the langchain extra, model also takes a LangChain chat model, described by its provider, name and the settings that change its answers.

Instructions

instructions takes a string, for a system prompt, or a list of strings, ga.System(...) and ga.Developer(...) prompts, in order. Each prompt takes text or a pathlib.Path, read when the definition is made, as UTF-8; a file that can’t be read raises OSError, and one that isn’t UTF-8 raises ValueError. The proxy stores any prompt over 16 KiB apart, by its own content ID. An agent’s prompts together, and each sub-agent’s, may total 1 MiB, counting a blank line between prompts (capabilities().limits.prompt_bytes), within the 4 MiB the whole registration may take. The SDK doesn’t check those totals when the definition is made, so a definition over them is refused when it’s registered: register() raises ga.DefinitionTooLarge, and a bound client’s first request fails with it (ga.proxy_error()).

Sub-agents

subagents takes ga.SubAgents: agents declared inside this one, which sign as it (Agents and identities). A ga.SubAgent takes the same arguments as ga.Definition, plus its name, and needs a description: the parent uses it to decide when to delegate.

  • Without a framework of its own, a sub-agent inherits its parent’s framework name; package versions are recorded once, on the parent.
  • Sub-agents nest at most three deep, and number at most 64 in all.
  • A sub-agent’s exclude pointers are relative to the sub-agent, and recorded in its own extensions.

Framework and version

  • framework: what the agent is built with, ga.Framework("custom") by default. packages takes versions by name ({"langgraph": "1.2.13"}), or a list of names whose installed versions are looked up; a listed package that isn’t installed raises ValueError. Package versions are recorded on purpose: upgrading one gives the agent a new definition.
  • version: your own label for this version of the agent, such as "2.3.0".
  • description: what the agent is for.

Middleware

middleware takes ga.Middleware(name, config=...): code that runs around the model calls, such as retries or routing, in the order it runs. config holds the values it was made with (a prompt template, fallback models, limits), never values computed per request. source_hash records a sha256:<hex> of its code; the framework integrations fill it in for the middleware they find.

MCP servers

mcp_servers takes the MCP servers the agent uses:

  • ga.McpServer.http(name, url) for streamable HTTP;
  • ga.McpServer.sse(name, url) for server-sent events;
  • ga.McpServer.stdio(name, command, *args) for a server the agent starts.

Headers and environment variables can’t be recorded, and a credential in a URL or an argument is refused. Tools loaded with langchain-mcp-adapters add their own server for you.

Memory

memory takes ga.Memorys, each where the agent reads it (path) and the kind of storage it’s in (backend):

  • Instructions the agent can’t change come from guardian.instructions(...), which uploads their files (Describe an agent), or ga.Memory.instructions(path, backend=..., cid=...) for files already stored. They’re recorded by their files’ content ID.
  • Memory the agent writes as it works comes from ga.Memory.learned(path, backend=...), recorded by where it lives and whose it is, never by its contents:
    • by default it’s shared, one memory for every user and thread;
    • namespace= takes the function that chooses whose memory loads, such as one per user. Its source is fingerprinted, so it must be a plain function whose source shows everything it uses: a lambda, a closure, or a function with default arguments or attributes set on it is refused. Globals it reads are seen by name only, so keep what decides the namespace in its body;
    • thread=True gives each thread its own memory, held in that thread’s state, so none carries from one thread to the next.

Graph

graph takes a ga.Graph of the agent’s nodes and the edges between them. An edge is (source, target), or (source, target, True) for a conditional one. Nodes an edge names are included for you; list any others in nodes.

Permissions, guardrails, sources and extensions

  • permissions and guardrails: strings the agent declares, such as "read:docs" and "pii-filter", recorded sorted, each once.
  • sources: ga.Source(path) for each file whose contents should be part of the definition, hashed when the definition is made, so editing the file gives a new definition. Use it for code or data your tools and middleware read, which their own hashes don’t cover. The path is recorded as given, with / separators, so relative paths read best.
  • extensions: framework-specific data, keyed by framework name. The guardian key is reserved (exclude records under it), and using it raises ValueError.