{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "$schema": {
      "description": "Path or URL of the JSON Schema an editor validates this file against; ignored by the app",
      "type": "string"
    },
    "activation": {
      "description": "When a turn starts: how a human’s fragments fold into one turn (§4.4)",
      "default": {},
      "type": "object",
      "properties": {
        "debounce": {
          "description": "The window before a turn starts, during which further fragments fold into it (§4.4)",
          "default": {},
          "type": "object",
          "properties": {
            "ceilingMs": {
              "default": 15000,
              "description": "Hard limit on how long folding may delay a turn, so a human typing steadily still gets a response",
              "type": "integer",
              "exclusiveMinimum": 0,
              "maximum": 9007199254740991
            },
            "windowMs": {
              "default": 750,
              "description": "How long to wait after each message before starting a turn, folding fragments into one turn for free (§4.4). Past this the running turn folds them instead, at the cost of a discarded completion. Keep it well under 5s: it is also how long the typing indicator must cover before the turn lights its own.",
              "type": "integer",
              "exclusiveMinimum": 0,
              "maximum": 9007199254740991
            }
          },
          "additionalProperties": false
        },
        "foldLimit": {
          "default": 3,
          "description": "How many times one running turn may discard its completion to absorb a further fragment (§4.4). Bounds the folding after a turn exists, as the debounce ceiling bounds the window before it.",
          "type": "integer",
          "exclusiveMinimum": 0,
          "maximum": 9007199254740991
        }
      },
      "additionalProperties": false
    },
    "agentDefaults": {
      "description": "Values every agent runs with unless its own entry states otherwise — the same keys, the same shapes. Grants and identity are never defaulted.",
      "default": {},
      "type": "object",
      "properties": {
        "completionTimeLimitMs": {
          "default": 1200000,
          "type": "integer",
          "exclusiveMinimum": 0,
          "maximum": 9007199254740991,
          "description": "How long one model completion may run, from the request to its last byte, before it is aborted, nothing of it kept, and the model told so; a second in the same turn ends the turn (§7.1). The idle timeout under inference still cuts a provider that goes quiet sooner."
        },
        "contextBudgetTokens": {
          "description": "Every agent's budget unless it states its own. Omit to give each agent 25% of its turn ceiling.",
          "type": "integer",
          "exclusiveMinimum": 0,
          "maximum": 9007199254740991
        },
        "model": {
          "oneOf": [
            {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string",
                  "enum": [
                    "deepseek-v4-flash",
                    "deepseek-v4-pro"
                  ]
                },
                "provider": {
                  "type": "string",
                  "const": "deepseek"
                },
                "reasoningEffort": {
                  "description": "How hard the model thinks before it answers, in DeepSeek’s own vocabulary: none turns thinking off. Omit for the provider’s default, which is high.",
                  "type": "string",
                  "enum": [
                    "none",
                    "low",
                    "high",
                    "max"
                  ]
                }
              },
              "required": [
                "name",
                "provider"
              ],
              "additionalProperties": false
            },
            {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string",
                  "enum": [
                    "anthropic/claude-fable-5.1",
                    "anthropic/claude-opus-5",
                    "anthropic/claude-sonnet-5",
                    "deepseek/deepseek-v4-flash",
                    "deepseek/deepseek-v4-pro",
                    "deepseek/deepseek-v4.1-flash",
                    "openai/gpt-5.6-luna",
                    "openai/gpt-5.6-luna-pro",
                    "openai/gpt-5.6-sol",
                    "openai/gpt-5.6-sol-pro",
                    "openai/gpt-5.6-terra",
                    "openai/gpt-5.6-terra-pro",
                    "openai/gpt-6-astra",
                    "z-ai/glm-5.3",
                    "z-ai/glm-5.3-flash",
                    "~anthropic/claude-fable-latest",
                    "~anthropic/claude-opus-latest",
                    "~anthropic/claude-sonnet-latest",
                    "~deepseek/deepseek-flash-latest",
                    "~deepseek/deepseek-pro-latest",
                    "~openai/gpt-astra-latest",
                    "~openai/gpt-luna-latest",
                    "~openai/gpt-sol-latest",
                    "~openai/gpt-terra-latest"
                  ]
                },
                "provider": {
                  "type": "string",
                  "const": "openrouter"
                },
                "reasoningEffort": {
                  "description": "How hard the model thinks before it answers, in OpenRouter’s unified vocabulary, mapped onto what the model supports. Omit for the model’s default; a Claude model then reasons without extended thinking.",
                  "type": "string",
                  "enum": [
                    "none",
                    "minimal",
                    "low",
                    "medium",
                    "high",
                    "xhigh",
                    "max"
                  ]
                },
                "upstreams": {
                  "description": "The upstreams to prefer for this model, in order, sent to OpenRouter as its provider order. Each upstream caches prompts separately, so holding a model to one keeps its cache warm (§3.8); the model itself never changes. OpenRouter falls back to its own choice when none of them can serve. Omit to leave routing to OpenRouter.",
                  "minItems": 1,
                  "type": "array",
                  "items": {
                    "type": "string",
                    "pattern": "^[a-z0-9][a-z0-9-]*(?:\\/[a-z0-9][a-z0-9-]*)?$",
                    "description": "An upstream as OpenRouter names it in a provider order: its lowercase slug, optionally with one variant after a slash, such as relace or streamlake/fp8. A bare slug covers every variant that upstream serves."
                  }
                }
              },
              "required": [
                "name",
                "provider"
              ],
              "additionalProperties": false
            }
          ],
          "description": "The model an agent thinks with. Its provider must be configured under providers."
        },
        "personality": {
          "type": "string",
          "enum": [
            "candid"
          ],
          "description": "An optional stance added after the shared behavioral baseline. \"candid\": states disagreement first, accepts unwelcome conclusions, and writes plainly without praise or rhetorical questions. Omit for no personality; the shared baseline still applies."
        },
        "toolSettings": {
          "default": {},
          "description": "Per-namespace settings every agent granted that namespace starts from. An agent’s own toolSettings merge over these shallowly, per namespace: a field the agent states replaces that field whole, and the rest keep their defaults.",
          "type": "object",
          "propertyNames": {
            "type": "string",
            "pattern": "^[a-z](?:_?[a-z0-9])*$"
          },
          "additionalProperties": {},
          "properties": {
            "mail": {
              "$schema": "http://json-schema.org/draft-07/schema#",
              "type": "object",
              "properties": {
                "announcementChannel": {
                  "type": "string",
                  "maxLength": 64,
                  "pattern": "^[a-z0-9][a-z0-9_-]*$",
                  "description": "Channel where arriving mail is announced, by handle. Must not be a DM, and the agent must be a member — both refused at boot rather than discovered at runtime."
                },
                "pollIntervalMs": {
                  "default": 60000,
                  "description": "How often the mailbox is polled for new arrivals, each poll advancing the durable cursor. Announcement latency is bounded by this plus idle-gating.",
                  "type": "integer",
                  "exclusiveMinimum": 0,
                  "maximum": 9007199254740991
                },
                "provider": {
                  "oneOf": [
                    {
                      "type": "object",
                      "properties": {
                        "address": {
                          "type": "string",
                          "format": "email",
                          "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$",
                          "description": "The mailbox address this agent acts as. Fixed here and never model-supplied: the from on everything the agent sends."
                        },
                        "clientId": {
                          "type": "string",
                          "minLength": 1,
                          "description": "Client id of this agent's Entra app registration. One registration per agent mailbox — scoped by Exchange App RBAC — so the provider itself enforces that an agent reaches no mailbox but its own."
                        },
                        "clientSecret": {
                          "type": "string",
                          "minLength": 1,
                          "description": "Client secret of the app registration. It expires on a tenant schedule, and mail stops when it does."
                        },
                        "kind": {
                          "type": "string",
                          "const": "exchange"
                        },
                        "tenantId": {
                          "type": "string",
                          "minLength": 1,
                          "description": "Entra tenant id the mailbox lives in"
                        }
                      },
                      "required": [
                        "address",
                        "clientId",
                        "clientSecret",
                        "kind",
                        "tenantId"
                      ],
                      "additionalProperties": false
                    },
                    {
                      "type": "object",
                      "properties": {
                        "address": {
                          "type": "string",
                          "format": "email",
                          "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$",
                          "description": "The mailbox address this agent acts as. Fixed here and never model-supplied: the from on everything the agent sends."
                        },
                        "imap": {
                          "type": "object",
                          "properties": {
                            "host": {
                              "type": "string",
                              "minLength": 1,
                              "description": "Hostname of the endpoint"
                            },
                            "password": {
                              "type": "string",
                              "minLength": 1,
                              "description": "Password for this endpoint"
                            },
                            "port": {
                              "type": "integer",
                              "minimum": 1,
                              "maximum": 65535,
                              "description": "Port of the endpoint"
                            },
                            "secure": {
                              "type": "boolean",
                              "description": "true for implicit TLS from the first byte (typically ports 993/465); false to connect plain and upgrade via STARTTLS where the server offers it (typically ports 143/587)"
                            },
                            "username": {
                              "type": "string",
                              "minLength": 1,
                              "description": "Login username for this endpoint"
                            }
                          },
                          "required": [
                            "host",
                            "password",
                            "port",
                            "secure",
                            "username"
                          ],
                          "additionalProperties": false,
                          "description": "The IMAP endpoint mail is read from, with its credentials"
                        },
                        "kind": {
                          "type": "string",
                          "const": "imap"
                        },
                        "smtp": {
                          "type": "object",
                          "properties": {
                            "host": {
                              "type": "string",
                              "minLength": 1,
                              "description": "Hostname of the endpoint"
                            },
                            "password": {
                              "type": "string",
                              "minLength": 1,
                              "description": "Password for this endpoint"
                            },
                            "port": {
                              "type": "integer",
                              "minimum": 1,
                              "maximum": 65535,
                              "description": "Port of the endpoint"
                            },
                            "secure": {
                              "type": "boolean",
                              "description": "true for implicit TLS from the first byte (typically ports 993/465); false to connect plain and upgrade via STARTTLS where the server offers it (typically ports 143/587)"
                            },
                            "username": {
                              "type": "string",
                              "minLength": 1,
                              "description": "Login username for this endpoint"
                            }
                          },
                          "required": [
                            "host",
                            "password",
                            "port",
                            "secure",
                            "username"
                          ],
                          "additionalProperties": false,
                          "description": "The SMTP endpoint mail is sent through, with its credentials"
                        }
                      },
                      "required": [
                        "address",
                        "imap",
                        "kind",
                        "smtp"
                      ],
                      "additionalProperties": false
                    }
                  ],
                  "description": "Which kind of provider serves this mailbox, with its credentials: Exchange Online, or generic IMAP/SMTP."
                },
                "template": {
                  "description": "An HTML file beneath `RESOURCES_ROOT` that every message this mailbox sends is wrapped in, holding `{BODY}` exactly once where the body goes. The body is rendered from markdown, with any raw HTML in it escaped. Absent, mail is sent as plain text.",
                  "type": "string",
                  "minLength": 1
                }
              },
              "additionalProperties": false,
              "description": "The one mailbox an agent granted mail acts as. Granting mail without these is a boot refusal (§8)."
            },
            "memory": {
              "$schema": "http://json-schema.org/draft-07/schema#",
              "type": "object",
              "properties": {
                "maxBodyChars": {
                  "default": 16000,
                  "description": "Longest body one entry may hold. A longer write is refused rather than truncated, since a silently cut note is worse than a refused one. Bodies are never listed in the prompt, but a written body stays in the channel window as the arguments of the call that wrote it, so this also bounds what one write costs each later turn whose window reaches it. A body read back replays to later turns in full up to 2000 characters, and as one line beyond that (§3.8).",
                  "type": "integer",
                  "exclusiveMinimum": 0,
                  "maximum": 9007199254740991
                },
                "maxDescriptionChars": {
                  "default": 200,
                  "description": "Longest description one entry may hold. Every description is listed after the channel window on every turn (§3.8), so this bounds that cost.",
                  "type": "integer",
                  "exclusiveMinimum": 0,
                  "maximum": 9007199254740991
                },
                "maxEntries": {
                  "default": 50,
                  "description": "How many entries one agent may hold. Writing beyond it evicts the entry whose body was read longest ago, counting an entry never read from when it was written.",
                  "type": "integer",
                  "exclusiveMinimum": 0,
                  "maximum": 9007199254740991
                }
              },
              "additionalProperties": false,
              "description": "The bounds on one agent's memory (§3.6). Every field has a default, so a bare grant works."
            },
            "tasks": {
              "$schema": "http://json-schema.org/draft-07/schema#",
              "type": "object",
              "properties": {
                "assignees": {
                  "description": "The usernames of the colleagues this agent may hand units to, continuations included. An assignment to anyone else is refused before it is posted, and the prompt names these colleagues. Each must be a configured agent other than this one that holds tasks::report, or boot is refused. Absent, any colleague in the channel that can report may take a unit (§3.15).",
                  "minItems": 1,
                  "type": "array",
                  "items": {
                    "type": "string",
                    "minLength": 1
                  }
                },
                "openUnitCap": {
                  "default": 20,
                  "description": "How many open units one agent may hold as creator in one channel; an assignment past it is refused (§3.15).",
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 9007199254740991
                },
                "shownInPrompt": {
                  "default": 20,
                  "description": "How many open units the prompt lists before it states a remainder count instead (§3.15).",
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 9007199254740991
                }
              },
              "additionalProperties": false,
              "description": "The bounds on delegated work (§3.15). Every field is optional, so a bare grant works."
            },
            "web": {
              "$schema": "http://json-schema.org/draft-07/schema#",
              "type": "object",
              "properties": {
                "search": {
                  "type": "object",
                  "properties": {
                    "provider": {
                      "oneOf": [
                        {
                          "type": "object",
                          "properties": {
                            "apiKey": {
                              "type": "string",
                              "minLength": 1,
                              "description": "Brave Search API subscription token (https://api-dashboard.search.brave.com)"
                            },
                            "kind": {
                              "type": "string",
                              "const": "brave"
                            }
                          },
                          "required": [
                            "apiKey",
                            "kind"
                          ],
                          "additionalProperties": false
                        }
                      ],
                      "description": "Which search provider answers web::search, with its credentials"
                    }
                  },
                  "required": [
                    "provider"
                  ],
                  "additionalProperties": false,
                  "description": "The search provider behind web::search. Absent, the agent is not offered web::search."
                }
              },
              "additionalProperties": false,
              "description": "What the web toolset runs with. Every field is optional, so a bare grant works."
            }
          }
        },
        "turnContextCeilingTokens": {
          "default": 200000,
          "description": "Every agent's turn ceiling unless it states its own",
          "type": "integer",
          "exclusiveMinimum": 0,
          "maximum": 9007199254740991
        }
      },
      "additionalProperties": false
    },
    "agents": {
      "type": "object",
      "propertyNames": {
        "type": "string",
        "maxLength": 22,
        "pattern": "^[a-z][a-z0-9-]*$",
        "description": "The agent's username: how it is addressed, and its bot account's own handle",
        "title": "username"
      },
      "additionalProperties": {
        "type": "object",
        "properties": {
          "actionBudget": {
            "description": "Overrides turns.actionBudget for this agent (§5.3): the action attempts one of its turns may make before asking to continue, and what an approved extension grants. State it for an agent whose unit of work is many ungated reads.",
            "type": "integer",
            "exclusiveMinimum": 0,
            "maximum": 9007199254740991
          },
          "completionTimeLimitMs": {
            "description": "Overrides agentDefaults.completionTimeLimitMs for this agent",
            "type": "integer",
            "exclusiveMinimum": 0,
            "maximum": 9007199254740991
          },
          "contextBudgetTokens": {
            "description": "Overrides agentDefaults.contextBudgetTokens for this agent",
            "type": "integer",
            "exclusiveMinimum": 0,
            "maximum": 9007199254740991
          },
          "displayName": {
            "description": "What this agent is called in prose, and the name its bot account shows: other agents read its posts and its line under Peers by this name, and write it without the @ that would start its turn (§3.1). Defaults to the username with its first letter capitalised.",
            "type": "string",
            "minLength": 1,
            "maxLength": 64,
            "pattern": "^[^@\\r\\n]+$"
          },
          "expertise": {
            "type": "string",
            "minLength": 1,
            "description": "What this agent should be contacted about, in a few words. Shown to every other agent so they know when to hand work over."
          },
          "model": {
            "description": "Overrides agentDefaults.model. Required when no default is set.",
            "oneOf": [
              {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "enum": [
                      "deepseek-v4-flash",
                      "deepseek-v4-pro"
                    ]
                  },
                  "provider": {
                    "type": "string",
                    "const": "deepseek"
                  },
                  "reasoningEffort": {
                    "description": "How hard the model thinks before it answers, in DeepSeek’s own vocabulary: none turns thinking off. Omit for the provider’s default, which is high.",
                    "type": "string",
                    "enum": [
                      "none",
                      "low",
                      "high",
                      "max"
                    ]
                  }
                },
                "required": [
                  "name",
                  "provider"
                ],
                "additionalProperties": false
              },
              {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "enum": [
                      "anthropic/claude-fable-5.1",
                      "anthropic/claude-opus-5",
                      "anthropic/claude-sonnet-5",
                      "deepseek/deepseek-v4-flash",
                      "deepseek/deepseek-v4-pro",
                      "deepseek/deepseek-v4.1-flash",
                      "openai/gpt-5.6-luna",
                      "openai/gpt-5.6-luna-pro",
                      "openai/gpt-5.6-sol",
                      "openai/gpt-5.6-sol-pro",
                      "openai/gpt-5.6-terra",
                      "openai/gpt-5.6-terra-pro",
                      "openai/gpt-6-astra",
                      "z-ai/glm-5.3",
                      "z-ai/glm-5.3-flash",
                      "~anthropic/claude-fable-latest",
                      "~anthropic/claude-opus-latest",
                      "~anthropic/claude-sonnet-latest",
                      "~deepseek/deepseek-flash-latest",
                      "~deepseek/deepseek-pro-latest",
                      "~openai/gpt-astra-latest",
                      "~openai/gpt-luna-latest",
                      "~openai/gpt-sol-latest",
                      "~openai/gpt-terra-latest"
                    ]
                  },
                  "provider": {
                    "type": "string",
                    "const": "openrouter"
                  },
                  "reasoningEffort": {
                    "description": "How hard the model thinks before it answers, in OpenRouter’s unified vocabulary, mapped onto what the model supports. Omit for the model’s default; a Claude model then reasons without extended thinking.",
                    "type": "string",
                    "enum": [
                      "none",
                      "minimal",
                      "low",
                      "medium",
                      "high",
                      "xhigh",
                      "max"
                    ]
                  },
                  "upstreams": {
                    "description": "The upstreams to prefer for this model, in order, sent to OpenRouter as its provider order. Each upstream caches prompts separately, so holding a model to one keeps its cache warm (§3.8); the model itself never changes. OpenRouter falls back to its own choice when none of them can serve. Omit to leave routing to OpenRouter.",
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "string",
                      "pattern": "^[a-z0-9][a-z0-9-]*(?:\\/[a-z0-9][a-z0-9-]*)?$",
                      "description": "An upstream as OpenRouter names it in a provider order: its lowercase slug, optionally with one variant after a slash, such as relace or streamlake/fp8. A bare slug covers every variant that upstream serves."
                    }
                  }
                },
                "required": [
                  "name",
                  "provider"
                ],
                "additionalProperties": false
              }
            ]
          },
          "personality": {
            "description": "Overrides agentDefaults.personality for this agent",
            "type": "string",
            "enum": [
              "candid"
            ]
          },
          "schedules": {
            "default": {},
            "description": "Recurring work for this agent, by handle. Each firing writes a trigger row the system bot posts when the channel is next idle (§4.2). An agent cannot create, change or remove one (§9).",
            "type": "object",
            "propertyNames": {
              "type": "string",
              "maxLength": 64,
              "pattern": "^[a-z0-9][a-z0-9_-]*$"
            },
            "additionalProperties": {
              "type": "object",
              "properties": {
                "channel": {
                  "type": "string",
                  "maxLength": 64,
                  "pattern": "^[a-z0-9][a-z0-9_-]*$",
                  "description": "Handle of the channel the announcement is posted in. The agent must be a member, and a DM can never be one (§4.2)."
                },
                "prompt": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 2000,
                  "description": "What the system bot posts when this schedule fires, verbatim: the operator’s instruction, which the agent carries out and then marks done (§4.2). Operator-written, never model-written (§3.2)."
                },
                "recurrence": {
                  "oneOf": [
                    {
                      "type": "object",
                      "properties": {
                        "atMinute": {
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 59,
                          "description": "Minute past the hour"
                        },
                        "every": {
                          "type": "string",
                          "const": "hour"
                        }
                      },
                      "required": [
                        "atMinute",
                        "every"
                      ],
                      "additionalProperties": false
                    },
                    {
                      "type": "object",
                      "properties": {
                        "at": {
                          "type": "string",
                          "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$",
                          "description": "A time of day as \"HH:MM\", 24-hour, read in this schedule’s timezone"
                        },
                        "every": {
                          "type": "string",
                          "const": "day"
                        }
                      },
                      "required": [
                        "at",
                        "every"
                      ],
                      "additionalProperties": false
                    },
                    {
                      "type": "object",
                      "properties": {
                        "at": {
                          "type": "string",
                          "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$",
                          "description": "A time of day as \"HH:MM\", 24-hour, read in this schedule’s timezone"
                        },
                        "every": {
                          "type": "string",
                          "const": "weekday"
                        }
                      },
                      "required": [
                        "at",
                        "every"
                      ],
                      "additionalProperties": false
                    },
                    {
                      "type": "object",
                      "properties": {
                        "at": {
                          "type": "string",
                          "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$",
                          "description": "A time of day as \"HH:MM\", 24-hour, read in this schedule’s timezone"
                        },
                        "every": {
                          "type": "string",
                          "const": "week"
                        },
                        "onDay": {
                          "type": "string",
                          "enum": [
                            "monday",
                            "tuesday",
                            "wednesday",
                            "thursday",
                            "friday",
                            "saturday",
                            "sunday"
                          ]
                        }
                      },
                      "required": [
                        "at",
                        "every",
                        "onDay"
                      ],
                      "additionalProperties": false
                    },
                    {
                      "type": "object",
                      "properties": {
                        "at": {
                          "type": "string",
                          "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$",
                          "description": "A time of day as \"HH:MM\", 24-hour, read in this schedule’s timezone"
                        },
                        "every": {
                          "type": "string",
                          "const": "month"
                        },
                        "onDayOfMonth": {
                          "type": "integer",
                          "minimum": 1,
                          "maximum": 28,
                          "description": "Day of the month, at most 28 so every month has one"
                        }
                      },
                      "required": [
                        "at",
                        "every",
                        "onDayOfMonth"
                      ],
                      "additionalProperties": false
                    }
                  ],
                  "description": "How often this schedule fires. Every shape is a wall-clock time in the schedule’s timezone."
                },
                "timezone": {
                  "description": "IANA timezone the recurrence is read in. Defaults to display.timezone.",
                  "type": "string"
                }
              },
              "required": [
                "channel",
                "prompt",
                "recurrence"
              ],
              "additionalProperties": false
            }
          },
          "skills": {
            "default": [],
            "description": "Skills assigned to this agent: framework skills by bare name, toolset-shipped skills as \"namespace::skill\". Core skills are always assigned.",
            "type": "array",
            "items": {
              "type": "string",
              "anyOf": [
                {
                  "enum": [
                    "web::reading-websites"
                  ]
                },
                {
                  "type": "string"
                }
              ]
            }
          },
          "systemPrompt": {
            "anyOf": [
              {
                "type": "string",
                "minLength": 1
              },
              {
                "type": "object",
                "properties": {
                  "resource": {
                    "type": "string",
                    "minLength": 1
                  }
                },
                "required": [
                  "resource"
                ],
                "additionalProperties": false
              }
            ],
            "description": "The agent’s system prompt: the text itself, or { \"resource\": \"<path>\" } naming a file beneath RESOURCES_ROOT that holds it. A prompt of more than a paragraph belongs in a file, where it is reviewable in a diff."
          },
          "tools": {
            "default": [],
            "description": "Toolsets and tools this agent may call: a namespace grants every tool it holds, \"namespace::tool\" grants one. Core tools are always available and never named here.",
            "type": "array",
            "items": {
              "type": "string",
              "description": "A toolset namespace, or one tool by its \"namespace::tool\" ref. Plugin grants use the plugin's namespace the same way.",
              "x-table": {
                "rows": [
                  {
                    "label": "ask",
                    "values": [
                      "ask::human"
                    ]
                  },
                  {
                    "label": "conversations",
                    "values": [
                      "conversations::search"
                    ]
                  },
                  {
                    "label": "mail",
                    "values": [
                      "mail::conversation",
                      "mail::list",
                      "mail::open",
                      "mail::reply",
                      "mail::search",
                      "mail::send"
                    ]
                  },
                  {
                    "label": "memory",
                    "values": [
                      "memory::append",
                      "memory::delete",
                      "memory::read",
                      "memory::replace",
                      "memory::rewrite",
                      "memory::write"
                    ]
                  },
                  {
                    "label": "shell",
                    "values": [
                      "shell::run"
                    ]
                  },
                  {
                    "label": "tasks",
                    "values": [
                      "tasks::assign",
                      "tasks::close",
                      "tasks::read",
                      "tasks::report"
                    ]
                  },
                  {
                    "label": "web",
                    "values": [
                      "web::click",
                      "web::fetch",
                      "web::fill",
                      "web::hover",
                      "web::navigate",
                      "web::search",
                      "web::select"
                    ]
                  },
                  {
                    "label": "workspace",
                    "values": [
                      "workspace::find",
                      "workspace::grep",
                      "workspace::list",
                      "workspace::read",
                      "workspace::stat",
                      "workspace::write"
                    ]
                  }
                ],
                "title": "Built-in options"
              },
              "anyOf": [
                {
                  "enum": [
                    "ask",
                    "ask::human",
                    "conversations",
                    "conversations::search",
                    "mail",
                    "mail::conversation",
                    "mail::list",
                    "mail::open",
                    "mail::reply",
                    "mail::search",
                    "mail::send",
                    "memory",
                    "memory::append",
                    "memory::delete",
                    "memory::read",
                    "memory::replace",
                    "memory::rewrite",
                    "memory::write",
                    "shell",
                    "shell::run",
                    "tasks",
                    "tasks::assign",
                    "tasks::close",
                    "tasks::read",
                    "tasks::report",
                    "web",
                    "web::click",
                    "web::fetch",
                    "web::fill",
                    "web::hover",
                    "web::navigate",
                    "web::search",
                    "web::select",
                    "workspace",
                    "workspace::find",
                    "workspace::grep",
                    "workspace::list",
                    "workspace::read",
                    "workspace::stat",
                    "workspace::write"
                  ]
                },
                {
                  "type": "string"
                }
              ]
            }
          },
          "toolSettings": {
            "default": {},
            "description": "Per-namespace settings for this agent, merged shallowly over agentDefaults.toolSettings and validated against the schema the toolset declares. Settings for an ungranted toolset are refused at boot.",
            "type": "object",
            "propertyNames": {
              "type": "string",
              "pattern": "^[a-z](?:_?[a-z0-9])*$"
            },
            "additionalProperties": {},
            "properties": {
              "mail": {
                "$schema": "http://json-schema.org/draft-07/schema#",
                "type": "object",
                "properties": {
                  "announcementChannel": {
                    "type": "string",
                    "maxLength": 64,
                    "pattern": "^[a-z0-9][a-z0-9_-]*$",
                    "description": "Channel where arriving mail is announced, by handle. Must not be a DM, and the agent must be a member — both refused at boot rather than discovered at runtime."
                  },
                  "pollIntervalMs": {
                    "default": 60000,
                    "description": "How often the mailbox is polled for new arrivals, each poll advancing the durable cursor. Announcement latency is bounded by this plus idle-gating.",
                    "type": "integer",
                    "exclusiveMinimum": 0,
                    "maximum": 9007199254740991
                  },
                  "provider": {
                    "oneOf": [
                      {
                        "type": "object",
                        "properties": {
                          "address": {
                            "type": "string",
                            "format": "email",
                            "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$",
                            "description": "The mailbox address this agent acts as. Fixed here and never model-supplied: the from on everything the agent sends."
                          },
                          "clientId": {
                            "type": "string",
                            "minLength": 1,
                            "description": "Client id of this agent's Entra app registration. One registration per agent mailbox — scoped by Exchange App RBAC — so the provider itself enforces that an agent reaches no mailbox but its own."
                          },
                          "clientSecret": {
                            "type": "string",
                            "minLength": 1,
                            "description": "Client secret of the app registration. It expires on a tenant schedule, and mail stops when it does."
                          },
                          "kind": {
                            "type": "string",
                            "const": "exchange"
                          },
                          "tenantId": {
                            "type": "string",
                            "minLength": 1,
                            "description": "Entra tenant id the mailbox lives in"
                          }
                        },
                        "required": [
                          "address",
                          "clientId",
                          "clientSecret",
                          "kind",
                          "tenantId"
                        ],
                        "additionalProperties": false
                      },
                      {
                        "type": "object",
                        "properties": {
                          "address": {
                            "type": "string",
                            "format": "email",
                            "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$",
                            "description": "The mailbox address this agent acts as. Fixed here and never model-supplied: the from on everything the agent sends."
                          },
                          "imap": {
                            "type": "object",
                            "properties": {
                              "host": {
                                "type": "string",
                                "minLength": 1,
                                "description": "Hostname of the endpoint"
                              },
                              "password": {
                                "type": "string",
                                "minLength": 1,
                                "description": "Password for this endpoint"
                              },
                              "port": {
                                "type": "integer",
                                "minimum": 1,
                                "maximum": 65535,
                                "description": "Port of the endpoint"
                              },
                              "secure": {
                                "type": "boolean",
                                "description": "true for implicit TLS from the first byte (typically ports 993/465); false to connect plain and upgrade via STARTTLS where the server offers it (typically ports 143/587)"
                              },
                              "username": {
                                "type": "string",
                                "minLength": 1,
                                "description": "Login username for this endpoint"
                              }
                            },
                            "required": [
                              "host",
                              "password",
                              "port",
                              "secure",
                              "username"
                            ],
                            "additionalProperties": false,
                            "description": "The IMAP endpoint mail is read from, with its credentials"
                          },
                          "kind": {
                            "type": "string",
                            "const": "imap"
                          },
                          "smtp": {
                            "type": "object",
                            "properties": {
                              "host": {
                                "type": "string",
                                "minLength": 1,
                                "description": "Hostname of the endpoint"
                              },
                              "password": {
                                "type": "string",
                                "minLength": 1,
                                "description": "Password for this endpoint"
                              },
                              "port": {
                                "type": "integer",
                                "minimum": 1,
                                "maximum": 65535,
                                "description": "Port of the endpoint"
                              },
                              "secure": {
                                "type": "boolean",
                                "description": "true for implicit TLS from the first byte (typically ports 993/465); false to connect plain and upgrade via STARTTLS where the server offers it (typically ports 143/587)"
                              },
                              "username": {
                                "type": "string",
                                "minLength": 1,
                                "description": "Login username for this endpoint"
                              }
                            },
                            "required": [
                              "host",
                              "password",
                              "port",
                              "secure",
                              "username"
                            ],
                            "additionalProperties": false,
                            "description": "The SMTP endpoint mail is sent through, with its credentials"
                          }
                        },
                        "required": [
                          "address",
                          "imap",
                          "kind",
                          "smtp"
                        ],
                        "additionalProperties": false
                      }
                    ],
                    "description": "Which kind of provider serves this mailbox, with its credentials: Exchange Online, or generic IMAP/SMTP."
                  },
                  "template": {
                    "description": "An HTML file beneath `RESOURCES_ROOT` that every message this mailbox sends is wrapped in, holding `{BODY}` exactly once where the body goes. The body is rendered from markdown, with any raw HTML in it escaped. Absent, mail is sent as plain text.",
                    "type": "string",
                    "minLength": 1
                  }
                },
                "additionalProperties": false,
                "description": "The one mailbox an agent granted mail acts as. Granting mail without these is a boot refusal (§8)."
              },
              "memory": {
                "$schema": "http://json-schema.org/draft-07/schema#",
                "type": "object",
                "properties": {
                  "maxBodyChars": {
                    "default": 16000,
                    "description": "Longest body one entry may hold. A longer write is refused rather than truncated, since a silently cut note is worse than a refused one. Bodies are never listed in the prompt, but a written body stays in the channel window as the arguments of the call that wrote it, so this also bounds what one write costs each later turn whose window reaches it. A body read back replays to later turns in full up to 2000 characters, and as one line beyond that (§3.8).",
                    "type": "integer",
                    "exclusiveMinimum": 0,
                    "maximum": 9007199254740991
                  },
                  "maxDescriptionChars": {
                    "default": 200,
                    "description": "Longest description one entry may hold. Every description is listed after the channel window on every turn (§3.8), so this bounds that cost.",
                    "type": "integer",
                    "exclusiveMinimum": 0,
                    "maximum": 9007199254740991
                  },
                  "maxEntries": {
                    "default": 50,
                    "description": "How many entries one agent may hold. Writing beyond it evicts the entry whose body was read longest ago, counting an entry never read from when it was written.",
                    "type": "integer",
                    "exclusiveMinimum": 0,
                    "maximum": 9007199254740991
                  }
                },
                "additionalProperties": false,
                "description": "The bounds on one agent's memory (§3.6). Every field has a default, so a bare grant works."
              },
              "tasks": {
                "$schema": "http://json-schema.org/draft-07/schema#",
                "type": "object",
                "properties": {
                  "assignees": {
                    "description": "The usernames of the colleagues this agent may hand units to, continuations included. An assignment to anyone else is refused before it is posted, and the prompt names these colleagues. Each must be a configured agent other than this one that holds tasks::report, or boot is refused. Absent, any colleague in the channel that can report may take a unit (§3.15).",
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1
                    }
                  },
                  "openUnitCap": {
                    "default": 20,
                    "description": "How many open units one agent may hold as creator in one channel; an assignment past it is refused (§3.15).",
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 9007199254740991
                  },
                  "shownInPrompt": {
                    "default": 20,
                    "description": "How many open units the prompt lists before it states a remainder count instead (§3.15).",
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 9007199254740991
                  }
                },
                "additionalProperties": false,
                "description": "The bounds on delegated work (§3.15). Every field is optional, so a bare grant works."
              },
              "web": {
                "$schema": "http://json-schema.org/draft-07/schema#",
                "type": "object",
                "properties": {
                  "search": {
                    "type": "object",
                    "properties": {
                      "provider": {
                        "oneOf": [
                          {
                            "type": "object",
                            "properties": {
                              "apiKey": {
                                "type": "string",
                                "minLength": 1,
                                "description": "Brave Search API subscription token (https://api-dashboard.search.brave.com)"
                              },
                              "kind": {
                                "type": "string",
                                "const": "brave"
                              }
                            },
                            "required": [
                              "apiKey",
                              "kind"
                            ],
                            "additionalProperties": false
                          }
                        ],
                        "description": "Which search provider answers web::search, with its credentials"
                      }
                    },
                    "required": [
                      "provider"
                    ],
                    "additionalProperties": false,
                    "description": "The search provider behind web::search. Absent, the agent is not offered web::search."
                  }
                },
                "additionalProperties": false,
                "description": "What the web toolset runs with. Every field is optional, so a bare grant works."
              }
            }
          },
          "turnContextCeilingTokens": {
            "description": "Overrides agentDefaults.turnContextCeilingTokens for this agent",
            "type": "integer",
            "exclusiveMinimum": 0,
            "maximum": 9007199254740991
          }
        },
        "required": [
          "expertise",
          "systemPrompt"
        ],
        "additionalProperties": false
      },
      "description": "The agents this deployment runs, by username, each a persistent identity with its own prompt, model, and grants (§3.1). Provisioning creates the bot account of that name if it is absent."
    },
    "display": {
      "description": "How facts are rendered for humans, not what they are",
      "default": {},
      "type": "object",
      "properties": {
        "timezone": {
          "default": "UTC",
          "description": "The IANA timezone every date rendered into a post is shown in",
          "type": "string"
        }
      },
      "additionalProperties": false
    },
    "inference": {
      "description": "The transport to model providers: how long a completion may take and how failures are retried (§7.2)",
      "default": {},
      "type": "object",
      "properties": {
        "retry": {
          "description": "How transport failures from a model provider are retried (§7.2). Semantic failures are never retried.",
          "default": {},
          "type": "object",
          "properties": {
            "backoffMs": {
              "default": 1000,
              "description": "Delay before the first retry of a transport failure, doubling with each further attempt and shortened by up to a quarter at random, so agents sharing a provider key do not retry in step",
              "type": "integer",
              "exclusiveMinimum": 0,
              "maximum": 9007199254740991
            },
            "maxAttempts": {
              "default": 5,
              "description": "Total attempts allowed for one completion, the first attempt included",
              "type": "integer",
              "exclusiveMinimum": 0,
              "maximum": 9007199254740991
            },
            "maxDelayMs": {
              "default": 30000,
              "description": "The longest single wait between attempts: the cap on the doubling delay, and on a wait the provider asks for with Retry-After. A provider asking for longer ends the turn at once, naming the rate limit, rather than stalling it (§7.2).",
              "type": "integer",
              "exclusiveMinimum": 0,
              "maximum": 9007199254740991
            }
          },
          "additionalProperties": false
        },
        "timeoutMs": {
          "default": 120000,
          "description": "How long a completion may go without the provider sending anything — the connection, the first token, or any later token — before it is aborted and classified as a retryable transport failure. A completion that keeps streaming is never cut, however long it thinks.",
          "type": "integer",
          "exclusiveMinimum": 0,
          "maximum": 9007199254740991
        }
      },
      "additionalProperties": false
    },
    "logging": {
      "description": "What the app logs",
      "default": {},
      "type": "object",
      "properties": {
        "level": {
          "default": "info",
          "description": "Minimum severity the app logs",
          "type": "string",
          "enum": [
            "debug",
            "info",
            "warn",
            "error"
          ]
        }
      },
      "additionalProperties": false
    },
    "mattermost": {
      "description": "What the deployment expects of its Mattermost team beyond what the environment locates: the main channel, the system bot, and each channel’s triggering mode",
      "default": {},
      "type": "object",
      "properties": {
        "channels": {
          "default": {},
          "description": "Per-channel triggering mode, by handle. Any channel not listed is mention-required. Each listed channel is created at provisioning if absent and holds the system bot, plus a mailbox owner when it is the channel that agent's arrivals are announced to. Which other agents belong in it is set in Mattermost, not here.",
          "type": "object",
          "propertyNames": {
            "type": "string",
            "maxLength": 64,
            "pattern": "^[a-z0-9][a-z0-9_-]*$",
            "description": "The channel's name in its URL, resolved against the team at boot",
            "title": "handle"
          },
          "additionalProperties": {
            "type": "object",
            "properties": {
              "triggeringMode": {
                "type": "string",
                "enum": [
                  "mention-required",
                  "respond-to-all"
                ],
                "description": "mention-required: the agent acts only when explicitly @-mentioned. respond-to-all: every human post starts a turn, and the channel may hold at most one agent (§3.10)."
              }
            },
            "required": [
              "triggeringMode"
            ],
            "additionalProperties": false
          }
        },
        "mainChannel": {
          "default": "town-square",
          "description": "Handle of the main channel: where the orchestrator posts its status, and the one channel in which agents answer only when explicitly @-mentioned. Every bot must be a member.",
          "type": "string",
          "maxLength": 64,
          "pattern": "^[a-z0-9][a-z0-9_-]*$"
        },
        "systemBotUsername": {
          "default": "orchestrator",
          "description": "Username of the orchestrator's own bot account, which posts status notices and owns the slash-command surface. Not an agent, and separate from every agent. Provisioning creates it if it is absent and grants it authority to manage the team's slash commands (§8.4).",
          "type": "string",
          "maxLength": 22,
          "pattern": "^[a-z][a-z0-9-]*$"
        }
      },
      "additionalProperties": false
    },
    "notifications": {
      "description": "Which of the system bot’s deterministic notices are posted, and when (§3.2, §7.6)",
      "default": {},
      "type": "object",
      "properties": {
        "lifecycle": {
          "default": true,
          "description": "Whether the system bot posts a notice in the main channel when the app comes online, naming the downtime and the turns it abandoned, and again when it shuts down (§3.2, §7.3)",
          "type": "boolean"
        },
        "stalls": {
          "description": "When the system bot announces, once, work that has stopped moving with nothing said about it (§7.6). It announces and never clears: nothing is killed, drained or retried.",
          "default": {},
          "type": "object",
          "properties": {
            "longTurnMs": {
              "default": 1800000,
              "description": "How long one turn may hold its channel, since it started or last waited on a person, before the system bot says so there and names /collegium kill (§7.6). Time parked on an approval or a question is not counted.",
              "type": "integer",
              "exclusiveMinimum": 0,
              "maximum": 9007199254740991
            },
            "standingQueueMs": {
              "default": 600000,
              "description": "How long an agent may have work queued in a channel with no turn of its own running there before the system bot says so and names what clears it (§7.6)",
              "type": "integer",
              "exclusiveMinimum": 0,
              "maximum": 9007199254740991
            }
          },
          "additionalProperties": false
        }
      },
      "additionalProperties": false
    },
    "plugins": {
      "default": [],
      "description": "Plugins to load at boot, by name. Each is a directory of that name beneath the plugin root, contributing one toolset under its own namespace (§3.14)",
      "type": "array",
      "items": {
        "type": "string",
        "pattern": "^[a-z](?:_?[a-z0-9])*$",
        "description": "The plugin's identity, stated once: the directory holding it beneath the plugin root, and the namespace its tools, storage, and skills appear under."
      }
    },
    "providers": {
      "description": "Credentials for each model provider an agent may name. A provider a model names must be configured here.",
      "default": {},
      "type": "object",
      "properties": {
        "deepseek": {
          "description": "DeepSeek, reached directly",
          "type": "object",
          "properties": {
            "apiKey": {
              "type": "string",
              "minLength": 1,
              "description": "DeepSeek API key (https://platform.deepseek.com/api_keys)"
            },
            "baseUrl": {
              "default": "https://api.deepseek.com",
              "description": "Base URL of the DeepSeek-compatible API",
              "type": "string",
              "format": "uri"
            }
          },
          "required": [
            "apiKey"
          ],
          "additionalProperties": false
        },
        "openrouter": {
          "description": "OpenRouter, fronting many providers under one key",
          "type": "object",
          "properties": {
            "apiKey": {
              "type": "string",
              "minLength": 1,
              "description": "OpenRouter API key (https://openrouter.ai/keys)"
            },
            "baseUrl": {
              "default": "https://openrouter.ai/api/v1",
              "description": "Base URL of the OpenRouter-compatible API",
              "type": "string",
              "format": "uri"
            }
          },
          "required": [
            "apiKey"
          ],
          "additionalProperties": false
        }
      },
      "additionalProperties": false
    },
    "turns": {
      "description": "The bounds on a turn and on chains of turns (§5.3, §7.4)",
      "default": {},
      "type": "object",
      "properties": {
        "actionBudget": {
          "default": 25,
          "description": "Action attempts one turn may make before it must ask to continue (§5.3), for every agent that does not state its own: every model-emitted tool invocation counts, denied ones included. An approved extension grants the agent its budget again.",
          "type": "integer",
          "exclusiveMinimum": 0,
          "maximum": 9007199254740991
        },
        "chainLengthLimit": {
          "default": 200,
          "description": "How many turns one human post may set in motion through agent-to-agent mentions, returns included (§7.4). At the limit, mentions of a peer are stripped and the turn says so; a fresh human post starts a fresh chain.",
          "type": "integer",
          "exclusiveMinimum": 0,
          "maximum": 9007199254740991
        },
        "delegationDepthLimit": {
          "default": 10,
          "description": "How many agent-to-agent hops from the nearest human a delegation chain may reach (§7.4). At the limit, mentions of a peer are stripped and the turn says so.",
          "type": "integer",
          "exclusiveMinimum": 0,
          "maximum": 9007199254740991
        },
        "hourlyCeiling": {
          "default": 500,
          "description": "Framework-wide ceiling on turns started per rolling hour. A breach halts every agent until a human posts /collegium resume (§7.4).",
          "type": "integer",
          "exclusiveMinimum": 0,
          "maximum": 9007199254740991
        }
      },
      "additionalProperties": false
    },
    "web": {
      "description": "What the web toolset may reach, and how many browser sessions it may hold open at once (§3.4)",
      "default": {},
      "type": "object",
      "properties": {
        "allowPrivateAddresses": {
          "default": false,
          "description": "Lifts the refusal of loopback, private-network and link-local addresses for every agent and every request the web toolset makes (§3.4). Only http(s) is still enforced. Meant for a deployment that serves its own test pages; it is logged at boot whenever it is on.",
          "type": "boolean"
        },
        "deniedHosts": {
          "default": [],
          "description": "Hosts the web toolset refuses, each with every subdomain beneath it, for every agent and every request: a fetch and each redirect it follows, a navigation, and anything a page loads itself (§3.4). Empty by default, so the open web is reachable. Set by the operator alone, and logged at boot whenever it names a host.",
          "type": "array",
          "items": {
            "type": "string",
            "pattern": "^(?=.{1,253}$)[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$"
          }
        },
        "maxBrowserSessions": {
          "default": 4,
          "description": "How many browser sessions may be live at once, across every agent (§3.4). A turn holds one from its first web::navigate until it ends, and each is a browser context holding a rendered page, so this bounds the browser's memory. A turn that would open one past it is refused rather than queued; web::fetch needs no session and still works.",
          "type": "integer",
          "exclusiveMinimum": 0,
          "maximum": 9007199254740991
        }
      },
      "additionalProperties": false
    }
  },
  "required": [
    "agents"
  ],
  "additionalProperties": false
}
