{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://charliek.github.io/craze/reference/protocol/schema/event.json",
  "title": "event",
  "description": "One event in the lossless event codec (internal/agent/eventcodec.go, codec version 1): an event notification's event, verbatim, and the shape every leaf payload of the protocol has. Every key but type is omitted at its zero value, so absent means zero; a pointer's presence is kept (an object that is present and empty is not an absent one); times are RFC 3339 with nanoseconds, in UTC. A client ignores keys and event types it does not know.",
  "type": "object",
  "properties": {
    "type": {
      "description": "The event's kind. A client ignores a kind it does not know.",
      "type": "string",
      "enum": ["text", "thought", "tool", "todos", "permission", "question", "plan", "done", "error", "meta", "user", "subagent", "queue", "command", "foreign_turn", "replay", "turn", "ask", "compaction"]
    },
    "text": {"type": "string", "description": "A text, thought or user chunk; a title (meta)."},
    "mode": {"type": "string", "description": "The mode an agent-initiated meta reports."},
    "agent": {"type": "string", "description": "The sub-agent the event belongs to; absent for the main transcript."},
    "tool": {"$ref": "#/$defs/tool"},
    "todos": {"type": "array", "items": {"$ref": "#/$defs/todo"}},
    "permission": {"$ref": "#/$defs/permission"},
    "question": {"$ref": "#/$defs/question"},
    "plan": {"$ref": "#/$defs/plan"},
    "subagent": {"$ref": "#/$defs/subagent"},
    "subagentChange": {"type": "string", "description": "spawned, progress or finished."},
    "queue": {"$ref": "#/$defs/queued"},
    "queueChange": {"type": "string", "description": "queued, edited, removed or sent."},
    "queuePos": {"type": "integer", "minimum": 0},
    "command": {"$ref": "#/$defs/command"},
    "interjection": {"type": "boolean"},
    "foreignTurn": {"$ref": "#/$defs/foreignTurn"},
    "turn": {"$ref": "#/$defs/turn"},
    "ask": {"$ref": "#/$defs/ask"},
    "state": {"$ref": "#/$defs/state"},
    "replay": {"$ref": "#/$defs/replay"},
    "replayed": {"type": "boolean", "description": "The event came from a session/load replay."},
    "compaction": {"$ref": "#/$defs/compaction"},
    "cause": {"type": "string", "description": "The command that caused it, \"clientId/commandId\"."},
    "err": {"$ref": "#/$defs/error"},
    "stopReason": {"type": "string"},
    "at": {"$ref": "#/$defs/time"}
  },
  "required": ["type"],
  "additionalProperties": false,
  "$defs": {
    "time": {
      "description": "A time: RFC 3339 with nanoseconds, in UTC.",
      "type": "string",
      "format": "date-time"
    },
    "tool": {
      "description": "A tool call's state (ToolEvent).",
      "type": "object",
      "properties": {
        "id": {"type": "string"},
        "name": {"type": "string"},
        "status": {"type": "string"},
        "kind": {"type": "string"},
        "title": {"type": "string"},
        "toolName": {"type": "string"},
        "rawInput": {"type": "string"},
        "contentText": {"type": "string"},
        "locations": {"type": "array", "items": {"type": "string"}},
        "output": {"$ref": "#/$defs/toolOutput"},
        "diffs": {"type": "array", "items": {"$ref": "#/$defs/diff"}},
        "task": {"$ref": "#/$defs/task"},
        "at": {"$ref": "#/$defs/time"}
      },
      "additionalProperties": false
    },
    "toolOutput": {
      "description": "A tool's output. exitCode is present, 0 included, whenever the tool reported one.",
      "type": "object",
      "properties": {
        "exitCode": {"type": "integer"},
        "stdout": {"type": "string"},
        "stderr": {"type": "string"},
        "content": {"type": "string"},
        "stdoutHead": {"type": "string"},
        "stderrHead": {"type": "string"},
        "truncated": {"type": "boolean"}
      },
      "additionalProperties": false
    },
    "diff": {
      "description": "One file a tool changed.",
      "type": "object",
      "properties": {
        "path": {"type": "string"},
        "oldText": {"type": "string"},
        "newText": {"type": "string"},
        "added": {"type": "integer"},
        "removed": {"type": "integer"},
        "truncated": {"type": "boolean"}
      },
      "additionalProperties": false
    },
    "task": {
      "description": "A task tool's sub-agent details; its presence, even empty, marks the tool a task.",
      "type": "object",
      "properties": {
        "description": {"type": "string"},
        "prompt": {"type": "string"},
        "model": {"type": "string"},
        "agentId": {"type": "string"},
        "subagentType": {"type": "string"},
        "durationMs": {"type": "integer"},
        "receipt": {"type": "boolean"},
        "status": {"type": "string"},
        "background": {"type": "boolean"}
      },
      "additionalProperties": false
    },
    "todo": {
      "description": "One todo: status is pending, in_progress, completed or cancelled.",
      "type": "object",
      "properties": {
        "id": {"type": "string"},
        "content": {"type": "string"},
        "status": {"type": "string"}
      },
      "additionalProperties": false
    },
    "permission": {
      "description": "A permission request: every offered option verbatim, duplicate kinds included. Answer with the exact optionId.",
      "type": "object",
      "properties": {
        "id": {"type": "string"},
        "tool": {"type": "string"},
        "options": {"type": "array", "items": {"$ref": "#/$defs/permissionOption"}}
      },
      "additionalProperties": false
    },
    "permissionOption": {
      "description": "One offered option: kind is ACP's (allow_once, allow_always, reject_once, reject_always).",
      "type": "object",
      "properties": {
        "optionId": {"type": "string"},
        "name": {"type": "string"},
        "kind": {"type": "string"}
      },
      "additionalProperties": false
    },
    "question": {
      "description": "A question request (QuestionEvent).",
      "type": "object",
      "properties": {
        "id": {"type": "string"},
        "title": {"type": "string"},
        "questions": {"type": "array", "items": {"$ref": "#/$defs/questionItem"}},
        "auto": {"type": "boolean"},
        "answers": {"$ref": "#/$defs/answers"}
      },
      "additionalProperties": false
    },
    "questionItem": {
      "description": "One question of a question request.",
      "type": "object",
      "properties": {
        "id": {"type": "string"},
        "prompt": {"type": "string"},
        "options": {"type": "array", "items": {"$ref": "#/$defs/option"}},
        "allowMultiple": {"type": "boolean"}
      },
      "additionalProperties": false
    },
    "option": {
      "description": "One option of a question.",
      "type": "object",
      "properties": {
        "id": {"type": "string"},
        "label": {"type": "string"},
        "description": {"type": "string"}
      },
      "additionalProperties": false
    },
    "answers": {
      "description": "Each question's id to the option ids chosen. A null list is not an empty one: null is written for a nil list, [] for an empty one.",
      "type": "object",
      "additionalProperties": {"type": ["array", "null"], "items": {"type": "string"}}
    },
    "plan": {
      "description": "A plan request (PlanEvent).",
      "type": "object",
      "properties": {
        "id": {"type": "string"},
        "name": {"type": "string"},
        "overview": {"type": "string"},
        "plan": {"type": "string"},
        "todos": {"type": "array", "items": {"$ref": "#/$defs/todo"}},
        "auto": {"type": "boolean"},
        "accepted": {"type": "boolean"}
      },
      "additionalProperties": false
    },
    "subagent": {
      "description": "One sub-agent roster row (SubagentInfo): status is running, completed, failed or cancelled.",
      "type": "object",
      "properties": {
        "id": {"type": "string"},
        "attemptId": {"type": "string"},
        "parentId": {"type": "string"},
        "toolCallId": {"type": "string"},
        "description": {"type": "string"},
        "subagentType": {"type": "string"},
        "model": {"type": "string"},
        "status": {"type": "string"},
        "error": {"type": "string"},
        "prompt": {"type": "string"},
        "output": {"type": "string"},
        "activity": {"type": "string"},
        "startedAt": {"$ref": "#/$defs/time"},
        "endedAt": {"$ref": "#/$defs/time"},
        "durationMs": {"type": "integer"},
        "toolCalls": {"type": "integer"},
        "turns": {"type": "integer"},
        "tokensUsed": {"type": "integer"},
        "toolsUsed": {"type": "array", "items": {"type": "string"}},
        "transcript": {"type": "boolean"},
        "background": {"type": "boolean"}
      },
      "additionalProperties": false
    },
    "queued": {
      "description": "One queued prompt (QueuedPrompt): a queue event's row, and every queue row the protocol carries.",
      "type": "object",
      "properties": {
        "id": {"type": "string"},
        "text": {"type": "string"},
        "queuedAt": {"$ref": "#/$defs/time"},
        "version": {"type": "integer", "minimum": 0}
      },
      "additionalProperties": false
    },
    "command": {
      "description": "A plugin command or skill craze expanded into the prompt it is about to send.",
      "type": "object",
      "properties": {
        "pluginCommand": {"$ref": "#/$defs/pluginCommand"},
        "path": {"type": "string"},
        "text": {"type": "string"}
      },
      "additionalProperties": false
    },
    "pluginCommand": {
      "description": "A plugin command or skill: kind is command, skill or agent.",
      "type": "object",
      "properties": {
        "plugin": {"type": "string"},
        "bare": {"type": "string"},
        "display": {"type": "string"},
        "qualified": {"type": "string"},
        "description": {"type": "string"},
        "kind": {"type": "string"}
      },
      "additionalProperties": false
    },
    "foreignTurn": {
      "description": "One end of a turn the agent ran on its own: running on its start, absent on its end. reason is on both ends when the agent gave one.",
      "type": "object",
      "properties": {
        "id": {"type": "string"},
        "text": {"type": "string"},
        "reason": {
          "type": "string",
          "description": "Why the agent started the turn: an open string, not an enum. Absent (\"\") is grok's interjection fallback; subagent_wake is the native session delivering a background sub-agent's result. A client reads a value it does not know as absent."
        },
        "running": {"type": "boolean"}
      },
      "additionalProperties": false
    },
    "turn": {
      "description": "One end of an engine-driven turn (TurnInfo): phase started (with text and origin: submit, drain or send_now) or ended (with its stop reason, and errClass/err for a failure; synthetic for an ending the wire never produced; next, the successor turn this settlement started; pending, the rows still queued).",
      "type": "object",
      "properties": {
        "id": {"type": "string"},
        "phase": {"type": "string"},
        "text": {"type": "string"},
        "origin": {"type": "string"},
        "stopReason": {"type": "string"},
        "errClass": {"type": "string"},
        "err": {"type": "string"},
        "synthetic": {"type": "boolean"},
        "next": {"type": "string"},
        "pending": {"type": "integer", "minimum": 0}
      },
      "additionalProperties": false
    },
    "ask": {
      "description": "One ask's ending (AskUpdate): kind is permission, question or plan; outcome answered, cancelled, turn_ended, closing or automatic; by whose doing (client, call, cancel, turn, close, policy, provider, unavailable). body is set only when no opening was ever published.",
      "type": "object",
      "properties": {
        "id": {"type": "string"},
        "kind": {"type": "string"},
        "outcome": {"type": "string"},
        "by": {"type": "string"},
        "optionId": {"type": "string"},
        "answers": {"$ref": "#/$defs/answers"},
        "skip": {"type": "boolean"},
        "accepted": {"type": "boolean"},
        "label": {"type": "string"},
        "body": {"$ref": "#/$defs/askBody"}
      },
      "additionalProperties": false
    },
    "askBody": {
      "description": "An ask's opening exactly as offered: one of the three, in the shape its own opening event has.",
      "type": "object",
      "properties": {
        "permission": {"$ref": "#/$defs/permission"},
        "question": {"$ref": "#/$defs/question"},
        "plan": {"$ref": "#/$defs/plan"}
      },
      "additionalProperties": false
    },
    "state": {
      "description": "A state delta (StateDelta): the sections of the shared state one event changed, each in full. A section that is absent was not touched; one that is present was set, to \"\" or {} included. The delta's seq is the change's revision.",
      "type": "object",
      "properties": {
        "title": {"type": "string"},
        "mode": {"type": "string"},
        "model": {"type": "string"},
        "config": {"$ref": "#/$defs/config"},
        "commands": {"$ref": "#/$defs/commands"},
        "plugins": {"$ref": "#/$defs/plugins"},
        "slash": {"$ref": "#/$defs/slash"},
        "sendNow": {"$ref": "#/$defs/sendNow"},
        "usage": {"$ref": "#/$defs/usage"},
        "catalog": {"$ref": "#/$defs/catalog"},
        "reason": {"type": "string"},
        "detail": {"type": "string"},
        "indexErr": {"type": "string"}
      },
      "additionalProperties": false
    },
    "usage": {
      "description": "The usage section (UsageState; native only, absent for every ACP session and never null or {}): the estimated size in tokens of what the session's next request sends (contextTokens) and the context window of the model it goes to (contextWindow, 0 when unknown), and what the session has spent — in the turn the report followed (after a load, the last turn the stored session recorded) and in all (session). Always every key.",
      "type": "object",
      "properties": {
        "contextTokens": {"type": "integer"},
        "contextWindow": {"type": "integer"},
        "turn": {"$ref": "#/$defs/spend"},
        "session": {"$ref": "#/$defs/spend"}
      },
      "required": ["contextTokens", "contextWindow", "turn", "session"],
      "additionalProperties": false
    },
    "catalog": {
      "description": "The catalog section (CatalogState; native only, absent for every ACP session, for a native session until it first reloads its model table, and never null or {}): every model the session offers now, in the picker's order, and the revision of that list, always written. The revision only ever grows within one incarnation, and a client never applies a revision lower than the one it holds: an older list never replaces a newer one, whatever order a reconnect or a replay brings them in. The session holds the list to these bounds where it builds it, the info document's catalogs.models with it: at most 512 models, in the picker's order with the running model among them; each name at most 128 bytes, cut with an ellipsis; a model whose id would be over 256 bytes is not offered, an id never cut; a model's effort facts at most 8 levels of at most 32 bytes each, or none.",
      "type": "object",
      "properties": {
        "models": {"type": "array", "maxItems": 512, "items": {"$ref": "#/$defs/catalogModel"}},
        "revision": {"type": "integer", "minimum": 0}
      },
      "required": ["revision"],
      "additionalProperties": false
    },
    "catalogModel": {
      "description": "One model the session offers: the info document's catalog model (info.json's model), id and name always written, recent its rank in the native provider's model memory and absent for a model not remembered, and its effort facts where the session capability effortFacts is true — efforts, the levels it offers in order, absent for a model with no effort control; defaultEffort; and switchEffort, the effort a switch to it lands on — each absent otherwise.",
      "type": "object",
      "properties": {
        "id": {"type": "string", "maxLength": 256},
        "name": {"type": "string", "maxLength": 128},
        "recent": {"type": "integer", "minimum": 1},
        "efforts": {"type": "array", "maxItems": 8, "items": {"type": "string", "minLength": 1, "maxLength": 32}},
        "defaultEffort": {"type": "string", "minLength": 1, "maxLength": 32},
        "switchEffort": {"type": "string", "minLength": 1, "maxLength": 32}
      },
      "required": ["id", "name"],
      "additionalProperties": false
    },
    "spend": {
      "description": "A sum of usage and its cost (Spend): the tokens as the provider billed them — input is the uncached input, reasoning is part of output — and costPicoUsd, the priced part's cost in picodollars (10^-12 USD; $1 is 1000000000000), exact. unpriced says some usage in it had no price in craze's model table: its tokens are counted and it added no cost. Always every key.",
      "type": "object",
      "properties": {
        "input": {"type": "integer"},
        "output": {"type": "integer"},
        "reasoning": {"type": "integer"},
        "cacheRead": {"type": "integer"},
        "cacheCreation": {"type": "integer"},
        "costPicoUsd": {"type": "integer"},
        "unpriced": {"type": "boolean"}
      },
      "required": ["input", "output", "reasoning", "cacheRead", "cacheCreation", "costPicoUsd", "unpriced"],
      "additionalProperties": false
    },
    "config": {
      "description": "The provider's config options, in full; {} for none.",
      "type": "object",
      "properties": {
        "options": {"type": "array", "items": {"$ref": "#/$defs/configOption"}}
      },
      "additionalProperties": false
    },
    "commands": {
      "description": "The agent's slash commands, in full; {} for none.",
      "type": "object",
      "properties": {
        "commands": {"type": "array", "items": {"$ref": "#/$defs/commandInfo"}}
      },
      "additionalProperties": false
    },
    "plugins": {
      "description": "The plugin commands and skills craze found, in full; {} for none.",
      "type": "object",
      "properties": {
        "plugins": {"type": "array", "items": {"$ref": "#/$defs/pluginCommand"}}
      },
      "additionalProperties": false
    },
    "slash": {
      "description": "The slash catalog section (SlashCatalog; a session whose capabilities.slashCatalog is true): every / row the session offers, in the order a menu lists them, in full; {} for none. A delta that carries commands or plugins restates it beside them, and one that carries it alone is a change of the disk skills the host scanned. Absent from every delta of a session that does not publish it, and never null. Nothing in it is cut: a row's action fields (every key but description, run.argHint and run.reason) are what a client acts on. A decoder reads it row by row: a row that does not match slashItem, or whose name is not one token, is dropped alone, and a slash or items value that is not an object or an array is a section with no rows — never a reason to reject the section or the event. craze writes neither.",
      "type": "object",
      "properties": {
        "items": {"type": "array", "items": {"$ref": "#/$defs/slashItem"}}
      },
      "additionalProperties": false
    },
    "slashItem": {
      "description": "One slash row: name is the token without its /, always written, never empty and never holding whitespace or a control character (a row whose name is not one token is dropped by a decoder). source is builtin, agent, plugin or skill; plugin and qualified are a plugin row's, qualified only where it differs from name (the second spelling the row answers to); requires is what the row needs: the session capability stop or aside, or effort, the client's own reading of the snapshot (an effort option for the current model); a client shows the row only where that is true — a requires row claims no name, so a client keeps, per name, the first row it shows; whileWorking says the row runs while a turn is running rather than being refused (absent false; a prompt row omits it, it queues). The values of source, requires and every run key are not closed here: a client hides a row with a value it does not know, and a later host may add one.",
      "type": "object",
      "properties": {
        "name": {"type": "string", "minLength": 1},
        "description": {"type": "string"},
        "source": {"type": "string"},
        "plugin": {"type": "string"},
        "qualified": {"type": "string"},
        "requires": {"type": "string"},
        "whileWorking": {"type": "boolean"},
        "run": {"$ref": "#/$defs/slashRun"}
      },
      "required": ["name"],
      "additionalProperties": false
    },
    "slashRun": {
      "description": "What running a slash row does, by kind: prompt (send /name and its argument as a session.prompt's text), method (call method — session.setTitle, session.set or session.stop — the argument filling the param it names, session.set sending setting verbatim), picker (open the picker it names: model), aside (ask a side question, session.aside, the argument filling param), local (the client's own action it names: help, clear, tasks, theme, connect) or unavailable (it cannot run here, and selecting it reports reason). arg is absent for none, optional or required; argHint is how to show it.",
      "type": "object",
      "properties": {
        "kind": {"type": "string"},
        "method": {"type": "string"},
        "param": {"type": "string"},
        "setting": {"$ref": "#/$defs/slashSetting"},
        "arg": {"type": "string"},
        "argHint": {"type": "string"},
        "picker": {"type": "string"},
        "local": {"type": "string"},
        "reason": {"type": "string"}
      },
      "required": ["kind"],
      "additionalProperties": false
    },
    "slashSetting": {
      "description": "session.set's setting object as a method row sends it, verbatim: its kind (mode) and value, a mode already resolved to an id the session advertised.",
      "type": "object",
      "properties": {
        "kind": {"type": "string"},
        "value": {"type": "string"}
      },
      "additionalProperties": false
    },
    "configOption": {
      "description": "One config option and its current value.",
      "type": "object",
      "properties": {
        "id": {"type": "string"},
        "name": {"type": "string"},
        "category": {"type": "string"},
        "type": {"type": "string"},
        "current": {"type": "string"},
        "selectValues": {"type": "array", "items": {"$ref": "#/$defs/selectValue"}}
      },
      "additionalProperties": false
    },
    "selectValue": {
      "description": "One value a select option offers.",
      "type": "object",
      "properties": {
        "value": {"type": "string"},
        "name": {"type": "string"}
      },
      "additionalProperties": false
    },
    "commandInfo": {
      "description": "One slash command the agent advertises.",
      "type": "object",
      "properties": {
        "name": {"type": "string"},
        "description": {"type": "string"}
      },
      "additionalProperties": false
    },
    "sendNow": {
      "description": "The send-now section: armed, its text, the row it re-takes and the turn it replaces; {} when nothing is armed.",
      "type": "object",
      "properties": {
        "armed": {"type": "boolean"},
        "text": {"type": "string"},
        "fromRow": {"type": "string"},
        "turn": {"type": "string"}
      },
      "additionalProperties": false
    },
    "replay": {
      "description": "One end of a session/load replay: phase start or end.",
      "type": "object",
      "properties": {
        "phase": {"type": "string"}
      },
      "additionalProperties": false
    },
    "compaction": {
      "description": "One end of a compaction of a session's context (CompactionInfo; native only): phase started, then ended. reason is auto, manual or overflow, an open string. An ended one carries tokensBefore, the context's estimated size before it, and either tokensAfter, its estimated size after, or err, why no summary was written. A replay carries only the ended one of each stored compaction. The event's agent names the sub-agent whose context it was.",
      "type": "object",
      "properties": {
        "phase": {"type": "string"},
        "reason": {"type": "string"},
        "tokensBefore": {"type": "integer", "minimum": 0},
        "tokensAfter": {"type": "integer", "minimum": 0},
        "err": {"type": "string"}
      },
      "additionalProperties": false
    },
    "error": {
      "description": "An event's error: its message, its class (rpc, closed, agent_exited, agent_exit_status, canceled, deadline_exceeded, auth, model_not_found, context_too_large, empty_step, empty_prompt, harness_closed, provider, other, prompt_cancelled, prompt_in_flight, foreign_turn; a class a later craze wrote is kept as it is) and its code (the JSON-RPC code, HTTP status or exit status, else 0). All three are always present.",
      "type": "object",
      "properties": {
        "message": {"type": "string"},
        "class": {"type": "string"},
        "code": {"type": "integer"}
      },
      "required": ["message", "class", "code"],
      "additionalProperties": false
    }
  }
}
