---
title: Learners and traces
description: Name your app's users as learners, keep their conversations, and store a trace of what each run did.
updated: 2026-10-29
---

> All VIABLE docs, for people and their agents: https://developers.viablelab.org/llms.txt

# Learners and traces

Name the person behind each run so VIABLE keeps them as a learner, and record what each run did. Set this up before real people use your app.

## Name your users with `user`

Send `user` on every run: a stable, opaque ID your server derives from the account it verified, up to 128 characters. Namespace it (`my-app:7f3a9c21`) or hash it. Never use a name, an email, a shared account or an unchecked ID.

- The first run with a new `user` creates a learner in the project, with a learner code (`L-…`). Later runs, on any agent in the project, add turns to it.
- [Teach → Learners](/dashboard/learners) lists them with your app's name and your ID. Viewers see that ID too.
- **Research → Export**, with **Your apps (API)**, exports their turns, and what you [send as learning data](/docs/connect/learning-data). `learners.csv` carries your ID as `external_id`.
- `user` doesn't enroll anyone or ask for consent. Consent for research on your users is your app's to handle.

Omit `user` for staff and test accounts. Their runs still run and are logged.

## Label task groups with `learner_group`

Send `learner_group` with `user` to label a turn's task group, such as `"Grade 8"`. It is kept on the turn, never in the prompt. **Teach → Learners → Group** filters by it, and `turns.csv` includes it. It never changes a course section or study condition. Without `user`, it returns 400.

## Keep the conversation

By default VIABLE keeps the conversation. The first run returns a `conversation_id`. Store it per person, thread and agent, and send it with each new `input`. Never share one among people. Only the key that started it can continue it.

To move a thread your app already has, seed it: send `messages` with two or more entries, the last from the user, and no `conversation_id`. The response carries the new `conversation_id`. To keep history yourself, send `history_mode: "client"` with the full `messages`.

## Ask for the trace

Add `"include": ["trace"]` to `/runs` or `/jobs`. The result then has `trace`.

```bash
curl https://api.viablelab.org/v1/agents/YOUR_AGENT_SLUG/runs \
  -H "Authorization: Bearer $VIABLE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"input": "Is my thesis clear?", "user": "my-app:student-0001",
       "conversation_id": "agtc_...", "include": ["trace"]}'
```

- `duration_ms` is wall-clock time. Don't add up steps: parallel branches overlap.
- `steps[]` lists each step's `node_type`, `status`, `model`, `tokens`, `cost` and `error`.
- `vetting[]` has one entry per VETTING step: `outcome` (`passed`, `unverified`, `attention`), `attempts` (more than one means a rewrite) and each round's `verdict` and `feedback`.

## Read a run back

`GET /v1/agents/{slug}/runs/{run_id}`, with the key that made the run, returns it with its `trace`. Runs from another key, a course or a chat page return 404 `run_not_found`.

## What to store

Keep one record per turn on your server:

```js
export const runRecord = (run, sent) => ({
  run_id: run.run_id ?? run.id, // a run read back calls it id
  conversation_id: run.conversation_id ?? null,
  idempotency_key: sent.idempotencyKey,
  user: sent.user ?? null, // null records that none was sent
  learner_group: sent.learnerGroup ?? null,
  reply: run.output.text,
  tokens: run.usage?.total ?? null,
  cost: run.cost ?? null,
  trace: run.trace ?? null // the whole trace as JSON
})
```

For failed requests, also keep the HTTP status, `error.code` and `error.request_id`.

> [!WARNING]
> A trace holds what learners wrote. Keep it on your server, never in the learner's browser, and never show it to learners.

::: cards
- [Send learning data](/docs/connect/learning-data): add what learners do, write and score in your app.
- [Background jobs and retries](/docs/connect/jobs-and-retries): retry a turn without running it twice.
- [Export](/docs/research/export): download your app's turns for analysis.
:::
