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

# Streaming components

> Yield strings for markdown or dicts for rich components: thinking bubbles, tables, code, callouts, images and client actions.

An agent body yields two kinds of value. Strings render as markdown. Dicts render
as components. Both stream as they are produced, so the interface fills in while
the model is still working.

```python theme={null}
@cycls.agent(web=web, volumes={"/workspace": chats})
async def demo(context):
    yield {"type": "status", "status": "Checking the fleet"}

    yield "Here is what I found:\n\n"

    yield {"type": "table", "headers": ["Server", "Status", "CPU"]}
    yield {"type": "table", "row": ["web-1", "Online", "45%"]}
    yield {"type": "table", "row": ["web-2", "Offline", "0%"]}

    yield {"type": "code", "code": "systemctl restart web-2", "language": "bash"}
    yield {"type": "callout", "callout": "web-2 needs attention.", "style": "warning"}
```

## Component reference

| Type       | Required keys                      | Behavior                                                     |
| ---------- | ---------------------------------- | ------------------------------------------------------------ |
| `text`     | `text`                             | accumulates into one block. A bare string is the same thing  |
| `thinking` | `thinking`                         | accumulates into a collapsible bubble                        |
| `code`     | `code`, `language`                 | accumulates into one block                                   |
| `table`    | `headers` or `row`                 | renders row by row                                           |
| `callout`  | `callout`, `style`                 | one card. `style` is `info`, `warning`, `error` or `success` |
| `status`   | `status`                           | a single line that replaces the previous status              |
| `image`    | `src`, optional `alt`, `caption`   | one image                                                    |
| `sources`  | `sources`                          | citation chips from `[{title, url, snippet}]`                |
| `step`     | `step`, optional `tool_name`, `id` | a tool step line                                             |
| `ui`       | `action`                           | a client action, not rendered and not stored                 |

Raw HTML strings pass through, which is the escape hatch for anything the
components do not cover.

## Thinking

Consecutive `thinking` yields append to the same bubble until a different type
arrives. Provider reasoning deltas map here automatically, so extended thinking
from Anthropic and `delta.reasoning` from OpenAI-compatible endpoints both show
up without any work on your side.

```python theme={null}
yield {"type": "thinking", "thinking": "Checking the invoice totals "}
yield {"type": "thinking", "thinking": "against last month."}
yield "The totals match."
```

## Tables

Send the header row once, then one `row` per record. Rows render as they arrive,
so a slow query shows partial results instead of nothing.

```python theme={null}
yield {"type": "table", "headers": ["Region", "Revenue"]}
for region, revenue in await query():
    yield {"type": "table", "row": [region, f"{revenue:,.0f}"]}
```

## Client actions

A `ui` event triggers something in the client. Nothing is rendered in the
conversation and nothing is kept in history.

```python theme={null}
if await over_quota(context.user):
    yield {"type": "callout", "callout": "Free tier limit reached.", "style": "warning"}
    yield {"type": "ui", "action": "open_plan_modal"}
    return
```

| Action            | Fields                  | Effect                                                                             |
| ----------------- | ----------------------- | ---------------------------------------------------------------------------------- |
| `open_plan_modal` | none                    | opens pricing. The client picks user or organization plans based on the active org |
| `open_canvas`     | `path`, optional `name` | opens a workspace file in the canvas viewer                                        |
| `suggest`         | `text`                  | one follow-up chip above the composer                                              |
| `ask`             | `questions`             | a question card above the composer, up to three questions                          |

The `Canvas`, `Suggest` and `Ask` [built-in tools](/agents/tools) fire these for
you. Yield them directly only from a custom loop. One difference worth knowing:
when the `Ask` tool fires, the turn ends, because the next user message carries
the answers.

## Errors

An unhandled exception inside the body becomes a callout with a short reference
id, and the full traceback goes to structured logs.

```
Something went wrong. Reference: abc12345
```

Look it up with the reference:

```bash theme={null}
cycls logs my-agent --query 'jsonPayload.error_id="abc12345"'
```

Errors the loop handles itself, such as a rate limit retry or a tool timeout, are
shown as callouts and are not logged as failures.

## Next

<Card title="Context" icon="user" href="/agents/context">
  What the body receives on every turn.
</Card>
