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
usercreates 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.csvcarries your ID asexternal_id. userdoesn'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.
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_msis wall-clock time. Don't add up steps: parallel branches overlap.steps[]lists each step'snode_type,status,model,tokens,costanderror.vetting[]has one entry per VETTING step:outcome(passed,unverified,attention),attempts(more than one means a rewrite) and each round'sverdictandfeedback.
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:
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.
- Send learning data: add what learners do, write and score in your app.
- Background jobs and retries: retry a turn without running it twice.
- Export: download your app's turns for analysis.
