{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "spt-core runtime manifest",
  "description": "Per-adapter runtime manifest for the spt-core harness contract. Authored as TOML (this schema describes the equivalent data model). A manifest declares only what varies per harness/shell; command templates are opaque strings spt-core never parses. Cross-field invariants (kind<->[shell] agreement, strategy/avenue field requirements) are enforced by spt-core's validate step beyond this schema.",
  "type": "object",
  "properties": {
    "adapter": {
      "$ref": "#/$defs/Adapter"
    },
    "hooks": {
      "description": "`[hooks.<event>]` — inbound hook table, keyed by harness event name.",
      "type": "object",
      "additionalProperties": {
        "$ref": "#/$defs/Hook"
      }
    },
    "session": {
      "description": "`[session]` — watched-dir keys plus the `[session.<role>]` templates.",
      "$ref": "#/$defs/Session"
    },
    "env": {
      "description": "`[env.<VAR>]` — env-var inject/read table.",
      "type": "object",
      "additionalProperties": {
        "$ref": "#/$defs/EnvVar"
      }
    },
    "history": {
      "anyOf": [
        {
          "$ref": "#/$defs/History"
        },
        {
          "type": "null"
        }
      ]
    },
    "inject": {
      "anyOf": [
        {
          "$ref": "#/$defs/Inject"
        },
        {
          "type": "null"
        }
      ]
    },
    "identity": {
      "anyOf": [
        {
          "$ref": "#/$defs/Identity"
        },
        {
          "type": "null"
        }
      ]
    },
    "update": {
      "anyOf": [
        {
          "$ref": "#/$defs/Update"
        },
        {
          "type": "null"
        }
      ]
    },
    "shell": {
      "description": "`[shell]` body — present iff `adapter.kind = \"shell\"` (validated).",
      "anyOf": [
        {
          "$ref": "#/$defs/Shell"
        },
        {
          "type": "null"
        }
      ]
    },
    "pty_digest": {
      "description": "`[pty_digest]` — the live-activity-buffer pattern seam (ADR-0008). The\nadapter supplies the boundary + tool patterns; spt-core only *runs* them\nover the broker PTY byte stream (the no-built-in-parser rule — identical to\nopaque command templates). Absent ⇒ the digest is unavailable for this\nadapter.",
      "anyOf": [
        {
          "$ref": "#/$defs/PtyDigest"
        },
        {
          "type": "null"
        }
      ]
    }
  },
  "required": [
    "adapter"
  ],
  "$defs": {
    "Adapter": {
      "description": "`[adapter]` — the manifest header, readable before any update (compat gate).",
      "type": "object",
      "properties": {
        "name": {
          "type": "string"
        },
        "kind": {
          "$ref": "#/$defs/AdapterKind",
          "default": "harness"
        },
        "version": {
          "type": "string"
        },
        "min_spt_core_version": {
          "description": "Lowest spt-core version this adapter tolerates (compat gate).",
          "type": "string"
        },
        "hostable_types": {
          "description": "Endpoint types this adapter can host (`LiveAgent`, `Worker`, …).",
          "type": "array",
          "items": {
            "type": "string"
          }
        }
      },
      "required": [
        "name",
        "version",
        "min_spt_core_version"
      ]
    },
    "AdapterKind": {
      "description": "The two adapter kinds. A `harness` hosts agents; a `shell` provides a driven\nsurface (MANIFEST §Shell adapters).",
      "type": "string",
      "enum": [
        "harness",
        "shell"
      ]
    },
    "Hook": {
      "description": "`[hooks.<event>]` — one harness event → the `api` command it fires, the\nstdin fields it maps, and whether it can surface context to the agent.",
      "type": "object",
      "properties": {
        "fires": {
          "description": "Opaque `api …` command line the harness invokes for this event.",
          "type": "string"
        },
        "reads": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "can_inject": {
          "description": "Whether this hook can inject context (false ⇒ sentinel/relay fallback).",
          "type": "boolean",
          "default": false
        }
      },
      "required": [
        "fires"
      ]
    },
    "Session": {
      "description": "`[session]` — the watched-dir keys (`commune_dir`/`signoff_dir`) co-located\nwith the fixed set of `[session.<role>]` command templates.",
      "type": "object",
      "properties": {
        "commune_dir": {
          "type": [
            "string",
            "null"
          ]
        },
        "signoff_dir": {
          "type": [
            "string",
            "null"
          ]
        },
        "self": {
          "anyOf": [
            {
              "$ref": "#/$defs/SessionRole"
            },
            {
              "type": "null"
            }
          ]
        },
        "psyche_init": {
          "anyOf": [
            {
              "$ref": "#/$defs/SessionRole"
            },
            {
              "type": "null"
            }
          ]
        },
        "psyche_resume": {
          "anyOf": [
            {
              "$ref": "#/$defs/SessionRole"
            },
            {
              "type": "null"
            }
          ]
        },
        "echo_commune": {
          "anyOf": [
            {
              "$ref": "#/$defs/SessionRole"
            },
            {
              "type": "null"
            }
          ]
        },
        "signoff": {
          "anyOf": [
            {
              "$ref": "#/$defs/SessionRole"
            },
            {
              "type": "null"
            }
          ]
        },
        "notif": {
          "description": "`[session.notif]` — the endpoint-native notification render\n(ADR-0007's `notif_command` seam, REQ-NOTIF-2): an OS toast, a\nGameRobot `alert-symbol`, anything the adapter can run. Spawned\ndetached when a notif surfaces at this endpoint, combinable with the\nagent-surface delivery. Keys spt-core fills: `{notif_id}`,\n`{notif_from}`, `{notif_subnet}`, `{notif_body}`.",
          "anyOf": [
            {
              "$ref": "#/$defs/SessionRole"
            },
            {
              "type": "null"
            }
          ]
        }
      }
    },
    "SessionRole": {
      "description": "`[session.<role>]` — one opaque outbound command template plus its spawn\ncontext. Model/tools/flags all live inside `command`, never as fields\n(MANIFEST §session roles). No nested tables here (keeps TOML round-trip\nemission scalar-before-table clean).",
      "type": "object",
      "properties": {
        "command": {
          "description": "Opaque command line, with `{key}` substitution placeholders.",
          "type": "string"
        },
        "cwd": {
          "type": [
            "string",
            "null"
          ]
        },
        "recursion_guard_env": {
          "description": "Env var set on summarizer children so their own hooks bail (recursion\nguard).",
          "type": [
            "string",
            "null"
          ]
        },
        "detach": {
          "type": "boolean",
          "default": false
        },
        "env_remove": {
          "description": "Env vars to strip from the child's inherited environment.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "keys": {
          "description": "Substitution keys spt-core guarantees to fill for this role.",
          "type": "array",
          "items": {
            "type": "string"
          }
        }
      },
      "required": [
        "command"
      ]
    },
    "EnvVar": {
      "description": "`[env.<VAR>]` — a single env-var directive.",
      "type": "object",
      "properties": {
        "direction": {
          "$ref": "#/$defs/EnvDirection"
        },
        "value": {
          "description": "Value to inject (with substitution); required for `inject`.",
          "type": [
            "string",
            "null"
          ]
        },
        "channel": {
          "description": "Harness-hosted injection channel (spt-hosted inherits from the broker).",
          "type": [
            "string",
            "null"
          ]
        }
      },
      "required": [
        "direction"
      ]
    },
    "EnvDirection": {
      "type": "string",
      "enum": [
        "inject",
        "read"
      ]
    },
    "History": {
      "description": "`[history]` — transcript access strategy.",
      "type": "object",
      "properties": {
        "strategy": {
          "$ref": "#/$defs/HistoryStrategy"
        },
        "fetcher": {
          "description": "`fetcher` strategy: adapter binary emitting normalized history.",
          "type": [
            "string",
            "null"
          ]
        },
        "locate_template": {
          "description": "`locate_normalize` strategy: where the raw transcript lives.",
          "type": [
            "string",
            "null"
          ]
        },
        "normalize_command": {
          "description": "`locate_normalize` strategy: command normalizing the raw transcript.",
          "type": [
            "string",
            "null"
          ]
        }
      },
      "required": [
        "strategy"
      ]
    },
    "HistoryStrategy": {
      "oneOf": [
        {
          "description": "spt-core asks the adapter (pull, adapter binary emits normalized).",
          "type": "string",
          "const": "fetcher"
        },
        {
          "description": "spt-core locates the raw transcript then normalizes it.",
          "type": "string",
          "const": "locate_normalize"
        },
        {
          "description": "Adapter pushes via `api history-log`; spt-core stores (Path-B).",
          "type": "string",
          "const": "native"
        }
      ]
    },
    "Inject": {
      "description": "`[inject]` — inject-input methods per activity state.",
      "type": "object",
      "properties": {
        "activity": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/InjectMethod"
          }
        },
        "idle": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/InjectMethod"
          }
        }
      }
    },
    "InjectMethod": {
      "type": "string",
      "enum": [
        "pty",
        "hook",
        "relay",
        "http"
      ]
    },
    "Identity": {
      "description": "`[identity]` — how the harness's session id is obtained.",
      "type": "object",
      "properties": {
        "session_id_source": {
          "$ref": "#/$defs/SessionIdSource"
        },
        "parent_ancestor_name": {
          "description": "Process-tree anchor name when `session_id` is absent.",
          "type": [
            "string",
            "null"
          ]
        }
      },
      "required": [
        "session_id_source"
      ]
    },
    "SessionIdSource": {
      "oneOf": [
        {
          "description": "Discovered after spawn (process-tree / wrapper handoff).",
          "type": "string",
          "const": "post_spawn"
        },
        {
          "description": "Injected as a UUID the harness echoes back.",
          "type": "string",
          "const": "uuid_inject"
        }
      ]
    },
    "Update": {
      "description": "`[update]` — adapter self-update directive (parsed in M2a; conducted in M3).",
      "type": "object",
      "properties": {
        "avenue": {
          "$ref": "#/$defs/UpdateAvenue"
        },
        "command": {
          "description": "`delegated` avenue: the command spt-core delegates to.",
          "type": [
            "string",
            "null"
          ]
        },
        "repo": {
          "description": "`file_pull` avenue: source repo.",
          "type": [
            "string",
            "null"
          ]
        },
        "path_regex": {
          "description": "`file_pull` avenue: path selector.",
          "type": [
            "string",
            "null"
          ]
        },
        "signing_key": {
          "description": "`file_pull` avenue: the adapter's Ed25519 **content-signing public key**\n(64 hex chars / 32 bytes). spt-core verifies a pulled payload against this\nper-adapter key before applying it (REQ-UPD-5 adapter content signing,\nADR-0004 §D) — the adapter author signs their own releases; spt-core's\nrelease key stays scoped to spt-core. **Required for `file_pull`** (there\nare bytes to verify); not applicable to `delegated` (opaque updater).",
          "type": [
            "string",
            "null"
          ]
        },
        "self_verifies": {
          "description": "`delegated` avenue: the adapter attests its own updater verifies the\ncontent it installs (e.g. `claude.exe plugin update` checks its own\nsignatures). spt-core cannot see a delegated updater's bytes, so it\ndelegates the trust **only** when this is set; an unattested delegated\nupdate is skipped as unverifiable (REQ-UPD-5).",
          "type": "boolean",
          "default": false
        },
        "version_check": {
          "description": "Verify spt-core satisfies `min_spt_core_version` before/after.",
          "type": "boolean",
          "default": false
        },
        "uninstall": {
          "description": "Optional inverse of install — run by `spt adapter remove` once the adapter\nis quiesced (the mirror of `spt adapter add`, which reuses this section as\nthe install mechanism). Absent ⇒ spt-core's default cleanup. (Modeled in\nM2a; conducted with adapter-registration later.)",
          "type": [
            "string",
            "null"
          ]
        }
      },
      "required": [
        "avenue"
      ]
    },
    "UpdateAvenue": {
      "oneOf": [
        {
          "description": "Delegate to the adapter's own updater (e.g. `claude plugin update`).",
          "type": "string",
          "const": "delegated"
        },
        {
          "description": "spt-core pulls files from a repo.",
          "type": "string",
          "const": "file_pull"
        }
      ]
    },
    "Shell": {
      "description": "`[shell]` — the body of a `kind = \"shell\"` adapter (a driven surface).",
      "type": "object",
      "properties": {
        "spawn": {
          "description": "Broker-launched opaque spawn command.",
          "type": "string"
        },
        "ephemeral": {
          "description": "Ephemeral ⇒ no offline perch + no history retention.",
          "type": "boolean",
          "default": false
        },
        "broadcast": {
          "anyOf": [
            {
              "$ref": "#/$defs/Broadcast"
            },
            {
              "type": "null"
            }
          ]
        },
        "command_receipt": {
          "description": "How the shell receives agent commands.",
          "anyOf": [
            {
              "$ref": "#/$defs/CommandReceipt"
            },
            {
              "type": "null"
            }
          ]
        },
        "pre_close": {
          "description": "Instruction sent to the binary on link-break.",
          "type": [
            "string",
            "null"
          ]
        },
        "close_timeout_ms": {
          "description": "Graceful-termination window before force-close.",
          "type": [
            "integer",
            "null"
          ],
          "format": "uint64",
          "minimum": 0
        },
        "persistent": {
          "description": "Auto-online whenever the owner endpoint is online.",
          "type": "boolean",
          "default": false
        },
        "wake_command": {
          "description": "Long-running wake-watcher run WHILE offline; exit ⇒ revive.",
          "type": [
            "string",
            "null"
          ]
        },
        "can_shutdown": {
          "description": "Whether the shell may fire `api owner-shutdown` to suspend its owner.",
          "type": "boolean",
          "default": false
        },
        "require_approval": {
          "description": "Per-spawn user approval gate (floor; a node/endpoint setting may tighten).\nAbsent ⇒ `none`. (Modeled now; conducted when shells land.)",
          "$ref": "#/$defs/ShellApproval"
        },
        "max_instances_per_owner": {
          "description": "Optional ceiling on concurrent-existing instances per owner endpoint\n(online + offline both count). Absent ⇒ unlimited.",
          "type": [
            "integer",
            "null"
          ],
          "format": "uint32",
          "minimum": 0
        },
        "over_cap": {
          "description": "What happens at the cap: `reject` (default) or `approve` (per-spawn\napproval beyond the cap; does not raise it). Only meaningful with a cap.",
          "$ref": "#/$defs/OverCap"
        },
        "capabilities": {
          "description": "`[shell.capabilities]` — the command vocabulary (agent→shell).",
          "type": "object",
          "additionalProperties": {
            "$ref": "#/$defs/ShellCapability"
          }
        },
        "sensory": {
          "description": "`[shell.sensory]` — the sensory vocabulary (shell→agent).",
          "$ref": "#/$defs/Sensory"
        }
      },
      "required": [
        "spawn"
      ]
    },
    "Broadcast": {
      "type": "string",
      "enum": [
        "subnet",
        "same-node",
        "none"
      ]
    },
    "CommandReceipt": {
      "type": "string",
      "enum": [
        "http",
        "stdin",
        "relay"
      ]
    },
    "ShellApproval": {
      "description": "Per-shell instantiation-approval mode (`require_approval`). Reuses the consent\nplumbing: `remembered` lets allow-always write a persistent grant; `always`\nsuppresses allow-always (prompt every spawn).",
      "oneOf": [
        {
          "description": "No approval (default — matches the system's everything-opt-in posture).",
          "type": "string",
          "const": "none"
        },
        {
          "description": "Prompt; allow-always persists a grant, later spawns auto-allow.",
          "type": "string",
          "const": "remembered"
        },
        {
          "description": "Prompt on every spawn; allow-always suppressed (no persistent grant).",
          "type": "string",
          "const": "always"
        }
      ]
    },
    "OverCap": {
      "description": "What happens when an owner is at its `max_instances_per_owner` cap.",
      "oneOf": [
        {
          "description": "Refuse the spawn outright (default).",
          "type": "string",
          "const": "reject"
        },
        {
          "description": "Require per-spawn approval beyond the cap (does not raise the cap).",
          "type": "string",
          "const": "approve"
        }
      ]
    },
    "ShellCapability": {
      "description": "One entry in `[shell.capabilities]` — a command and its argument names.",
      "type": "object",
      "properties": {
        "args": {
          "type": "array",
          "items": {
            "type": "string"
          }
        }
      }
    },
    "Sensory": {
      "description": "`[shell.sensory]` — the sensory payload types a shell may emit.",
      "type": "object",
      "properties": {
        "types": {
          "type": "array",
          "items": {
            "type": "string"
          }
        }
      }
    },
    "PtyDigest": {
      "description": "`[pty_digest]` — the adapter-supplied patterns for the live activity buffer\n(ADR-0008). `input_pattern`/`agent_pattern` are the turn-boundary markers;\n`tool_patterns` is one regex per known tool and `catchall_pattern` an optional\nfinal catchall for user-installed tools (each naming `name`/`arg` capture\ngroups). spt-core compiles + runs these over the broker PTY stream — it never\nparses harness transcripts (the history-subsystem no-built-in-parser rule the\ndigest deliberately honors). The patterns are **opaque to spt-core**, exactly\nlike the `[session.*]` command templates.\n\n`window_turns` is the only presentation override exposed here; the remaining\npresentation knobs (arg truncation, ANSI stripping, scan-buffer cap) stay\nspt-core-owned defaults. `persist` opts this endpoint into the coarse Path-B\nactivity log (ADR-0008 \"Option (C)\") — off by default.",
      "type": "object",
      "properties": {
        "input_pattern": {
          "description": "User-input boundary marker (regex). Opens a span until the next match.",
          "type": "string"
        },
        "agent_pattern": {
          "description": "Agent-output boundary marker (regex). Opens a span until the next match.",
          "type": "string"
        },
        "tool_patterns": {
          "description": "One regex per known tool; each should name an `arg` (and may name a\n`name`) capture group.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "catchall_pattern": {
          "description": "Optional final catchall for unknowable user-installed tools.",
          "type": [
            "string",
            "null"
          ]
        },
        "window_turns": {
          "description": "Window depth override (last N user turns). `None` ⇒ spt-core default (~3).",
          "type": [
            "integer",
            "null"
          ],
          "format": "uint",
          "minimum": 0
        },
        "persist": {
          "description": "Opt-in Path-B persistence (ADR-0008 \"Option (C)\"): append completed turns\nto the perch's native history log as a coarse activity record.",
          "type": "boolean"
        }
      },
      "required": [
        "input_pattern",
        "agent_pattern"
      ]
    }
  },
  "$id": "https://sabermage.github.io/spt-releases/manifest.schema.json"
}
