Overlay manifest (kind: Overlay)¶
Package: mas-runtime · Schema: overlay.schema.yaml · apiVersion: mas/v1
An overlay is a manifest that patches an Agent, MAS, Infra, Flavour,
Experiment, or in-memory Workspace document
without copying the whole file. Experiments reference overlays per scenario
(scenarios[].overlays); the CLI applies them with -o path/to/overlay.yaml.
Merge semantics¶
Symmetrical partial or full override of Agent, MAS, or Flavour documents. Used as benchmark scenarios, runtime overlays, and UI overlay builder output.
Contract boundaries are expressed through schema fields such as design_pattern,
governance, observability, workflow, and tools — there is no separate
spec.contracts list.
CLI overlays¶
Command-line overrides become the same canonical Overlay document and are
applied after all -o overlays:
mas-ctl chat agent.yaml --override 'agent:spec.context.role=reviewer'
mas-ctl run-mas mas.yaml \
--override 'mas:spec.agency.agents[id=qa].spec.memory=long'
The $op operators and x-merge strategies are shared with file overlays.
Selector overrides in an overlay¶
An overlay may group the same root-qualified assignments accepted by the CLI in
spec.overrides. They are applied in list order after spec.patch, so identity
selectors and [*] wildcards can update several existing list entries without
repeating the surrounding manifest structure:
apiVersion: mas/v1
kind: Overlay
metadata:
name: reviewer-policy
spec:
target: {kind: MAS}
patch: {}
overrides:
- 'mas:spec.agency.agents[id=reviewer].spec.memory=long'
- 'mas:spec.agency.agents[*].spec.context.environment=staging'
The root must match spec.target.kind (mas, agent, infra, flavour,
experiment, or workspace). Values use YAML syntax, including $op
collections. A selector that matches no entry remains an error.
Shape¶
apiVersion: mas/v1
kind: Overlay
metadata:
name: cot-ablation
spec:
target:
kind: MAS # Agent | MAS | Infra | Flavour | Experiment | Workspace
name: optional-filter
patch:
workflow:
entry: broker
nodes:
- id: broker
delegates_to: [researcher]
agents:
$entry: # workflow.entry after this overlay's workflow patch
design_pattern: { type: cot, params: { max_steps: 10 } }
$not-entry: # every agency agent except $entry
skills: { "$op": { add: [l9-concord-v2-receiver] } }
$delegates: # workflow.nodes[$entry].delegates_to
skills: { "$op": { add: [peer-note] } }
broker:
tools: { "$op": { remove: [web-search] } }
params: # opaque; tools read ctx.runtime_params
region: eu-west-1
spec.tools (outside patch) is a separate scenario-level tool injection field.
target.kind must be Agent, MAS, Flavour, or Infra.
Target contract¶
spec.target is required and controls both validation and merge dispatch:
| Field | Required | Semantics |
|---|---|---|
kind |
yes | Target document kind: Agent, MAS, Infra, Flavour, Experiment, or Workspace. |
name |
no | For an Agent overlay applied to a MAS, selects one agency row by id, name, or metadata.name. For a standalone target, the document is already selected by the command. |
The target kind must match the root used by every spec.overrides expression.
An overlay with an unsupported kind, an unknown target name, or a selector with
no match fails instead of being silently ignored.
Patch contract¶
spec.patch is required and must be a mapping. Its allowed fields come from
the target-specific schema fragment:
| Target kind | Patch schema | Merge behavior |
|---|---|---|
Agent |
overlay-agent-patch.schema.yaml |
Agent fields, $op, and x-merge metadata |
MAS |
overlay-mas-patch.schema.yaml |
Workflow, agency entries, and MAS collection operators |
Infra |
overlay-infra-patch.schema.yaml |
RFC 7396 merge of the Infra spec |
Flavour |
overlay-flavour-patch.schema.yaml |
Deployment posture and plugin collection operators |
Experiment, Workspace |
target document schema | In-memory RFC 7396 merge |
Plain values follow the field's declared merge strategy. Use an explicit
$op mapping for collection operations such as replace, add, remove,
clear, or merge. Unknown fields are rejected by schema validation; this
keeps a typo from becoming a new configuration field.
Merge semantics¶
RFC 7396-style merge on the target resource. Flavour overlays still cannot
select models or API keys (deployment posture only). Agent overlays may
patch any Agent spec field the schema declares (including models[],
sampling, reasoning, extra, description, behavior, tools_ref).
spec.models[] deep-merges by id, then model, then main. Access fields
(api_base, api_key_env) stay on infra.
Examples: llm-reasoning.yaml, llm-sampling.yaml.
On a MAS overlay, patch.agents.$entry is applied to spec.workflow.entry after this
overlay's workflow patch (so a design-pattern overlay need not name the entry agent).
A missing entry, an unknown entry id, or both $entry and that id as keys is an error.
A target.kind: Agent overlay applied to a MAS selects nested agency
rows (spec.agency.agents[], or spec.agents[] when agency is absent).
Optional spec.target.name selects one row by id, name, or metadata.name
(the agency row id should match the agent YAML metadata.name when the same
overlay is reused on both). Zero matches is an error (OverlayTargetError) — the overlay must attach somewhere. That is how
labs reuse an Agent overlay such as with-guardrail on trip-planner: compose /
run-mas keep Form A rows as {id, ref} and merge the overlay onto each
referenced Agent YAML when that file is loaded (governance / observability union by plugin id,
they do not replace the agent's existing lists). skills and tools follow the
same rule: a {"$op": {"add": [...]}} value appends to what the agent YAML
already declares, while a raw list is an explicit replace (and [] clears).
A base MAS cannot declare skills (or tools, context, …) on an agency row;
those fields live in the agent manifest. Overlay targeting ($entry, $all,
$not-entry, $delegates, or a named id) is how a scenario changes them
per agent — there is no MAS-level copy of those fields. spec.models on the
MAS is the one agent-inherited MAS attribute (unresolved any is filled from
experiment / workspace / CLI, not from a silent package default).
Lab/bench MAS runs already attach
a shared native sink; a fanned-out default native list joins that sink instead of
opening a second events.jsonl. Custom observability paths stay per-agent.
On a MAS overlay, reserved patch.agents keys expand onto agency ids after this
overlay's workflow patch:
| Key | Expands to |
|---|---|
$entry |
spec.workflow.entry |
$all |
every spec.agency.agents id |
$not-entry |
every agency id except $entry |
$delegates |
workflow.nodes[$entry].delegates_to |
They compose in that order, then a named id wins. $entry plus that same id as a
key is an error. $entry / $not-entry require spec.workflow.entry. An unknown
$entry id is an error.
Merge semantics: later overlays in a scenario stack win on conflicting keys.
Patches use RFC 7396 JSON merge; list fields such as tools accept an explicit
{"$op": {replace|add|remove|clear: [...]}} form (handled explicitly by the
runtime) alongside plain-list implicit replace.
For MAS overlays, patch.agents uses the same operations to modify the MAS
participant list at spec.agency.agents. add and replace entries are
{id, ref} agent references, remove is a list of agent ids, and clear: true
empties the list:
target:
kind: MAS
patch:
agents:
$op:
remove: [schedule_agent]
add:
- id: generalist
ref: ./agents/generalist.yaml
The former agents_add and agents_remove patch fields are not supported; use
patch.agents.$op instead.
context (prompt/role text) gets the same $op sugar, but per chunk name.
Each spec.context.<key> value may be a plain string/{ref} (implicit full
replace, same as always) or a list of fragments — a $op patch operates on
that fragment list, so an overlay can append or drop one line without
restating the rest of the base prompt:
patch:
context:
role:
$op:
add:
- "Confirm the budget before booking."
# remove:
# - "Confirm the budget before booking."
# replace:
# - "Whole new role text."
# clear: true
Document-level x-* keys on the overlay (and under spec.patch) are copied
onto the target document root, not into spec. Runtime merge of spec
fields ignores these keys. Later overlays win; nested dicts are RFC 7396-merged.
Inspect the compiled spec¶
mas-ctl compile agent.yaml -o overlays/tools.yaml -o overlays/skills.yaml
mas-ctl compile mas.yaml -o overlays/linear.yaml -O ./compiled/
--layout bundle inlines MAS agents into one YAML file; the default for a
directory --output keeps mas.yaml plus agents/*.yaml. See
compile.
Experiment linkage¶
scenarios:
- id: baseline
overlays: {logic: [baseline], control: [], infra: []}
- id: cot
overlays: {logic: [cot, no-tools], control: [], infra: []}
Tool provider and connection configuration is not patchable through agent
overlays. Remote MCP endpoints belong on infra
ToolServerRegistry; agent and overlay
schemas reject providers[]. See tool.md.
See also¶
- plugin-bindings.md — string shorthand vs
{type, params} - experiment.md — scenario overlay stacks
- compile — dump the resolved spec
- ToolContract
- ToolServerRegistry