---
title: "Definition fields"
description: "Every field of an agent's definition: models and their settings, prompts, sub-agents, middleware, MCP servers, memory, graph, sources and the rest."
source: "Guardian Agent SDK"
---
`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](./describe-an-agent.mdx) 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](./index.mdx#the-files-they-read) shows, and registers as the `planner` agent:

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

```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")))
```

{/* end generated */}

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.SubAgent`s: agents declared inside this one, which sign as it ([Agents and identities](../concepts/agents-and-identities.mdx#sub-agents)). 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.Memory`s, 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](./describe-an-agent.mdx#skills-and-instructions)), 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`.