{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "PersonaSpec",
  "description": "Canonical persona definition artifact. Validated, assembled, and registered by larva. Consumed by anima (runtime) and nervus (dispatch).",
  "type": "object",
  "required": ["id", "description", "prompt", "model", "tools", "spec_version"],
  "properties": {
    "id": {
      "type": "string",
      "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$",
      "description": "Stable canonical persona identity. Required by schema (missing id is invalid). Flat kebab-case, no namespaces in v1. Used by registry, resolution, promotion, and dispatch references."
    },
    "description": {
      "type": "string",
      "minLength": 1,
      "description": "Short operational summary of the persona's purpose and capability boundary. For humans and orchestrators, not a second prompt."
    },
    "prompt": {
      "type": "string",
      "minLength": 1,
      "description": "Fully composed executable prompt text. This is the final baked artifact, not a template. Assembly may exist upstream but canonical PersonaSpec stores the result."
    },
    "model": {
      "type": "string",
      "minLength": 1,
      "description": "Default model identifier, e.g. 'claude-sonnet-4-20250514'. Model selection is part of persona execution identity."
    },
    "tools": {
      "type": "object",
      "additionalProperties": {
        "type": "string",
        "enum": ["none", "read_only", "read_write", "destructive"]
      },
      "description": "Map of tool_family -> posture. Each key is a stable, provider-agnostic tool-surface identifier. Each value is the maximum intended posture for that tool family. Empty map is valid but triggers a warning."
    },
    "model_params": {
      "type": "object",
      "properties": {
        "temperature": { "type": "number", "minimum": 0, "maximum": 2 },
        "top_p": { "type": "number", "minimum": 0, "maximum": 1 },
        "max_tokens": {
          "type": "integer",
          "minimum": 1,
          "description": "Per-turn max output tokens."
        }
      },
      "additionalProperties": true,
      "description": "Provider-specific LLM inference parameters. additionalProperties allows provider-specific params (e.g. top_k, frequency_penalty)."
    },
    "side_effect_policy": {
      "type": "string",
      "enum": ["allow", "approval_required", "read_only"],
      "description": "Global default side-effect posture. 'allow' = no gate, 'approval_required' = human approval for mutations, 'read_only' = no mutations permitted. Runtime enforcement combines this with per-tool postures."
    },
    "can_spawn": {
      "oneOf": [
        { "type": "boolean" },
        {
          "type": "array",
          "items": { "type": "string", "minLength": 1 },
          "uniqueItems": true
        }
      ],
      "default": false,
      "description": "Spawn capability boundary. false/omitted = no spawn. true = spawn allowed, targets are a runtime concern. string[] = spawn restricted to listed canonical persona ids."
    },
    "compaction_prompt": {
      "type": "string",
      "description": "Persona-specific prompt for context compaction/summarization. Optional but changes execution behavior."
    },
    "spec_version": {
      "type": "string",
      "const": "0.1.0",
      "description": "Schema version envelope (semver). All consumers must reject unknown versions."
    },
    "spec_digest": {
      "type": "string",
      "description": "SHA-256 of canonical JSON (sorted keys, no whitespace, spec_digest excluded from input). Computed by larva; optional in raw input, always present in larva output."
    }
  },
  "additionalProperties": false
}
