> ## Documentation Index
> Fetch the complete documentation index at: https://docs.promptjuggler.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Transcript

> A run as a renderable sequence: assistant text, the tools it used, and structured payloads in position.

Every prompt run returns its answer twice, in two shapes for two jobs.

`output` is the flat text — the right answer for a pipeline, a webhook, a curl call, or
anything that just wants the result. It is always present and it is not going anywhere.

`transcript` is the same run as an ordered sequence you can render: blocks of assistant
prose, the tools the model used, and [emit](/tools/emit) payloads where the model produced
them. It is what a chat UI needs and what a flat string cannot express.

## Why a flat string isn't enough

Two things get lost when a run collapses to one string.

**Text runs together at tool boundaries.** Providers split one logical block of prose into
several messages — Anthropic does it around citations, sometimes mid-word — so `output`
joins them with nothing at all. That is correct, and it is also why a model that says
"I'll look that up.", calls a tool, then says "Here's what I found…" comes back as
`I'll look that up.Here's what I found…`. A tool call is a real boundary and the string
has no way to say so.

**Tool activity is invisible.** A chat UI wants a bubble, then a "used web\_search" chip,
then another bubble. Nothing in a string carries that.

The transcript fixes both by construction: adjacent prose is already merged, and a tool
sits between the blocks either side of it.

## The shape

```json theme={null}
{
  "output": "I'll look that up.Here's what I found: 2850 km.",
  "transcript": [
    { "type": "text", "content": "I'll look that up.", "citations": [] },
    {
      "type": "tool",
      "name": "lookup",
      "status": "ok",
      "queries": [],
      "citations": []
    },
    {
      "type": "text",
      "content": "Here's what I found: 2850 km.",
      "citations": []
    }
  ]
}
```

Three kinds of item, discriminated by `type`:

| Kind   | Carries                                                                               |
| ------ | ------------------------------------------------------------------------------------- |
| `text` | A block of assistant prose, plus any sources it cited                                 |
| `tool` | The tool's name and how the call ended — deliberately not its arguments or its result |
| `data` | An [emit tool](/tools/emit) payload, in the position the model produced it            |

A tool item's `status` is `ok`, `error`, or `pending`. **Pending** is a real state on a
finished fetch: an async tool — a [prompt or workflow call](/tools/prompt-and-workflow), a
[knowledge search](/tools/knowledge-search) — can still be running when you read the run.

`queries` and `citations` on a tool item are only ever populated by the built-in
[web search](/tools/web-search); every other tool leaves them empty.

<Note>
  Tool items carry no arguments, no results and no call ids, on purpose. Results can be
  enormous, arguments can carry user data, and a chip needs neither.
</Note>

A failed run has an empty `transcript`, exactly as it has a null `output` and an empty
`emitted`.

## Rendering it

```tsx theme={null}
{run.transcript.map((item, i) => {
  switch (item.type) {
    case 'text':
      return <Markdown key={i}>{item.content}</Markdown>;
    case 'tool':
      return <ToolChip key={i} name={item.name} status={item.status} />;
    case 'data':
      return <RecordCard key={i} {...item.payload} />;
  }
})}
```

## Live and fetched agree

The same shape arrives on the [token stream](/concepts/streaming) as a run is generated, so
one component renders it mid-flight and after a refetch. Tools PromptJuggler runs for you —
[HTTP](/tools/http-call), [script](/tools/script),
[knowledge search](/tools/knowledge-search), [prompt and workflow](/tools/prompt-and-workflow)
calls — stream as chips with a live `pending` → `ok`/`error` status.

The stream is built from text deltas and end-of-turn events, so whatever a provider attaches
to a whole message, or does inside its own turn, is only known once that turn has finished.
Four details follow from that. The **text is the same either way** in all of them, and the
fetched transcript is the exact one:

| Detail                                                                 | Live            | Fetched                  |
| ---------------------------------------------------------------------- | --------------- | ------------------------ |
| Citations                                                              | absent          | on the text block        |
| Search queries                                                         | absent          | on the `web_search` chip |
| [Web search](/tools/web-search) and remote [MCP](/tools/mcp) chips     | absent          | present, in position     |
| Where a chip splits the text, if the model keeps talking after calling | after that text | at the call              |

## What this does not replace

`output` and `emitted` are unchanged and always present.

* Reach for **`output`** when you want the answer and nothing else — most integrations.
* Reach for **`emitted`** when you want the run's structured results without walking a
  list — a pipeline or a webhook follow-up.
* Reach for **`transcript`** when you are rendering the run to a person.

All three derive from the same run, so they cannot disagree.
