{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/cotal-ai/json-schema/cotal-ai-roster-entry-schema.json",
  "title": "RosterEntry",
  "description": "Presence plus an optional progress signal. Working rows currently report progress.kind as unknown because the dashboard has no session-store observer.",
  "x-generated": "2026-11-04",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/cotal-ai-dashboard-openapi.yml#/components/schemas/RosterEntry",
  "allOf": [
    {
      "$ref": "#/$defs/WirePresence"
    },
    {
      "type": "object",
      "properties": {
        "progress": {
          "type": "object",
          "properties": {
            "kind": {
              "const": "unknown",
              "type": "string"
            }
          },
          "required": [
            "kind"
          ]
        }
      }
    }
  ],
  "$defs": {
    "WireAgentCard": {
      "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": "#/$defs/WireEndpointKind",
          "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": "#/$defs/WireAgentSkill"
          },
          "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"
    },
    "WireAgentSkill": {
      "properties": {
        "description": {
          "type": "string"
        },
        "id": {
          "type": "string"
        },
        "name": {
          "type": "string"
        }
      },
      "required": [
        "id",
        "name"
      ],
      "type": "object"
    },
    "WireAttentionMode": {
      "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"
    },
    "WireChannelMode": {
      "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"
    },
    "WireEndpointKind": {
      "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"
    },
    "WirePresence": {
      "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": "#/$defs/WireAttentionMode",
          "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": "#/$defs/WireAgentCard"
        },
        "channelModes": {
          "additionalProperties": {
            "$ref": "#/$defs/WireChannelMode"
          },
          "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": "#/$defs/WirePresenceCondition",
          "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": "#/$defs/WirePresenceRuntime",
          "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": "#/$defs/WirePresenceStatus"
        },
        "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"
    },
    "WirePresenceCondition": {
      "description": "A condition the harness reported. Connectors relay it and never infer one from absence.",
      "properties": {
        "code": {
          "$ref": "#/$defs/WirePresenceConditionCode"
        },
        "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"
    },
    "WirePresenceConditionCode": {
      "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"
    },
    "WirePresenceRuntime": {
      "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": "#/$defs/WirePresenceRuntimeSource"
        },
        "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"
    },
    "WirePresenceRuntimeSource": {
      "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"
    },
    "WirePresenceStatus": {
      "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"
    }
  }
}