{
  "$id": "https://ns.eqtylab.io/viper/agent-definition/v1",
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "Agent definition v1",
  "description": "What an agent is: its models, prompts, tools, sub-agents and declared capabilities. This schema checks shape and single-value rules. The agent-definition crate's validate() is the authority and also enforces rules relating values to each other, length limits in UTF-8 bytes (maxLength counts characters), safe integers inside extensions, middleware config and tool inputSchema, full URL parsing and credential detection; producers must run it or an equivalent check. It also reads the alternate spellings its parser allows (a struct as an array, an enum as a one-key map), storing the usual one, so the content ID is the same; producers write the usual one, which this schema describes.",
  "type": "object",
  "properties": {
    "description": {
      "description": "What the agent is for. A parent uses it to decide when to delegate to a\nsub-agent.",
      "type": [
        "string",
        "null"
      ],
      "pattern": "[^\\u0009-\\u000D\\u0020\\u0085\\u00A0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]"
    },
    "extensions": {
      "description": "Framework-specific data, keyed by framework name, so additions don't\nneed a new schema version.",
      "type": "object",
      "additionalProperties": true
    },
    "framework": {
      "$ref": "#/$defs/Framework"
    },
    "graph": {
      "$ref": "#/$defs/Graph"
    },
    "guardrails": {
      "type": "array",
      "items": {
        "type": "string",
        "pattern": "[^\\u0009-\\u000D\\u0020\\u0085\\u00A0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]"
      },
      "uniqueItems": true
    },
    "mcpServers": {
      "description": "MCP servers the agent's tools come from. An empty list means none were\nrecorded, not that the agent uses none.",
      "type": "array",
      "items": {
        "$ref": "#/$defs/McpServer"
      },
      "maxItems": 64
    },
    "memory": {
      "description": "Text the agent loads into its prompt from storage. Memory the agent\ncan't change is configuration, content-addressed like a skill (`cid`).\nMemory it writes as it works is recorded by where it lives and whose it\nis (`scope`), not by its contents, which change with every run.",
      "type": "array",
      "items": {
        "$ref": "#/$defs/Memory"
      },
      "maxItems": 64
    },
    "middleware": {
      "description": "Middleware in execution order, which is part of its behaviour. It\ncovers what the static fields can't show, such as dynamic prompts or\nmodel fallback.",
      "type": "array",
      "items": {
        "$ref": "#/$defs/Middleware"
      }
    },
    "models": {
      "description": "Every model the agent calls, each once: its own first, then those its\nmiddleware calls (fallbacks, a summarizer, a tool selector). A\nmiddleware's `config` names one by its place here, `{\"@model\": 1}`.",
      "type": "array",
      "items": {
        "$ref": "#/$defs/Model"
      },
      "minItems": 1
    },
    "name": {
      "type": "string",
      "pattern": "^[a-z0-9][a-z0-9._-]{0,63}$"
    },
    "permissions": {
      "type": "array",
      "items": {
        "type": "string",
        "pattern": "[^\\u0009-\\u000D\\u0020\\u0085\\u00A0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]"
      },
      "uniqueItems": true
    },
    "prompts": {
      "description": "The agent's configured instructions, never conversation messages.\nPrompts rendered per request are recorded as the middleware that\nrenders them, so the definition only changes when configuration does.",
      "type": "array",
      "items": {
        "$ref": "#/$defs/Prompt"
      }
    },
    "schema": {
      "type": "string",
      "const": "viper/agent-definition/v1"
    },
    "skills": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/Skill"
      },
      "maxItems": 256
    },
    "sources": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/Source"
      }
    },
    "subAgents": {
      "description": "Agents declared inside this one, described with the same vocabulary.\nThey share this agent's identity, so their configuration is part of\nthis definition.",
      "type": "array",
      "items": {
        "$ref": "#"
      },
      "maxItems": 64
    },
    "tools": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/Tool"
      },
      "maxItems": 256
    },
    "version": {
      "description": "A label chosen by the developer; informational only.",
      "type": [
        "string",
        "null"
      ],
      "maxLength": 64,
      "minLength": 1,
      "pattern": "[^\\u0009-\\u000D\\u0020\\u0085\\u00A0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]"
    }
  },
  "additionalProperties": false,
  "required": [
    "schema",
    "name",
    "framework",
    "models"
  ],
  "$defs": {
    "Edge": {
      "description": "A graph edge as [source, target, conditional].",
      "type": "array",
      "items": false,
      "minItems": 3,
      "prefixItems": [
        {
          "type": "string"
        },
        {
          "type": "string"
        },
        {
          "type": "boolean"
        }
      ]
    },
    "Framework": {
      "type": "object",
      "properties": {
        "name": {
          "type": "string",
          "maxLength": 64,
          "minLength": 1,
          "pattern": "[^\\u0009-\\u000D\\u0020\\u0085\\u00A0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]"
        },
        "packages": {
          "description": "Package versions, so a dependency upgrade yields a new definition.",
          "type": "object",
          "additionalProperties": {
            "type": "string"
          }
        }
      },
      "additionalProperties": false,
      "required": [
        "name"
      ]
    },
    "Graph": {
      "type": "object",
      "properties": {
        "edges": {
          "type": "array",
          "default": [],
          "items": {
            "$ref": "#/$defs/Edge"
          }
        },
        "nodes": {
          "type": "array",
          "default": [],
          "items": {
            "type": "string"
          },
          "uniqueItems": true
        }
      },
      "additionalProperties": false
    },
    "McpServer": {
      "description": "Where an MCP server is reached. Headers and environment are deliberately\nnot representable: they are where such servers' credentials usually live.",
      "type": "object",
      "properties": {
        "args": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "command": {
          "description": "For `stdio`: the program started, and its arguments.",
          "type": [
            "string",
            "null"
          ],
          "pattern": "[^\\u0009-\\u000D\\u0020\\u0085\\u00A0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]"
        },
        "name": {
          "type": "string",
          "maxLength": 64,
          "minLength": 1,
          "pattern": "[^\\u0009-\\u000D\\u0020\\u0085\\u00A0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]"
        },
        "transport": {
          "$ref": "#/$defs/McpTransport"
        },
        "url": {
          "description": "For network transports.",
          "type": [
            "string",
            "null"
          ],
          "pattern": "^[Hh][Tt][Tt][Pp][Ss]?://([^\\x00-\\x1f\\x7f\\u0020\\u0085\\u00A0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000/?#@:\\[\\]]+|\\[[0-9A-Fa-f:.]+\\])(:[0-9]*)?([/?#][^\\x00-\\x1f\\x7f\\u0020\\u0085\\u00A0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]*)?$"
        }
      },
      "additionalProperties": false,
      "required": [
        "name",
        "transport"
      ]
    },
    "McpTransport": {
      "type": "string",
      "enum": [
        "stdio",
        "sse",
        "streamable_http"
      ]
    },
    "Memory": {
      "description": "One source of memory: a file or directory the agent loads into its prompt.",
      "type": "object",
      "properties": {
        "backend": {
          "description": "The kind of storage it's in, as the framework names it (`filesystem`,\n`store`, `state`).",
          "type": "string",
          "maxLength": 64,
          "minLength": 1,
          "pattern": "[^\\u0009-\\u000D\\u0020\\u0085\\u00A0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]"
        },
        "cid": {
          "description": "For memory the agent can't change: the iroh collection CID of its\nfiles, as for a skill (a single file is a collection of one, keyed by\nthe name the agent reads it by, the last part of `path`; a `path`\nending in `/` is a directory). Absent for memory the agent can change.",
          "type": [
            "string",
            "null"
          ],
          "pattern": "^bagaachra[a-z2-7]{51}[aq]$"
        },
        "path": {
          "description": "Where the agent reads it, as the agent sees it; never a host path.",
          "type": "string",
          "pattern": "[^\\u0009-\\u000D\\u0020\\u0085\\u00A0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]"
        },
        "scope": {
          "description": "For memory the agent can change: whose it is. Absent otherwise.",
          "anyOf": [
            {
              "$ref": "#/$defs/MemoryScope"
            },
            {
              "type": "null"
            }
          ]
        },
        "writable": {
          "description": "Whether the agent can change it as it runs.",
          "type": "boolean"
        }
      },
      "additionalProperties": false,
      "required": [
        "path",
        "backend",
        "writable"
      ]
    },
    "MemoryScope": {
      "description": "Whose memory an agent that writes it reads.",
      "type": "object",
      "properties": {
        "kind": {
          "$ref": "#/$defs/MemoryScopeKind"
        },
        "sourceHash": {
          "description": "For `namespace`: `sha256:<hex>` of the code that chooses the namespace,\nwhich decides whose memory loads. Absent otherwise.",
          "type": [
            "string",
            "null"
          ],
          "pattern": "^sha256:[0-9a-f]{64}$"
        }
      },
      "additionalProperties": false,
      "required": [
        "kind"
      ]
    },
    "MemoryScopeKind": {
      "oneOf": [
        {
          "description": "One memory for every user and thread.",
          "type": "string",
          "const": "shared"
        },
        {
          "description": "A memory per namespace, such as per user.",
          "type": "string",
          "const": "namespace"
        },
        {
          "description": "A memory per thread, held in the thread's own state: none is carried\nfrom one thread to the next.",
          "type": "string",
          "const": "thread"
        }
      ]
    },
    "Middleware": {
      "description": "Middleware identified by name. Its code and its configuration both shape\nbehaviour, so a change to either is a new definition.",
      "type": "object",
      "properties": {
        "config": {
          "description": "Values the middleware was constructed with, such as a prompt template\nor a call limit. A model it calls is written as its place in the\ndefinition's `models`, `{\"@model\": 1}`. Never values computed per\nrequest.",
          "type": "object",
          "additionalProperties": true
        },
        "name": {
          "type": "string",
          "pattern": "[^\\u0009-\\u000D\\u0020\\u0085\\u00A0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]"
        },
        "sourceHash": {
          "description": "`sha256:<hex>` of the middleware's source, when it is available.",
          "type": [
            "string",
            "null"
          ],
          "pattern": "^sha256:[0-9a-f]{64}$"
        }
      },
      "additionalProperties": false,
      "required": [
        "name"
      ]
    },
    "Model": {
      "type": "object",
      "properties": {
        "model": {
          "type": "string",
          "pattern": "[^\\u0009-\\u000D\\u0020\\u0085\\u00A0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]"
        },
        "provider": {
          "type": "string",
          "pattern": "[^\\u0009-\\u000D\\u0020\\u0085\\u00A0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]"
        },
        "settings": {
          "$ref": "#/$defs/ModelSettings"
        }
      },
      "additionalProperties": false,
      "required": [
        "provider",
        "model"
      ]
    },
    "ModelSettings": {
      "description": "Settings that change a model's behaviour. Every level is a closed set of\ntyped fields, so credentials, headers and client options can't be recorded.\nThe one header value recorded is `betas`, as feature names held to a\npattern of lowercase words and hyphens.",
      "type": "object",
      "properties": {
        "betas": {
          "description": "Beta features of the provider's API that the model's requests may\ncarry, by name, as Anthropic's `anthropic-beta` header takes them:\nsorted, with no duplicates. A request may carry fewer, since some are\nonly sent with the tools that need them, but no others.",
          "type": "array",
          "items": {
            "type": "string",
            "maxLength": 64,
            "minLength": 1,
            "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$"
          },
          "uniqueItems": true
        },
        "effort": {
          "type": [
            "string",
            "null"
          ]
        },
        "frequencyPenalty": {
          "type": [
            "number",
            "null"
          ],
          "format": "double"
        },
        "maxCompletionTokens": {
          "type": [
            "integer",
            "null"
          ],
          "format": "uint64",
          "maximum": 9007199254740991,
          "minimum": 0
        },
        "maxTokens": {
          "type": [
            "integer",
            "null"
          ],
          "format": "uint64",
          "maximum": 9007199254740991,
          "minimum": 0
        },
        "presencePenalty": {
          "type": [
            "number",
            "null"
          ],
          "format": "double"
        },
        "reasoning": {
          "$ref": "#/$defs/Reasoning"
        },
        "reasoningEffort": {
          "type": [
            "string",
            "null"
          ]
        },
        "seed": {
          "type": [
            "integer",
            "null"
          ],
          "format": "int64",
          "maximum": 9007199254740991,
          "minimum": -9007199254740991
        },
        "serviceTier": {
          "type": [
            "string",
            "null"
          ]
        },
        "stop": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "temperature": {
          "type": [
            "number",
            "null"
          ],
          "format": "double"
        },
        "thinking": {
          "anyOf": [
            {
              "$ref": "#/$defs/Thinking"
            },
            {
              "type": "null"
            }
          ]
        },
        "topK": {
          "type": [
            "integer",
            "null"
          ],
          "format": "uint64",
          "maximum": 9007199254740991,
          "minimum": 0
        },
        "topP": {
          "type": [
            "number",
            "null"
          ],
          "format": "double"
        }
      },
      "additionalProperties": false
    },
    "Prompt": {
      "description": "A system or developer prompt, held inline or by content ID when it is too\nlarge to inline.\nExactly one of `content` and `content_cid` is expected.",
      "type": "object",
      "properties": {
        "content": {
          "description": "At most 16 KiB of UTF-8; larger prompts go by content ID.",
          "type": [
            "string",
            "null"
          ],
          "maxLength": 16384
        },
        "contentCid": {
          "type": [
            "string",
            "null"
          ],
          "pattern": "^bafkr4i[a-z2-7]{51}[aeimquy4]$"
        },
        "role": {
          "type": "string",
          "enum": [
            "system",
            "developer"
          ]
        }
      },
      "additionalProperties": false,
      "required": [
        "role"
      ]
    },
    "Reasoning": {
      "description": "Reasoning configuration, as OpenAI's reasoning models take it.",
      "type": "object",
      "properties": {
        "effort": {
          "type": [
            "string",
            "null"
          ]
        },
        "summary": {
          "type": [
            "string",
            "null"
          ]
        }
      },
      "additionalProperties": false
    },
    "Skill": {
      "description": "A skill the agent can load: a directory of files, its instructions and\nwhatever they refer to.",
      "type": "object",
      "properties": {
        "cid": {
          "description": "The iroh collection CID of every regular file in the skill's directory,\nkeyed by its path inside it: hidden files included, symlinks inside the\nskill skipped (the directory itself may be a link, followed when it\nholds a regular `SKILL.md`). That is how Viper collects a coding agent's\nskill, so this is the CID Viper gives it, so an edit to any file is a\nnew definition. Producers don't compute it themselves: content IDs\ncome from Viper's own code.",
          "type": "string",
          "pattern": "^bagaachra[a-z2-7]{51}[aq]$"
        },
        "name": {
          "type": "string",
          "pattern": "[^\\u0009-\\u000D\\u0020\\u0085\\u00A0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]"
        }
      },
      "additionalProperties": false,
      "required": [
        "name",
        "cid"
      ]
    },
    "Source": {
      "description": "A source file the developer chose to fingerprint.",
      "type": "object",
      "properties": {
        "path": {
          "type": "string",
          "pattern": "[^\\u0009-\\u000D\\u0020\\u0085\\u00A0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]"
        },
        "sha256": {
          "type": "string",
          "pattern": "^[0-9a-f]{64}$"
        }
      },
      "additionalProperties": false,
      "required": [
        "path",
        "sha256"
      ]
    },
    "Thinking": {
      "description": "Extended-thinking configuration, as Anthropic's models take it.",
      "type": "object",
      "properties": {
        "budgetTokens": {
          "type": [
            "integer",
            "null"
          ],
          "format": "uint64",
          "maximum": 9007199254740991,
          "minimum": 0
        },
        "type": {
          "description": "`enabled`, `disabled` or `adaptive`.",
          "type": "string"
        }
      },
      "additionalProperties": false,
      "required": [
        "type"
      ]
    },
    "Tool": {
      "type": "object",
      "properties": {
        "description": {
          "type": "string"
        },
        "inputSchema": {
          "description": "JSON Schema for the tool's arguments.",
          "type": "object",
          "additionalProperties": true
        },
        "name": {
          "type": "string",
          "pattern": "[^\\u0009-\\u000D\\u0020\\u0085\\u00A0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]"
        },
        "sourceHash": {
          "description": "`sha256:<hex>` of the tool's source, when it is available.",
          "type": [
            "string",
            "null"
          ],
          "pattern": "^sha256:[0-9a-f]{64}$"
        }
      },
      "additionalProperties": false,
      "required": [
        "name",
        "description",
        "inputSchema"
      ]
    }
  }
}
