#  Copyright (c) 2026 Cisco Systems, Inc. and its affiliates
#  SPDX-License-Identifier: Apache-2.0
$schema: "https://json-schema.org/draft-07/schema#"
$id: "mas/v1"
title: "Agent Manifest"
description: >
  Schema for kind: Agent manifests (apiVersion: mas/v1).
  Authoritative field list is derived from AgentSpec in agent.py.
  Validate with: python -m jsonschema --instance my-agent.yaml agent.schema.yaml
type: object
required:
  - apiVersion
  - kind
  - metadata
  - spec
additionalProperties: false
patternProperties:
  "^x-":
    description: "Extension properties (UI metadata, canvas state, etc.) — ignored by the runtime."
properties:
  apiVersion:
    type: string
    const: "mas/v1"

  kind:
    type: string
    const: "Agent"

  metadata:
    type: object
    required: [name]
    additionalProperties: false
    patternProperties:
      "^x-":
        description: "Extension properties (UI metadata) — ignored by the runtime."
    properties:
      name:
        type: string
        minLength: 1
        not:
          pattern: "[/\\\\]"
        description: >
          Agent ID — must match the workflow node id that references this
          agent. A filesystem path is not a name.
      description:
        type: string
        description: "Human-readable description of this agent."
        default: ""
      version:
        type: string
        pattern: "^[0-9]+\\.[0-9]+\\.[0-9]+.*$"
        default: "0.1.0"
      tags:
        type: array
        items:
          type: string
        description: "Free-form tags for filtering and grouping."
        default: []

  spec:
    type: object
    required: [description]
    additionalProperties: false
    properties:
      context:
        type: object
        description: >
          Named chunks injected into the system prompt as [key] content.
          Keys are author-defined (role, intent, tool_usage, …). Values may be
          inline text, a path to an existing file (./prompts/role.md or
          prompts/role.md), or {ref: path} which always loads the file.
        x-merge:
          strategy: context_merge
        additionalProperties:
          $ref: "./fragments/context-chunk.schema.yaml"

      params:
        type: object
        description: >
          Free-form key/value params threaded onto the merged spec dict for
          infra middleware and session-scoped side-channels to read (e.g.
          ctl/session/params_sidecar.py, engine/infra_pipeline.py). Not
          consumed directly by the kernel — a passthrough bag.
        x-merge:
          strategy: mapping_merge_or_ops
        additionalProperties:
          type: string
        default: {}

      description:
        type: string
        minLength: 1
        x-merge:
          strategy: replace
        description: >
          Routing-facing one-liner for delegation tools, AgentCard, and registry
          discovery. Not injected into the LLM system prompt — use spec.context.*
          for prompt text. Write from the delegator's perspective.

      models:
        type: array
        description: >
          Preferred model(s) for this agent. ``kind`` selects the wire-protocol
          plugin for model calls.
          Connection fields (api_base, api_key_env) belong on infra, not here.
          Static sampling/reasoning live on each row; per-call dynamic fields
          are LLMProvider.chat_completion kwargs. Capabilities:
          docs/manifests/llm-model-catalog.md.
        default: []
        x-merge:
          strategy: named_list_merge
          identity: id
        items:
          allOf:
            - $ref: "./fragments/llm-model-entry.schema.yaml"
            - required: [model]

      design_pattern:
        description: >
          Reasoning strategy. String shorthand is the plugin name
          (`design_pattern: cot` ≡ `{type: cot}`). Defaults to react when absent.
          See docs/manifests/plugin-bindings.md.
        x-merge:
          strategy: plugin_binding_merge
        oneOf:
          - type: "null"
          - type: string
            minLength: 1
            description: Plugin name (react, cot, plan_execute, …).
          - type: object
            additionalProperties: false
            properties:
              type:
                type: string
                description: >
                  Plugin name — mutually exclusive with ref.
                  react | cot | cot_native | plan_execute | tree_of_thoughts | accumulator | role aliases.
              ref:
                type: string
                description: >
                  Unified plugin locator — mutually exclusive with type.
                  Forms: bare-name, ./local/path, module://pkg.Class, oci://registry/img:tag
              params:
                $ref: "./fragments/design-pattern-config.schema.yaml"
                description: Plugin-specific parameters (max_steps → kernel dispatch cap).
                default: {}
              config:
                $ref: "./fragments/design-pattern-config.schema.yaml"
                description: Alias for params (overlay compatibility).
                default: {}

      context_manager:
        description: >
          Conversation-history plugin. String shorthand is the plugin name
          (`context_manager: stack` ≡ `{type: stack}`). Defaults to summarising
          when absent. Builtin types: summarising | sliding-window | stack |
          full-history. See docs/manifests/plugin-bindings.md and context-assembly.md.
        x-merge:
          strategy: plugin_binding_merge
        oneOf:
          - type: string
            minLength: 1
          - type: object
            additionalProperties: false
            properties:
              type:
                type: string
                description: "Plugin name — mutually exclusive with ref: summarising | sliding-window | stack | full-history."
              ref:
                type: string
                description: "Unified plugin locator — mutually exclusive with type. Same forms as design_pattern.ref."
              params:
                $ref: "./fragments/context-manager-params.schema.yaml"
                default: {}
              memory:
                type: array
                description: >
                  Memory types whose content is auto-injected into the system
                  prompt as context facets.  E.g. [semantic].
                default: []
                items:
                  type: string
            allOf:
              - if:
                  properties:
                    type:
                      enum: [summarising, summarizing]
                  required: [type]
                then:
                  properties:
                    params:
                      $ref: "./fragments/context-manager-summarising-params.schema.yaml"
              - if:
                  properties:
                    type:
                      enum: [sliding-window, sliding_window]
                  required: [type]
                then:
                  properties:
                    params:
                      $ref: "./fragments/context-manager-sliding-window-params.schema.yaml"
              - if:
                  properties:
                    type:
                      enum: [stack, full-history, full_history]
                  required: [type]
                then:
                  properties:
                    params:
                      $ref: "./fragments/context-manager-stack-params.schema.yaml"

      assembler:
        description: >
          Prompt assembler. Builds messages[] for each LLM call. String shorthand
          is the plugin name (`assembler: assembler`). Defaults to assembler
          (ContextAssemblerPlugin) when absent. See plugin-bindings.md.
        x-merge:
          strategy: plugin_binding_merge
        oneOf:
          - type: "null"
          - type: string
            minLength: 1
          - type: object
            additionalProperties: false
            properties:
              type:
                type: string
                description: Plugin name (default assembler / alias context-assembler).
              ref:
                type: string
                description: Unified plugin locator — mutually exclusive with type.
              params:
                type: object
                description: Constructor kwargs for the assembler plugin.
                additionalProperties: true
                default: {}
                properties:
                  token_budget:
                    type: integer
                    minimum: 1
                    description: Max estimated tokens for injected context parts.
                  always_reassemble:
                    type: boolean
                    default: false
                  emit_segments:
                    type: boolean
                    default: true

      memory_seed:
        type: array
        description: >
          Documents to pre-index into the memory backend at startup.
          Useful for demos and tests that need pre-populated memory
          without a persistent store.  Each entry is indexed via
          index_document(source, content, metadata?).
        x-merge:
          strategy: list_ops
          identity: value
        items:
          $ref: "./fragments/memory-seed-entry.schema.yaml"

      memory:
        description: >
          Memory configuration for this agent.  Three forms:
          - String shorthand: "semantic" — resolved to plugin bundle.
          - Array shorthand: ["semantic", "episodic"] — multiple bundles.
          - Full object: memory.types, memory.compaction, etc.
          When absent, the active flavour decides defaults.
        x-merge:
          strategy: replace
        oneOf:
          - type: string
            description: >
              Shorthand: a single memory type name, e.g. "semantic".
              Resolved to the corresponding plugin bundle by the builder.
          - type: array
            description: >
              List of memory type names, e.g. ["semantic", "episodic"].
            items:
              type: string
          - type: object
            description: >
              Full memory configuration object.
            additionalProperties: false
            properties:
              enabled:
                type: boolean
                description: "Master switch.  false = no memory plugins loaded."
                default: true

              types:
                type: array
                description: >
                  Ordered list of memory layers to activate.  The runtime instantiates
                  one MemoryProviderPlugin per entry.  When absent, the flavour
                  decides which layers to activate.
                default: []
                items:
                  type: object
                  required: [name]
                  additionalProperties: false
                  properties:
                    name:
                      type: string
                      description: >
                        Memory layer name: session | episodic | semantic | working | procedural.
                    backend:
                      type: string
                      description: >
                        Backend implementation: in-memory | file | sqlite-vec | redis | remote_tool.
                      default: "in-memory"
                    params:
                      $ref: "./fragments/memory-backend-params.schema.yaml"
                      default: {}

              # NOTE: compaction lived here (spec.memory.compaction) but was
              # never wired to any code -- zero consumers anywhere in the
              # repo. Removed; conversation compaction is now
              # spec.working_memory.compaction (below), backed by the real,
              # tested context_manager/CMFactory machinery. See
              # docs/design/working-memory-compaction.md.

              persistence:
                type: object
                description: >
                  Session transcript persistence.  Controls where and how conversation
                  history is saved between runs.
                additionalProperties: false
                properties:
                  backend:
                    type: string
                    description: "Persistence backend: none | file | sqlite | redis."
                    default: "none"
                  path:
                    type: string
                    description: >
                      Base directory for file-based persistence.  Supports {agent_id}
                      and {session_id} placeholders.  Default when unset:
                      $XDG_DATA_HOME/mas/agents/{agent_id}/sessions/
                      (see docs/user-config.md).
                    default: ""
                  auto_save:
                    type: boolean
                    description: "Automatically persist after each turn."
                    default: true
                  auto_load:
                    type: boolean
                    description: "Automatically load session on bootstrap."
                    default: true

              search:
                type: object
                description: >
                  Semantic memory search configuration.  Controls vector retrieval
                  from the semantic memory layer.
                additionalProperties: false
                properties:
                  enabled:
                    type: boolean
                    description: "Enable semantic search injection into context."
                    default: false
                  max_results:
                    type: integer
                    description: "Maximum retrieved chunks per query."
                    default: 6
                  min_score:
                    type: number
                    description: "Minimum similarity score threshold."
                    default: 0.35
                  chunking:
                    type: object
                    additionalProperties: false
                    properties:
                      tokens:
                        type: integer
                        description: "Chunk size in tokens for indexing."
                        default: 400
                      overlap:
                        type: integer
                        description: "Overlap tokens between chunks."
                        default: 80
                  hybrid:
                    type: object
                    description: "Hybrid search (vector + full-text) configuration."
                    additionalProperties: false
                    properties:
                      enabled:
                        type: boolean
                        default: true
                      vector_weight:
                        type: number
                        default: 0.7
                      text_weight:
                        type: number
                        default: 0.3
                      mmr_enabled:
                        type: boolean
                        description: "Max Marginal Relevance re-ranking."
                        default: false
                      mmr_lambda:
                        type: number
                        default: 0.7
                      temporal_decay_enabled:
                        type: boolean
                        default: false
                      temporal_decay_half_life_days:
                        type: integer
                        default: 30
                  cache:
                    type: object
                    additionalProperties: false
                    properties:
                      enabled:
                        type: boolean
                        default: true
                      max_entries:
                        type: integer
                        default: 128

              citations:
                type: object
                description: >
                  Citation formatting for memory search results.
                  Controls whether and how source references are included
                  in search results injected into context.
                additionalProperties: false
                properties:
                  mode:
                    type: string
                    enum: [auto, "on", "off"]
                    description: >
                      Citation mode: auto (include when useful), on (always include),
                      off (strip source info).  Controls citation presentation for memory results.
                    default: "auto"
                  max_snippet_chars:
                    type: integer
                    description: "Maximum characters per search result snippet."
                    default: 700

              sync:
                type: object
                description: >
                  File watching and re-indexing synchronization policy.
                  Controls when workspace memory files (MEMORY.md, memory/*.md)
                  are re-indexed into semantic memory.
                additionalProperties: false
                properties:
                  on_session_start:
                    type: boolean
                    description: "Re-index when a session begins."
                    default: true
                  on_search:
                    type: boolean
                    description: "Re-index before search (ensures fresh results)."
                    default: false
                  interval_seconds:
                    type: integer
                    description: "Periodic re-index interval in seconds (0 = disabled)."
                    default: 300
                  delta_messages:
                    type: integer
                    description: "Re-index after this many new messages."
                    default: 50
                  delta_bytes:
                    type: integer
                    description: "Re-index after this many bytes of new content."
                    default: 100000
                  post_compaction_force:
                    type: boolean
                    description: "Force re-index after compaction."
                    default: true
                  file_watch:
                    type: object
                    description: "File change detection settings."
                    additionalProperties: false
                    properties:
                      enabled:
                        type: boolean
                        default: true
                      patterns:
                        type: array
                        description: "Glob patterns to watch for changes."
                        items:
                          type: string
                        default: ["MEMORY.md", "memory/*.md"]
                      debounce_ms:
                        type: integer
                        description: "Debounce interval for file change detection."
                        default: 1500

              overflow_retry:
                type: object
                description: >
                  Compaction retry on context overflow.  When an LLM call fails
                  because the context exceeds the window, automatically compact
                  and retry when context exceeds the model window.
                additionalProperties: false
                properties:
                  enabled:
                    type: boolean
                    description: "Enable overflow detection and automatic retry."
                    default: false
                  max_retries:
                    type: integer
                    description: "Maximum compaction+retry attempts."
                    default: 2
                  aggregate_timeout_seconds:
                    type: number
                    description: "Maximum total time for all retries."
                    default: 60.0
                  budget_reduction_factor:
                    type: number
                    description: "Multiply budget by this factor on each retry."
                    default: 0.7

              qmd:
                type: object
                description: >
                  QMD (Qualitative Memory Database) backend configuration.
                  Alternative search via external qmd binary.
                additionalProperties: false
                properties:
                  enabled:
                    type: boolean
                    description: "Enable QMD as a retriever backend."
                    default: false
                  binary_path:
                    type: string
                    description: "Path to qmd binary (auto-detected if empty)."
                    default: ""
                  index_path:
                    type: string
                    description: "Path to QMD index directory."
                    default: ""
                  search_mode:
                    type: string
                    description: "QMD search mode."
                    default: "search"
                  max_snippet_chars:
                    type: integer
                    default: 700
                  timeout_seconds:
                    type: number
                    default: 4.0

      working_memory:
        type: object
        description: >
          Controls whether this agent's committed conversation history
          (user/assistant turns — never the system prompt, which is always
          rebuilt fresh from the manifest at materialize time) survives
          across separate delegation calls within the same mas-ctl session,
          keyed by (session_id, agent_id) in an in-process registry.
          Distinct from spec.memory: that's the MemoryContract retrieval-store
          subsystem (semantic/episodic/procedural search); this is the raw
          turn buffer a delegated agent falls back on. Only consulted on the
          delegation path — an entry agent driven directly by the user
          already keeps its own history across turns regardless of this
          field.
        x-merge:
          strategy: mapping_merge_or_ops
        additionalProperties: false
        properties:
          persistent:
            type: boolean
            description: >
              true (default): reuse committed turn history across delegate
              calls within the same session_id — the sub-agent continues its
              previous exchange instead of starting fresh. false: clear
              working memory before every delegate call, even within the
              same session, for agents that must stay stateless per call.
            default: true
          compaction:
            type: object
            description: >
              How much of this agent's committed history to include in each
              LLM call. Sugar for spec.context_manager — not a second engine
              and not a working-memory cache. Compaction is an assembly-time
              view (manage_history); the summarising plugin caches the last
              summary (hysteresis). Prefer spec.context_manager. Ignored when
              context_manager is already set.
            additionalProperties: false
            properties:
              strategy:
                type: string
                enum: [keep_recent, sliding_window, summarize]
                default: keep_recent
                description: >
                  keep_recent (default): cap total messages at max_messages,
                  no LLM call. sliding_window: keep the last window_size
                  user/assistant exchange pairs, no LLM call. summarize:
                  compress older turns into one summary block via an LLM
                  call (default: summarizer slot, else this agent's turn
                  model), keep the last keep_turns pairs verbatim. See
                  docs/manifests/summarization.md.
              max_messages:
                type: integer
                minimum: 1
                description: "keep_recent — cap on total committed messages."
              window_size:
                type: integer
                minimum: 1
                description: "sliding_window — number of recent exchange pairs to keep."
              summary_threshold:
                type: integer
                minimum: 0
                description: >
                  summarize — estimated-token threshold before compaction
                  triggers. 0 / omitted uses model context_window minus
                  completion reserve (same as spec.context_manager).
              keep_turns:
                type: integer
                minimum: 1
                default: 10
                description: "summarize — recent exchange pairs kept verbatim alongside the summary."
              model:
                type: string
                description: >
                  summarize — model for the summary LLM call. A spec.models[].id
                  or a LiteLLM model string. Default: this agent's primary
                  model (same as the turn). Sugar for
                  context_manager.params.summarizer.params.model.

      skills:
        type: array
        description: >
          Skills to activate for this agent.  Each entry is a string with two forms:

            # Name only — resolved from the default locator chain:
            skills:
              - triage-protocol
              - memory-protocol

            # @library/name — explicit library source:
            skills:
              - "@web-skills/search-protocol"
              - "@mas.library.samples/web-search-skill"

          No file paths, no directory references.  Resolution order:
          1. App-local skills/ directory (if present).
          2. Libraries declared under 'libraries:' in config.yaml.
          3. Installed Python packages.
        x-merge:
          strategy: list_ops
          identity: value
        default: []
        items:
          type: string
          description: >
            Skill name (resolved from default locator chain) or
            @library/name (explicit library source).

      tools_ref:
        type: [string, "null"]
        x-merge:
          strategy: replace
        description: >
          Logical tool-set name resolved by the infra manifest ToolRegistry.
          No file paths, no extensions — a simple identifier only.
          The infra ToolRegistry maps this name to the actual JSON tool-index
          file path. E.g. web-tools, backend-tools, verifier-tools.
        pattern: "^[a-z0-9][a-z0-9_-]*$"
        default: null

      tools:
        type: array
        x-merge:
          strategy: list_ops
          identity: value
        description: >
          Tool declarations for this agent.  Three forms are supported:

          Form A — ref to a kind: Tool manifest (recommended):
            tools:
              - ref: ./tools/calculator.tool.yaml
              - ref: bundle://web-tools/fetch-page   # ToolBundle entry

          Form B — inline anonymous (backward-compatible):
            tools:
              - module_path: library-samples/tools/calc.py
                class_name: CalcTool

          Form D — system tools (runtime-owned; exposed only when declared
          here or implied by the manifest):
            tools:
              - kind: system
                name: request_human_input
                params: {timeout: 300}
              - kind: system
                name: run_skill_script

          ``activate_skill`` is implicit when ``spec.skills`` lists at
          least one skill. ``run_skill_script`` is implicit when a listed
          skill has a ``scripts/`` directory. ``request_human_input`` and
          ``inform_user`` are never implicit. Unknown names are rejected.
          See docs/manifests/system-tools.md.

          Additive with tools_ref — both may be set simultaneously.
        default: []
        items:
          oneOf:
            # ── Form C: semantic name (bare string) ──────────────────────
            - type: string
              description: >
                Semantic tool name resolved by the active infra/local tool catalog.
                E.g. web-search, calculator, memory-search.

            # ── Form A: ref —————————————————————————————————————————————
            - type: object
              required: [ref]
              additionalProperties: false
              description: >
                Reference to a kind: Tool manifest or a ToolBundle entry.
                Accepted values:
                  ./path/to/tool.tool.yaml        — relative path to kind: Tool manifest
                  bundle://<bundle-name>/<tool-id> — named entry in a ToolBundle
              properties:
                ref:
                  type: string
                  description: >
                    Path to a kind: Tool manifest (./tools/calculator.tool.yaml) or a
                    ToolBundle entry reference (bundle://web-tools/fetch-page).
                priority:
                  type: integer
                  description: "Registration priority (higher = loaded first)."
                  default: 100
                params:
                  type: object
                  description: >
                    Init kwargs merged into the referenced tool's own
                    spec.impl.params (this entry's keys win on conflict) —
                    e.g. {impl: adk, base_dir: ./skills/} for a skill tool ref.
                  additionalProperties: true
                  default: {}

            # ── Form D: system tool ──────────────────────────────────────
            - type: object
              required: [kind, name]
              additionalProperties: false
              description: >
                Runtime-owned system tool. request_human_input and inform_user
                are injected by default. activate_skill is implicit when
                spec.skills is non-empty; declare it here to opt in explicitly.
                run_skill_script is implicit when a listed skill has scripts/;
                declare it here to opt in without shipping scripts yet.
              properties:
                kind:
                  type: string
                  const: system
                name:
                  type: string
                  minLength: 1
                enabled:
                  type: boolean
                  default: true
                  description: Explicit per-tool opt-in/out. `false` overrides host requests.
                params:
                  type: object
                  additionalProperties: true
                  default: {}

            # ── Form B: inline anonymous (backward-compatible) ───────────
            - type: object
              required: [module_path]
              additionalProperties: false
              description: >
                Inline anonymous tool declaration.  Provide module_path at minimum;
                class_name is auto-discovered when omitted.
                Use a kind: Tool manifest (Form A) instead when the description and
                parameters need to be declared explicitly.
              properties:
                kind:
                  type: string
                  enum: [python, remote_tool, openapi]
                  description: >
                    Implementation kind — defaults to python.
                    python  — ToolContract subclass loaded via module_path.
                    remote_tool — remote tool-server tool; module_path points to ToolServerClient adapter.
                    openapi — OpenAPI endpoint; module_path points to OpenAPITool adapter.
                  default: python
                module_path:
                  type: string
                  description: >
                    Dotted Python module path, e.g. library-samples/tools/calc.py.
                    May also be a relative filesystem path (./tools/my_tool.py).
                class_name:
                  type: string
                  description: "Class name in the module. Auto-discovered when omitted."
                priority:
                  type: integer
                  description: "Registration priority (higher = loaded first)."
                  default: 100
                params:
                  type: object
                  description: "Optional tool-specific init params."
                  additionalProperties: true
                  default: {}

      behavior:
        type: object
        x-merge:
          strategy: mapping_merge_or_ops
        description: >
          Per-agent runtime behavior configuration.
          These settings are structural/semantic — they affect what system tools and
          capabilities are exposed to this agent's LLM at runtime.
          Do NOT put secrets, API keys, or deployment config here (use flavour.yaml).
        additionalProperties: false
        properties:
          share_reasoning:
            type: boolean
            default: false
            description: >
              When true, the send_to_caller tool exposes an optional reasoning_context
              parameter.  The sub-agent can (but is not required to) provide a concise
              summary of HOW it reached its answer — not its message history, but
              key evidence, decision points, and confidence signals.
              Forwarded to the orchestrator as result["reasoning"] so it can bridge
              the reasoning-context gap between delegation and synthesis.
              Privacy-by-default: false.  Enable only for trusted internal agents
              where context leakage is acceptable.
          delegation_style:
            type: string
            enum: [typed]
            default: typed
            description: >
              Controls which delegation tools are registered for this agent.

              typed (only value in this release): delegate_to_<id> tools derived
              from MAS workflow.delegates_to. Peer
              IDs are fixed at compose time — the LLM cannot call agents outside
              the topology.

              generic (delegate_task with free-form agent_id) is a design target —
              not implemented in OSS; see ROADMAP.

      checkpoint:
        type: object
        description: Session checkpoint cadence, portability, and retention policy.
        x-merge:
          strategy: mapping_merge_or_ops
        additionalProperties: false
        properties:
          mode:
            type: string
            enum: [none, in_memory, on_event, every_turn]
            default: none
            description: >
              When to capture a checkpoint. ``none`` is manual only
              (``--save-checkpoint``, control persist). ``on_event`` uses
              ``triggers``. ``every_turn`` writes after each completed user
              turn. ``in_memory`` is a storage alias (process-local) kept for
              existing manifests; prefer ``storage.kind``.
          storage:
            description: >
              Where checkpoint payloads live. Independent of cadence (mode).
              Omit to infer from mode: ``in_memory`` → memory; ``on_event`` /
              ``every_turn`` → disk. Hybrid keeps an in-process copy for
              backtrack and writes files so a later process can resume.
            oneOf:
              - type: string
                enum: [memory, disk, hybrid]
              - type: object
                additionalProperties: false
                properties:
                  kind:
                    type: string
                    enum: [memory, disk, hybrid]
                  path:
                    type: string
                    description: >
                      Directory for disk/hybrid files, relative to the agent
                      directory unless absolute. CLI ``--checkpoint-dir``
                      overrides this path.
          triggers:
            type: array
            items:
              type: string
              enum: [after_llm_call, after_tool_call, before_destructive_tool, every_n_turns]
            default: []
          every_n_turns:
            type: integer
            minimum: 1
          retention:
            type: object
            additionalProperties: false
            properties:
              mode:
                type: string
                enum: [all, single, last_n]
                default: all
              n:
                type: integer
                minimum: 1
                default: 5
          portability:
            type: string
            enum: [self_contained, reference, deep, shallow]
            default: self_contained
            description: >
              How much of the conversation archive a checkpoint blob
              embeds. self_contained/deep hydrates every payload (portable
              without the archive; right for a single snapshot).
              reference/shallow stores covering heads that point at
              traces/archive (cheap when tracing every checkpoint).
              Shallow requires the archive on disk; without it the runtime
              falls back to deep.
          auto_resume_latest:
            type: boolean
            default: false

      debug:
        type: object
        additionalProperties: false
        description: >
          gdb-like debug_script plugin (script / script_file).
          Enable the plugin from config.yaml plugins / lab.enable_plugins.
          This object is the script config (script_file or inline script).
        properties:
          script_file:
            type: string
          script:
            type: string

      governance:
        $ref: "./fragments/governance-binding.schema.yaml"
        description: >
          Governance bindings (typically applied via overlay). See
          spec-contract-bindings.md — flat scalars + ingress_plugins[] only.
        x-merge:
          strategy: plugin_list_ops
          identity: plugin_id
        default: {}

      hitl_contract:
        description: Human approval contract; caller-injected options override this manifest binding.
        x-merge:
          strategy: plugin_binding_merge
        oneOf:
          - type: string
            minLength: 1
          - type: object
            additionalProperties: false
            properties:
              type: { type: string }
              ref: { type: string }
              params: { type: object, additionalProperties: true }

      user_io_contract:
        description: User progress-update contract; caller-injected options override this manifest binding.
        x-merge:
          strategy: plugin_binding_merge
        oneOf:
          - type: string
            minLength: 1
          - type: object
            additionalProperties: false
            properties:
              type: { type: string }
              ref: { type: string }
              params: { type: object, additionalProperties: true }

      llm:
        $ref: "./fragments/llm-binding.schema.yaml"
        description: >
          Deprecated compat shim for model id and sampling params. Prefer
          spec.models[]. Overlays may still patch provider/sampling hints here until
          migrated.
        x-merge:
          strategy: mapping_merge_or_ops
        default: {}

      budget:
        type: object
        x-merge:
          strategy: mapping_merge_or_ops
        description: >
          Call-count ceilings enforced by the runtime engine during a run
          (LiveLlmEngine / BudgetTracker). Omit a field, or the whole
          object, for no limit -- this is the default. Typically left
          unset on the agent manifest and set via an overlay instead
          (e.g. pkg://mas.library.standard/overlays/with-hardened.yaml).
          Distinct from spec.control.budget (token/cost accounting; not
          yet wired to the engine).
        additionalProperties: false
        properties:
          max_llm_calls:
            type: integer
            minimum: 0
            description: Maximum LLM calls allowed in a run. Omitted means no limit.
          max_tool_calls:
            type: integer
            minimum: 0
            description: Maximum tool calls allowed in a run. Omitted means no limit.
        default: {}

      control:
        $ref: "./fragments/control-binding.schema.yaml"
        x-merge:
          strategy: mapping_ops
        description: "Control-plane plugin configs keyed by plugin id."
        default: {}

      observability:
        $ref: "./fragments/observability-binding.schema.yaml"
        description: "ObservabilitySink plugin list — list form only."
        x-merge:
          strategy: plugin_list_ops
          identity: plugin_id
        default: null

      context_sources:
        $ref: "./fragments/context-sources-binding.schema.yaml"
        description: >
          ContextContract source plugin list — list form only, same shape as
          observability/governance. Selects which skill-plugin engine
          (native, adk, langchain) backs SkillCatalogPlugin/SkillToolsPlugin.
        x-merge:
          strategy: plugin_list_ops
          identity: plugin_id
        default: null
