Learners and traces

Name your app's users as learners, keep their conversations, and store a trace of what each run did.

2 min read · Updated Oct 29, 2026

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 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. 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.

This page is also plain Markdown at /docs/connect/learners-and-traces.md. Point your AI at /llms.txt to find every page.