{
  "$ref": "#/definitions/CotalMessage",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "definitions": {
    "CotalMessage": {
      "description": "A message on the mesh (chat / direct message for now; extensible to other families).",
      "oneOf": [
        {
          "properties": {
            "channel": {
              "description": "Channel name — multicast (broadcast to everyone on the channel).",
              "type": "string"
            },
            "contextId": {
              "description": "Conversation / thread correlation id.",
              "type": "string"
            },
            "from": {
              "$ref": "#/definitions/EndpointRef"
            },
            "id": {
              "description": "Unique message id.",
              "type": "string"
            },
            "mentions": {
              "description": "Lowercased peer names called out within a `channel` message — a wake hint that also, on a `live` channel, routes a durable copy to each mentioned target **authorized to read that channel** (SPEC §4/§5). It never carries content outside the target's read ACL and is not a routing substitute for `channel`/`to`; the message still multicasts to the whole channel. Omitted when empty.",
              "items": {
                "type": "string"
              },
              "type": "array"
            },
            "parts": {
              "items": {
                "$ref": "#/definitions/Part"
              },
              "type": "array"
            },
            "replyTo": {
              "description": "Id of the message being replied to.",
              "type": "string"
            },
            "space": {
              "type": "string"
            },
            "ts": {
              "description": "Epoch ms.",
              "type": "number"
            }
          },
          "required": [
            "channel",
            "from",
            "id",
            "parts",
            "space",
            "ts"
          ],
          "type": "object",
          "not": {
            "anyOf": [
              {
                "required": [
                  "to"
                ]
              },
              {
                "required": [
                  "toService"
                ]
              }
            ]
          }
        },
        {
          "properties": {
            "contextId": {
              "description": "Conversation / thread correlation id.",
              "type": "string"
            },
            "from": {
              "$ref": "#/definitions/EndpointRef"
            },
            "id": {
              "description": "Unique message id.",
              "type": "string"
            },
            "mentions": {
              "description": "Lowercased peer names called out within a `channel` message — a wake hint that also, on a `live` channel, routes a durable copy to each mentioned target **authorized to read that channel** (SPEC §4/§5). It never carries content outside the target's read ACL and is not a routing substitute for `channel`/`to`; the message still multicasts to the whole channel. Omitted when empty.",
              "items": {
                "type": "string"
              },
              "type": "array"
            },
            "parts": {
              "items": {
                "$ref": "#/definitions/Part"
              },
              "type": "array"
            },
            "replyTo": {
              "description": "Id of the message being replied to.",
              "type": "string"
            },
            "space": {
              "type": "string"
            },
            "to": {
              "description": "Instance id — unicast (direct to one specific endpoint).",
              "type": "string"
            },
            "ts": {
              "description": "Epoch ms.",
              "type": "number"
            }
          },
          "required": [
            "from",
            "id",
            "parts",
            "space",
            "to",
            "ts"
          ],
          "type": "object",
          "not": {
            "anyOf": [
              {
                "required": [
                  "channel"
                ]
              },
              {
                "required": [
                  "toService"
                ]
              }
            ]
          }
        },
        {
          "properties": {
            "contextId": {
              "description": "Conversation / thread correlation id.",
              "type": "string"
            },
            "from": {
              "$ref": "#/definitions/EndpointRef"
            },
            "id": {
              "description": "Unique message id.",
              "type": "string"
            },
            "mentions": {
              "description": "Lowercased peer names called out within a `channel` message — a wake hint that also, on a `live` channel, routes a durable copy to each mentioned target **authorized to read that channel** (SPEC §4/§5). It never carries content outside the target's read ACL and is not a routing substitute for `channel`/`to`; the message still multicasts to the whole channel. Omitted when empty.",
              "items": {
                "type": "string"
              },
              "type": "array"
            },
            "parts": {
              "items": {
                "$ref": "#/definitions/Part"
              },
              "type": "array"
            },
            "replyTo": {
              "description": "Id of the message being replied to.",
              "type": "string"
            },
            "space": {
              "type": "string"
            },
            "toService": {
              "description": "Service / role — anycast (any one instance of the service receives it).",
              "type": "string"
            },
            "ts": {
              "description": "Epoch ms.",
              "type": "number"
            }
          },
          "required": [
            "from",
            "id",
            "parts",
            "space",
            "toService",
            "ts"
          ],
          "type": "object",
          "not": {
            "anyOf": [
              {
                "required": [
                  "channel"
                ]
              },
              {
                "required": [
                  "to"
                ]
              }
            ]
          }
        }
      ]
    },
    "EndpointRef": {
      "properties": {
        "id": {
          "type": "string"
        },
        "name": {
          "type": "string"
        },
        "role": {
          "type": "string"
        }
      },
      "required": [
        "id",
        "name"
      ],
      "type": "object"
    },
    "ExtensionPartKind": {
      "description": "Reverse-DNS extension part kind, e.g. `com.acme.snapshot`.",
      "pattern": "^[A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)+$",
      "type": "string"
    },
    "JsonValue": {
      "anyOf": [
        {
          "type": "null"
        },
        {
          "type": "boolean"
        },
        {
          "type": "number"
        },
        {
          "type": "string"
        },
        {
          "items": {
            "$ref": "#/definitions/JsonValue"
          },
          "type": "array"
        },
        {
          "additionalProperties": {
            "anyOf": [
              {
                "$ref": "#/definitions/JsonValue"
              },
              {
                "not": {}
              }
            ]
          },
          "type": "object"
        }
      ],
      "description": "Any JSON value (SPEC §5): `null`, a boolean, a number, a string, an array, or an object whose members are JSON values. `undefined` is not a JSON value — `JSON.stringify` omits the key carrying it — so it is excluded at the top level, where it would drop the whole `data` key; an optional member is allowed because serialization drops just that key."
    },
    "Part": {
      "oneOf": [
        {
          "properties": {
            "kind": {
              "const": "text",
              "type": "string"
            },
            "text": {
              "type": "string"
            }
          },
          "required": [
            "kind",
            "text"
          ],
          "type": "object"
        },
        {
          "properties": {
            "data": {
              "$ref": "#/definitions/JsonValue"
            },
            "kind": {
              "const": "data",
              "type": "string"
            }
          },
          "required": [
            "kind",
            "data"
          ],
          "type": "object"
        },
        {
          "description": "Reference to bytes in the space's artifact store — SPEC §5's reserved slot, now defined. Bare (not reverse-DNS) because it is Cotal's own core primitive: extension kinds wrap EXTERNAL vocabularies, core kinds are the standard's own. The full contract, the guard, and the object-store digest boundary live in `artifact.ts`.",
          "properties": {
            "digest": {
              "description": "`sha256:<hex>` over the raw bytes — the artifact's identity. The only self-verifying field.",
              "type": "string",
              "pattern": "^sha256:[0-9a-f]{64}$"
            },
            "kind": {
              "const": "artifact",
              "type": "string"
            },
            "mediaType": {
              "description": "MIME type. A publisher's claim.",
              "type": "string"
            },
            "name": {
              "description": "Human name, e.g. `coverage-report.html`. A publisher's claim, not a checked fact.",
              "type": "string"
            },
            "size": {
              "description": "Size in bytes. A publisher's claim: a receiver must never preallocate from it.",
              "type": "integer",
              "minimum": 0
            }
          },
          "required": [
            "kind",
            "name",
            "mediaType",
            "digest",
            "size"
          ],
          "type": "object"
        },
        {
          "additionalProperties": {},
          "properties": {
            "kind": {
              "$ref": "#/definitions/ExtensionPartKind"
            }
          },
          "required": [
            "kind"
          ],
          "type": "object"
        }
      ]
    },
    "AgentCard": {
      "description": "A2A-inspired identity record for an endpoint or agent.",
      "properties": {
        "actor": {
          "description": "The agent-instance actor token under  {@link  owner }  (server-derived from the spawn ledger in user mode; the connection id in the dev default). The other half of  {@link  id } .",
          "type": "string"
        },
        "description": {
          "description": "A2A-style one-line summary of what this agent does (discovery / observability).",
          "type": "string"
        },
        "id": {
          "description": "Unique, stable for the lifetime of this connection. The owner+actor **principal dot-form** `<owner>.<actor>` (= `principalKey().key`) under the owner+actor grammar — the wire identity every `msg.from.id` carries and every sender guard compares against. Peers address each other by this.",
          "type": "string"
        },
        "kind": {
          "$ref": "#/definitions/EndpointKind",
          "description": "'agent' (participates in coordination) or a plain 'endpoint' (logger, dashboard…)."
        },
        "meta": {
          "additionalProperties": {},
          "description": "Free-form advisory display metadata. Reserved flat string keys are `connector`, `model`, `provider` (the effective provider the connector reported), `host`, `cwd`, `repo`, `branch`, `head`, `sessionKind`, and `sessionId`.",
          "type": "object"
        },
        "name": {
          "description": "Human-readable display name.",
          "type": "string"
        },
        "owner": {
          "description": "The human/account owner token this agent acts under (opaque, per-space; `\"local\"` in the no-login dev default). One of the two halves of  {@link  id } ; travels in presence so peers resolve name→principal.",
          "type": "string"
        },
        "protocolVersion": {
          "description": "Wire-contract version this participant speaks (the SPEC.md version, `\"0.2\"` today). A change signal, not negotiation: v0 has none, but a peer can detect a mismatch instead of silently misreading a future envelope. Omitted ⇒ assume the v0.x line.",
          "type": "string"
        },
        "role": {
          "description": "Cotal addition: the role this participant plays (planner, reviewer, …).",
          "type": "string"
        },
        "skills": {
          "items": {
            "$ref": "#/definitions/AgentSkill"
          },
          "type": "array"
        },
        "tags": {
          "description": "Cotal: free-form \"what it can do\" tags (A2A skill-tags, flattened) — discovery only.",
          "items": {
            "type": "string"
          },
          "type": "array"
        }
      },
      "required": [
        "id",
        "name",
        "kind"
      ],
      "type": "object"
    },
    "AgentSkill": {
      "properties": {
        "description": {
          "type": "string"
        },
        "id": {
          "type": "string"
        },
        "name": {
          "type": "string"
        }
      },
      "required": [
        "id",
        "name"
      ],
      "type": "object"
    },
    "AttentionMode": {
      "description": "How aggressively peer traffic interrupts an agent — chosen by the agent, orthogonal to  {@link  PresenceStatus } . Defined here (the wire layer) because it is now published in  {@link  Presence } ; the connector imports it. Advisory observability, not a security boundary.",
      "enum": [
        "open",
        "dnd",
        "focus"
      ],
      "type": "string"
    },
    "ChannelMode": {
      "description": "Per-channel attention override (more specific than the global  {@link  AttentionMode } ).\n- `quiet` — still delivered + buffered, but never wakes; an `@`-mention still wakes (per-channel `dnd`).\n- `muted` — channel messages dropped on receive, incl. `@`-mentions (\"don't receive this channel\").",
      "enum": [
        "quiet",
        "muted"
      ],
      "type": "string"
    },
    "EndpointKind": {
      "description": "Cotal wire types (v0.2).\n\nThese are the shapes that travel on the mesh. They are intentionally A2A-inspired (AgentCard / Message / Part) but transport-agnostic. This file IS part of the \"wire contract\" — treat changes here as protocol changes.",
      "enum": [
        "agent",
        "endpoint"
      ],
      "type": "string"
    },
    "Presence": {
      "description": "Live presence record. Stored in the space's KV bucket under key = card.id.",
      "properties": {
        "activeAt": {
          "description": "Epoch ms of the last work progress the harness reported (a turn event such as a token or a tool call). `ts` is only the heartbeat: a seat whose turn stopped advancing keeps heartbeating while this stays old. Missing means the connector reports none.",
          "type": "number"
        },
        "activity": {
          "description": "Freeform \"what I'm doing right now\".",
          "type": "string"
        },
        "activitySince": {
          "description": "Epoch ms when the current `activity` was set. A status change does not move it, so an activity left behind while hooks flip the status every turn still reads as old.",
          "type": "number"
        },
        "attention": {
          "$ref": "#/definitions/AttentionMode",
          "description": "This instance's current global attention mode. Advisory, within-space observability — a peer can see \"they're in focus\" and choose to DM. Published from the connector's authoritative state (presence is a mirror, never the source of truth for delivery). `open`/absent ⇒ receives all."
        },
        "card": {
          "$ref": "#/definitions/AgentCard"
        },
        "channelModes": {
          "additionalProperties": {
            "$ref": "#/definitions/ChannelMode"
          },
          "description": "Per-channel attention overrides this instance currently has (runtime, reset on restart). Keys are concrete channel names. Advisory: lets a peer see \"locally muted #deploys → DM to reach me\". NOT access control — the broker still authorizes and delivers; this is a receive-side presentation.",
          "type": "object"
        },
        "condition": {
          "$ref": "#/definitions/PresenceCondition",
          "description": "Structured condition reported by the harness. Missing means nothing was reported."
        },
        "environment": {
          "description": "Opaque reference whose meaning belongs to the provider that issued it.",
          "type": "string"
        },
        "lifecycleUid": {
          "description": "This incarnation's lifecycle UID (SPEC §6/§13.1: MUST in auth mode from v0.4) — the value the alias currently maps to, published so peers/observers can attribute the alias's live occupant. Advisory observability, never authority (authority is the ledger/broker grants).",
          "type": "string"
        },
        "runtime": {
          "$ref": "#/definitions/PresenceRuntime",
          "description": "The process that publishes this record, with its observed start. Status, activity, heartbeats and reconnects inside the process leave it unchanged; a new process publishes a new one. An offline record keeps it to name the process that went away, so it dates a running duration only while the record is live. Missing means the publisher reports none: unknown, not zero."
        },
        "status": {
          "$ref": "#/definitions/PresenceStatus"
        },
        "statusSince": {
          "description": "Epoch ms when the instance entered its current `status` and `activity`. A change to either moves it; a heartbeat or a repeated report does not, so an activity that outlived what it described reads as old even while `ts` stays fresh. Never carried by an offline record.",
          "type": "number"
        },
        "ts": {
          "description": "Epoch ms of the last heartbeat.",
          "type": "number"
        }
      },
      "required": [
        "card",
        "status",
        "ts"
      ],
      "type": "object"
    },
    "PresenceCondition": {
      "description": "A condition the harness reported. Connectors relay it and never infer one from absence.",
      "properties": {
        "code": {
          "$ref": "#/definitions/PresenceConditionCode"
        },
        "message": {
          "description": "Free-text detail reported by the harness.",
          "type": "string"
        },
        "since": {
          "description": "Epoch ms when this condition began.",
          "type": "number"
        },
        "source": {
          "description": "The harness's native value, preserved verbatim.",
          "type": "string"
        }
      },
      "required": [
        "code"
      ],
      "type": "object"
    },
    "PresenceConditionCode": {
      "description": "Closed, provider-neutral categories a connector may relay from its harness's native signal.",
      "enum": [
        "rate_limit",
        "overloaded",
        "auth",
        "billing",
        "budget",
        "context",
        "model",
        "request",
        "server",
        "retrying",
        "approval",
        "input",
        "failed"
      ],
      "type": "string"
    },
    "PresenceRuntime": {
      "description": "The running process behind a presence record, as that process observed itself. Advisory display data, never authority. It carries no pid, host, path, command line or environment.",
      "properties": {
        "id": {
          "description": "Opaque CSPRNG id minted once per OS process and lifecycleUid scope, never reused. A new process or lifecycle scope gets a new id; reconnect within that scope keeps it.",
          "type": "string"
        },
        "source": {
          "$ref": "#/definitions/PresenceRuntimeSource"
        },
        "startedAt": {
          "description": "Epoch ms when the process started, read in-process from its own clock origin (`performance.timeOrigin`). Never a launch request, registration, heartbeat, status or activity time. Every lifecycle in this process shares this start, including one registered later. It does not date a separate parent, model harness or lifecycle.",
          "type": "number"
        }
      },
      "required": [
        "id",
        "startedAt",
        "source"
      ],
      "type": "object"
    },
    "PresenceRuntimeSource": {
      "const": "endpoint-process",
      "description": "Where a  {@link  PresenceRuntime }  start was observed. Closed: a consumer that meets any other value treats the observation as unsupported rather than guessing what it dates.\n- `endpoint-process`: the OS process that holds this presence's mesh connection, observed by   that process itself.",
      "type": "string"
    },
    "PresenceStatus": {
      "description": "Lifecycle status of a participant.\n- `idle`: connected, no active task\n- `waiting`: blocked — awaiting input, approval, or a peer\n- `working`: actively executing a task / in a turn\n- `offline`: disconnected or heartbeat lapsed (derived by observers, not self-set while live)",
      "enum": [
        "idle",
        "waiting",
        "working",
        "offline"
      ],
      "type": "string"
    }
  }
}
