{
  "name": "AgentRun",
  "summary": "A single agent task from start to finish: what it is doing, what it produced, and a way to stop it at any point.",
  "useWhen": [
    "an agent performs work that takes long enough for the user to wonder whether it is still running",
    "the work happens in steps the user would want to see — a tool call, a search, a write",
    "the output arrives incrementally rather than all at once"
  ],
  "composes": [
    "AgentActivity",
    "AgentActivityStep",
    "AgentStop",
    "PromptInput",
    "ToolCall",
    "StreamingText",
    "Alert",
    "Badge",
    "ProgressRing"
  ],
  "drivenBy": {
    "hook": "useAgentRun",
    "note": "It turns the events a run reports into the props on AgentActivityStep (`status`), StreamingText (`outputProps`), AgentStop (`stopProps`) and ApprovalGate (`approvalProps`), and applies this pattern's states on the way: one active step, a stop keeps what was done and skips the rest, a failure marks the step that was running. It owns state, not markup or copy — which steps to show is `log`, and every sentence is yours. Feed it with `dispatch` from a live run or `replay(recording)` from a stored one; `agentRunFrom(events)` rebuilds a finished run without playing it.",
    "events": [
      "`run-started` — optionally with `steps: [{ id, label }]`, the plan, so steps the run never reaches can be named as not done",
      "`step-started` { id, label? } — closes whichever step was active; exactly one is active at a time",
      "`step-done` / `step-skipped` { id }",
      "`step-failed` { id, error? }",
      "`approval-requested` { id } — the step becomes the ApprovalGate; the run is `awaiting`",
      "`approval-resolved` { id, decision: approved | rejected | expired, resolution? } — `expired` ends the run without acting",
      "`output` { text } — appended; `output-done` ends streaming",
      "`run-finished`, `run-failed` { error? }, `run-stopped` { resolution? } — the three ends a producer sends; after any end, later events are ignored"
    ]
  },
  "structure": [
    "A header row carrying the task name and the AgentStop control. The stop is the first thing on screen and it stays there for the whole run.",
    "An AgentActivity list of AgentActivityStep, one per step, in the order they happened. Exactly one step is `active` at a time.",
    "The result below the log, in a StreamingText with `isStreaming` true until the run ends.",
    "A ToolCall under any step that called a tool, collapsed, with the same `status` as its step. The step is the account; the ToolCall is the evidence for it.",
    "When the run was started from a PromptInput, that composer's `isRunning` stop IS the run's AgentStop. Render one stop control, not two.",
    "An Alert only when the run fails or is stopped — not for ordinary progress, which the step log already carries.",
    "While the run is going, a Small ProgressRing in the header's status: the steps done out of the steps planned, with `valueText` saying so — \"3 of 5 steps done\". The log carries where the run is; the ring says how far, in a glance, above a log that may be off screen. When the run ends a Badge takes its place, saying how it ended."
  ],
  "states": {
    "loading": {
      "must": "Show the step log from the first step, not after the first result. Set `isStreaming` on the StreamingText and give it `minLines` so the region does not grow under the reader. AgentStop stays enabled the entire time.",
      "why": "a spinner with no steps and no stop is indistinguishable from a hang, and the user's only remaining move is to reload the page and lose the run"
    },
    "empty": {
      "must": "Before the first step exists, render the header and the AgentStop and nothing else. Do not render an empty AgentActivity list.",
      "why": "an empty `<ol>` announces as a list of zero items, which tells a screen-reader user the run produced nothing rather than that it has not started"
    },
    "error": {
      "must": "Mark the step that failed `status=\"failed\"`, leave every earlier step visible, and put an Alert with `intent=\"error\"` below the log naming which step failed and what it was doing. Clear `isStreaming`.",
      "why": "replacing the log with an error message destroys the only record of how far the run got, which is the thing a user needs to decide whether to retry or start over"
    },
    "partial": {
      "must": "When the user stops a run mid-flight, keep every completed step, mark the interrupted one `status=\"skipped\"`, and say plainly what was and was not done. Leave whatever the run already wrote on screen.",
      "why": "a stopped agent has usually already changed something, and a UI that clears itself on stop hides the side effects the user stopped it to prevent"
    }
  },
  "propsUsed": {
    "AgentActivity": [
      "announceActive"
    ],
    "AgentActivityStep": [
      "status",
      "detail"
    ],
    "AgentStop": [
      "onStop",
      "isStopping",
      "label",
      "size",
      "stopOnEscape"
    ],
    "PromptInput": [
      "label",
      "onSubmit",
      "isRunning",
      "onStop",
      "isStopping"
    ],
    "ToolCall": [
      "title",
      "name",
      "status",
      "input",
      "output",
      "errorMessage"
    ],
    "StreamingText": [
      "isStreaming",
      "minLines",
      "label",
      "hideCursor"
    ],
    "Alert": [
      "intent",
      "title"
    ],
    "Badge": [
      "intent",
      "size"
    ],
    "ProgressRing": [
      "label",
      "value",
      "max",
      "size",
      "valueText",
      "isValueVisible"
    ]
  },
  "variantsUsed": {
    "AgentActivityStep": {
      "status": [
        "pending",
        "active",
        "done",
        "failed",
        "skipped"
      ]
    },
    "AgentStop": {
      "size": [
        "sm",
        "md"
      ]
    },
    "ToolCall": {
      "status": [
        "active",
        "done",
        "failed",
        "skipped"
      ]
    },
    "Alert": {
      "intent": [
        "error",
        "warning"
      ]
    },
    "Badge": {
      "intent": [
        "neutral",
        "success",
        "error"
      ]
    }
  },
  "a11y": {
    "requires": [
      "`announceActive` on AgentActivity when the run is unattended — it announces only the step that becomes active, once",
      "an AgentStop that is reachable by keyboard without passing through the step log",
      "a `label` on StreamingText when the output is not preceded by a visible heading"
    ],
    "notes": [
      "StreamingText is deliberately NOT a live region. `aria-live=\"polite\"` on streaming output queues an announcement per token, so the answer is re-read and stuttered dozens of times. The step log is where progress is announced; the output is ordinary readable text.",
      "Do not also wrap the step log in your own live region. AgentActivity owns one, and two announcing regions produce interleaved speech.",
      "`stopOnEscape` is off by default on purpose. Turn it on only when the run owns the whole screen — inside a dialog, Escape must close the dialog."
    ]
  },
  "antiPatterns": [
    {
      "dont": "a ProgressRing counting the steps started, or a percentage with no count",
      "do": "`value={done}`, `max={planned}`, `valueText=\"3 of 5 steps done\"`",
      "why": "a step still running is not done, and \"60%\" of a run is a number nobody planned; the count is what the log shows"
    },
    {
      "dont": "a stop control that appears only after the run has been going for a few seconds",
      "why": "the moment a user most wants to stop is the moment they realise the agent misunderstood, which is usually within the first second"
    },
    {
      "dont": "removing the AgentStop while the stop is in progress",
      "do": "AgentStop",
      "why": "the control vanishing is read as the click having failed; `isStopping` keeps it in place and says what is happening"
    },
    {
      "dont": "collapsing finished steps to a single \"Done\" line",
      "why": "the log is the audit trail — it is what the user reads when the result is wrong, and it is worthless after the fact"
    },
    {
      "dont": "raw tool names or JSON in the step text",
      "why": "\"Calling search_documents({q: ...})\" describes the implementation; \"Searching your documents\" describes the work, and only one of them is useful to the person deciding whether to stop"
    },
    {
      "dont": "more than one step marked `active`",
      "why": "the status is what tells a screen-reader user where the run is, and two active steps make that unanswerable"
    }
  ]
}
