Playground and evals

Try an agent interactively, compare variants, and measure it against versioned datasets before you ship a change.

After reading this page you will be able to:
  • Run and compare prompts in the playground
  • Build an eval dataset from JSONL, transcripts or tickets
  • Run an eval against a baseline and gate CI on it
  • Red-team an agent and gate its release on the result

Playground

The agent's Playground tab sends a message through the same governed path real traffic uses: guardrails, budgets and the audit log all apply.

ActionEndpointUse it to
RunPOST /api/agents/{key}/playgroundTry a message
ComparePOST /api/agents/{key}/playground/compareRun variants side by side (instructions, model)
ReplayPOST /api/agents/{key}/playground/replayRe-run a past conversation with edits
SimulatePOST /api/agents/{key}/playground/simulateCheck whether a given person could run the agent, from a channel and context. A dry run: nothing is sent to a model and nothing runs as them

Simulated users (/api/agents/{key}/simulations) let a model play a user with a hidden goal for multi-turn tests.

Attach files

Attach (or drop or paste) up to 5 files on a message. Each is uploaded as soon as you pick it, with POST /api/agents/{key}/playground/files, and shows its preview or the reason it was refused. Send waits until every file has uploaded.

ImagesPNG, JPEG, GIF or WebP, up to 5 MB. Sent to the model as an image
DocumentsPDF, Word, Excel, PowerPoint, text, Markdown, CSV, TSV, JSON, log, XML, YAML, and zip or gzip archives, up to 10 MB. The agent is given the text read from them, marked as data you supplied

A file's contents must match its name, and programs and scripts are refused whatever they are called. Archives are read under the same bounded extraction as email attachments, so a decompression bomb cannot exhaust the server. A document with no readable text (a scanned PDF, an empty file) is refused, since the agent could not use it. Files are kept for 7 days, or until their session is deleted, and only you can open yours. A reopened session shows each message's files and says which have expired.

Compare variants against a checklist

Compare asks the prompt in the message box under 2 to 4 variants, each a system half and a model, and shows the answers side by side. Each column is its own governed run with its own transcript.

  • Attach document uploads a file the same way the chat's Attach does. Every column is given the same document as text, so you can see whether a model keeps its instructions with a long document in its context. Files attached in the message box go with the compare too.
  • Rules each reply must keep is a checklist of up to 10 rules, scored on each column's reply. Choose Load demo rules for a ready-made set: JSON only, at most 50 words, never "sorry", ends with "— Liya", and an escalate field that is true.
  • Each column shows its speed, for example first token 1.2 s · 240 tok/s · total 3.4 s, and a pill such as 5/6 rules kept above a ✓/✗ line for each rule.
RuleValueJSON field
json_onlyOptional. The keys the object must have, exactly, comma-separatedNot used
max_wordsThe most words allowedOptional
ends_withThe text the reply must end withOptional
contains, not_containsText that must (or must never) appear, in any caseOptional
regexA pattern, checked like a guardrail's: RE2 syntax, no lookarounds or backreferencesOptional
json_equalsThe value, read as JSON: true matches true, 3 matches 3. Anything else is a stringRequired

A JSON field such as answer or $.answer.text makes the rule read that field of a JSON reply rather than the whole reply. A reply that isn't JSON fails every rule with a field. Rules are checked before anything runs, so a bad rule is refused with 422 and no column is run. A column that was refused or failed has no reply to check, and says so.

The API takes rules ({kind, value, path, label}) and files (upload ids) on POST /api/agents/{key}/playground/compare. Each column then carries rules ({scored, kept, total, results}) and speed. The audit row of each column records how many rules it kept and which failed.

speed fieldMeaning
first_token_msFrom sending the run's first model request to its first streamed token. null for providers other than OpenAI-compatible and Anthropic
model_msThe time the run's model calls took, added up
output_tokens_per_sOutput tokens divided by generation time: model_ms less the wait for each call's first token, where known

To time the first token, the playground streams its calls to OpenAI-compatible and Anthropic providers and puts the answer back together before guardrails, metering and the transcript see it. Single runs (POST /api/agents/{key}/playground) report speed too. Calls made anywhere else are not streamed.

What else the Playground shows

  • Tables and charts in replies, when the agent's instructions say how to write them. See Tables and charts in replies.
  • Outputs: the charts, tables and code the agent wrote, results its tools kept for you and resources they returned, each downloadable, and the sources it used (links it cited and tools it read), each tied to its turn.
  • Requests waiting for you: an approval, a connection, an answer or a form an MCP server sent is pinned above the message box, asked one question at a time. A send that fails keeps what you typed. Once settled, a request folds into one line in its turn as answered or declined.
  • Context: an estimate of what the next request will carry, against the model's input and output limits from the model catalogue, kept apart from the token counts the provider reported.

Feedback on replies

A person may rate any reply: POST /api/agents/{key}/playground/feedback with {"conversation", "helpful", "comment"} records the vote on the conversation (feedback on its record), on the audit trail as agent.reply.feedback, and in the agent's counters (agent.feedback.helpful, agent.feedback.unhelpful). The mobile app offers Helpful and Not helpful under each reply, with room to say what was wrong. This is the human oversight a deployer must be able to show.

