Messages are recorded explicitly by design, via the Python SDK, so a transcript shows the user's words rather than whatever the prompt builder assembled.
The conversation, as it actually happened.
Messages in the order they were sent, the tools and model calls that produced each answer, their durations and any errors. Not a rewritten prompt log, what the human typed and what the agent said back.
Everything behind the answer, in order.
A transcript is the messages, in the order they were sent, with the tool and task calls that produced each answer alongside them, timings and errors included. Attachments show up as what was shared, and every message, tool call and action log can carry its own metadata. A turn that never finished is recorded as incomplete, with a reason, rather than leaving a silent gap.
| Record | What it carries |
|---|---|
| Messages | Sender, content and metadata, in the order they happened |
| Tool calls | Name, arguments, response, error and duration |
| Task calls | Internal work, parsing, reranking, captured the same way |
| Buttons | Clicks, recorded as interactions rather than fake sentences |
| Attachments | Sender, count and file list; contents only if you upload them |
| Turn completeness | Whether it finished, and why not, error, abandoned, deadline, shutdown |
Message content, tool responses and error strings truncate at 16,384 characters. Conversation string fields clamp at 255.
Structured answers render as blocks.
Define a per-agent HTML block bound to message-metadata keys. When a message carries all the bound keys, the block renders inside the bubble, so an order lookup arrives as an order card, not a JSON blob your client has to decode.
Templates support {{key.path}} substitution and {{#each}} loops over arrays. The HTML is sanitised server-side against an allowlist, so a template cannot smuggle script into a client’s view.
Hi! Just checking, has order #7734 shipped yet?
Yep, right on schedule, here's where it stands:
Perfect, thank you! Could you add a gift note too? I typed it up here.
Done, added to the package. Anything else?
<div class="order-card"> <div class="order-card__head"> <span>Order {{order.id}}</span> <span class="order-card__badge">{{order.status}}</span> </div>{{#each order.items}} <div class="order-card__item"> <span>{{name}}</span> <span>{{qty}}×</span> </div>{{/each}} <div class="order-card__eta">Arriving {{order.eta}}</div> <button type="button">Track package →</button></div>Ten levels is a limit on the key path you filter or bind by, not on how deeply the JSON you store may nest.
Robust metadata, on every conversation, message and action log.
Attach whatever JSON your product already tracks, an order id, a plan tier, an internal flag, and it becomes a key path you can filter by, discover from existing traffic, or bind a message template to. A path can run ten segments deep, which comfortably covers a nested object like order.customer.plan without needing a flattened schema on your side.
| Field | What it means |
|---|---|
| Conversation metadata | Free-form JSON, updatable as the conversation unfolds |
| Message metadata | Free-form JSON per message, what a template binds to |
| Action log metadata | Free-form JSON per tool or task call |
| Key paths | Up to ten segments deep, e.g. order.customer.plan |
| Discovery | Sampled from the most recent 1,000 rows, up to 200 keys or values back |
| Bound to a template | Up to 20 keys per template, 20 templates per agent |
Recording that a file exists and uploading its contents are two separate calls, on purpose.
Files a client shared, without moving bytes you do not need to.
record_attachments() notes that a file exists, its name, size and type, and moves no bytes, useful when the file already lives on your own storage. upload_attachments() sends the contents and stores them with the conversation, so they show up in the dashboard.
| Limit | Value |
|---|---|
| Per file, decoded | 25 MB |
| Per request | 10 files |
| Per request, on the wire | 40 MB (base64 inflates a decoded file by roughly a third) |
| Over any limit | 413, not retried automatically |
Bring the history you already have.
Upload one JSON document, a schema version and a list of conversations, each with an id, a start time and its messages, through the dashboard’s migrate page. Imports are deduplicated, and a committed run can be undone from the import history if something was mapped wrong.
Only conversations and messages come across, no tool calls, token usage, actions, feedback or attachments, so historic cost and token charts stay empty for imported history. The agentsight-migration skill can inspect your source data and generate the file for you; you still complete the upload yourself.
Filter down to the one that went wrong.
Filter by date range, environment and your own metadata keys, then open a conversation and read it end to end. When your client finds the bad exchange, they raise a ticket against it from the same screen.
How tickets workTranscript questions.
Do you store our prompts?
No. The transcript holds the messages you explicitly record with user_message() and agent_message(). Model spans carry token counts and model ids, never prompt or completion text.
Can a client see the tool calls?
Only if you let them. Action logs are one of the eight fields you can hide from the client-facing view.
How deep can metadata go?
A key path can run ten segments deep, that is a limit on the path you filter, discover or bind a template by, not on how the JSON itself nests. Discovery samples the most recent 1,000 rows and returns up to 200 keys or values.
What can I upload as an attachment?
Any file type. Limits are 25 MB per file, 10 files per request, and 40 MB per request on the wire, since base64 inflates a decoded file by roughly a third.
Can we import old conversations?
Yes, upload a JSON export through the dashboard's migrate page. It brings across conversations and messages; tool calls, usage, actions, feedback and attachments do not migrate, so those charts start from zero on imported history.
What happens to an unfinished turn?
It is recorded as incomplete with a reason: error, abandoned, deadline or shutdown. Silent gaps would be worse than a labelled one.
Give your client the conversation, not a log file.
- Full dashboard on the free plan
- No card required
- Model spans never carry prompts