# Agent Manifest Contract The manifest is the third stable contract — the declarative description of one agent. Defined by `AgentManifest` (`src/core/manifest.py`), loaded from `config/agents/.yaml`, validated by `validate_agent()`. Adding an agent is dropping in a manifest; no engine edit. ## Schema ```yaml name: scene-whisperer # unique slug == actor on emitted events (required) role: worker # worker | judge | observer | reflector persona: > # IDENTITY block, injected verbatim every prompt (required) You are the Seedkeeper ... describe how the wood changed in one sentence. handler: null # optional behaviour binding (see below) # Communication contract subscribes_to: # event kinds that trigger this agent (reactive) - user.injected may_emit: # event kinds this agent is allowed to emit (authority) - world.observed - agent.reflected # Scheduling schedule: tick_every: 1 # also fire every N turns (null = event-driven only; 0 = every turn) max_consecutive: 3 # documented cap (enforcement deferred) # Model (resolved to a concrete small model by the ModelRouter) model_profile: fast # tiny ≤4B | fast ≤7B | balanced ≤13B | strong ≤32B model_endpoint: null # optional: pin ONE specific catalogue model (modal/catalogue.py # endpoint slug, e.g. minicpm-4-1-8b), overriding the tier above # Memory (a view over the ledger, not separate state) memory: window: 6 # recent visible events in the prompt use_salience: false # rank by relevance×recency×importance instead of pure recency salience_top_k: 8 reflection_threshold: null # emit agent.reflected every N visible events (null = off) # Capability grants tools: [] # tool names the ToolRegistry will allow this agent to call # Output shaping output_extra_fields: [] # extra payload fields the model is asked for, e.g. ["emotion"] ``` ## Field notes - **`may_emit`** is the safety boundary. An agent's structured output is coerced to one of these kinds; `agent.reflected` is permitted implicitly when reflection is enabled. - **`subscribes_to` vs `schedule.tick_every`** are orthogonal — an agent may be reactive, periodic, or both. Cadence is per-agent; scenarios don't schedule. - **`model_profile`** never names a model; the router (config/env) does. Mix tiers freely across a cast. - **`model_endpoint`** is the escape hatch from tiers to a *specific* served model: a `modal/catalogue.py` endpoint slug the router resolves to that model's live binding (overriding `model_profile`). `null` → route by tier. This is how a cast pins concrete sponsor models — one mind on MiniCPM, the Judge on Nemotron — and what the Fishbowl Lab's per-cast model picker writes. Offline it folds into the deterministic stub like any tier, so demos stay reproducible. See ADR-0022. - **`handler`** stays `null` for the common case (the generic `ManifestAgent`). Set it to a key registered via `@register_handler` for agents that call tools or need custom prompt logic; the YAML still supplies all declarative fields. - **`output_extra_fields`** entries are required strings, except the well-known typed names `winner` and `scores` (optional, engine-typed — ADR-0029). A judge in a competition scenario lists them to make its verdict machine-readable; see [structured-output.md](../architecture/structured-output.md#well-known-typed-fields). - **`memory.*`** layers are pure views over the ledger — see [memory-stack.md](../architecture/memory-stack.md). See also: [manifest-spec.md](../architecture/manifest-spec.md) (detailed guide), [scenario-config.md](scenario-config.md), [world-config.md](world-config.md).