Long chats

A chat can run as long as you like. Each follow-up is sent with the newest 30 exchanges as they were said. Once a chat grows past that, its oldest turns are folded into a summary of the conversation, written by the agent's own model under the same guardrails, budget and audit as any of its calls, and sent ahead of the newest turns. The summary is updated about every 10 exchanges, not on every message. If the model call is refused, a plain extract of the turns is kept instead, so nothing is dropped without a record.

Each request is also fitted to the context window of the model it goes to, measured in tokens. If the newest turns alone would not fit, the oldest of them are folded into the summary for that request. If a provider still refuses a prompt as too long, the request is sent once more with half the history, rather than the message failing.

When a reply was written from a summary, the chat says so under it, in the console and in the mobile app. The summary is stored with the session, sealed like its messages, and deleted with it. Under the off recording mode nothing is kept, so nothing is summarised either.

Datasets

Datasets are versioned and append-only. Each has a kind:

KindMeasures
retrievalDid the right knowledge come back?
triageCategory, priority and team against expected values
agent_turnAn agent's reply, scored by evaluators

Create one from Agents › Evaluations or with the API:

  1. POST /api/evals/datasets with a name, a kind and its cases, as items or as JSONL text in jsonl. Posting again writes the next version, but append: true adds to the latest instead of replacing it.
  2. Add cases from a transcript (POST /api/evals/datasets/{name}/from-transcript, redacted), or from tickets (.../from-tickets).
  3. Optionally split it into calibrate and holdout sets with a fixed seed (.../split).

Runs

  1. Start a run with POST /api/evals/runs, naming the dataset and agent. The run pins the dataset version and the agent's config version.
  2. Open the run on Agents › Evaluations. Scores are compared with the baseline run.
  3. When you are happy, mark it as the new baseline with POST /api/evals/runs/{run_id}/baseline.
Tip

In CI, liya eval run --dataset <name> --fail-on-regression (the tiq eval run verb, forwarded) exits 1 when a run fails or regresses against its baseline. liya agents eval <agent> --dataset <name> runs a dataset against one agent and exits 1 on a failure or regression.

Evaluators (/api/evals/evaluators) include built-in checks and LLM-judge evaluators. POST /api/agents/{key}/evals/generate drafts cases from an agent's description to get you started.

Red teaming

Agents › Red teaming throws known attacks at an agent: instruction override, role-play jailbreaks, system-prompt extraction, PII exfiltration and tool abuse, each obfuscated (base64, homoglyphs, zero-width characters and more) and sent directly or planted in content the agent reads. A run reports the attack success rate (ASR) overall and by category, transform and vector, each with a 95% interval.

TargetWhat it measures
guardrailsEach attempt screened by the agent's own guardrails. No model is called, so a run takes seconds.
agentEach attempt asked of the agent through the governed path, and its answer judged. Needs transcripts.content.read.

A run records the configuration it measured: the agent's version fingerprint. Successes in PII exfiltration and tool abuse are critical findings, and the report counts them.

  1. Start a run from the page, or with POST /api/redteam/runs (governance.write) naming the agent, the target and optionally a subset of categories, transforms and vectors.
  2. Open it on Agents › Red teaming. Compared with the latest earlier run, it lists the attacks that newly get through.
  3. Export the attempts with GET /api/redteam/runs/{run_id}?format=csv.
Tip

In CI, python -m ticketiq.redteam --agent <id> --max-asr 0.1 exits 1 when the ASR is over the bar. Add --baseline latest to fail on a rise against the last run as well.

Release gate

With the release gate on, an agent configuration is released only after a red-team run of exactly that configuration passed. These are refused with 409 (code: redteam_gate) until it has:

  • restoring a version (POST /api/agents/{key}/versions/{id}/restore)
  • promoting a candidate that restores a version or adopts an optimizer proposal (POST /api/settings/candidates/{id}/promote)
  • publishing the agent's card for the first time (PUT /api/agents/{key}/spec).

The refusal names each failed criterion and links the run it judged. The criteria:

SettingDefaultThe release passes when
requiredoffNot applicable (the gate is on)
max_asr0.1the run's overall ASR is at most this
no_criticalonno PII-exfiltration or tool-abuse attack got through
max_age_days14the run finished at most this many days ago
targetanythe run's target is this (guardrails, agent)

The run must be of the configuration being released: its fingerprint is the agent's version snapshot without the card, so publishing a card does not void the run made just before it. The newest completed run of that configuration is the one judged. An optimizer proposal has never been in force, so no run can have measured it. With the gate on, it is adopted only through an override. Agents a run cannot target (the built-in pipeline steps) are outside the gate.

Set the organisation default on Agents › Red teaming (PUT /api/redteam/gate), and an agent's own policy on its History tab (PUT /api/agents/{key}/redteam-gate, or {"inherit": true} to follow the default again). Both need governance.write. Loosening a gate that is on also needs governance.weaken and a reason. The History tab shows whether the agent's current configuration would pass.

An owner can release past a refusal: send override_redteam_gate: true and a reason with the release. It needs governance.weaken, and is audited as redteam.gate.override with the criteria that failed. Refusals are audited as redteam.gate.refused.

Next steps