Start qualitatively, then turn the stable contracts you discover into tests. Automated tests are best for definition shape, required tool calls, and a small number of critical prompt paths.
Run tests from the project root:
@meetluno/cli. No project vitest.config.ts is required.
Qualitative tests
Before writing test files, run the agent the way you expect people to use it. Ask your coding agent to call the Luno OS CLI with a batch of prompts, inspect the sessions, and report where the agent misunderstood instructions, skipped tools, or used tools incorrectly.--wait returns each completed session for review. For long prompts, choose a bounded wait with --timeout <seconds> and continue with agents sessions get <agent> <session-id> --wait if the session is still running.
Good qualitative prompts cover:
- Normal requests the agent should handle cleanly.
- Tool-required requests where the agent must call an action, workflow, MCP tool, or subagent.
- Missing-information cases where the agent should ask a clarifying question instead of guessing.
- Follow-up messages in the same session.
- Edge cases that should be refused, escalated, or handled cautiously.
Definition tests
Definition tests are fast and do not need provider keys.Smoke-tests
The init template includes an agent integration test shaped like this:model (or use AI_GATEWAY_API_KEY, which works for any catalog id).
Run integration tests from the project root:
.env when present and skip when required keys (like AI_GATEWAY_API_KEY) are unset. Vitest and its config ship with @meetluno/cli — no project vitest.config.ts.
Tool use tests
When a tool call is the contract, assert on the recorded messages rather than only the final answer.Sessions and memory
Prompt tests create sessions. If a test should be repeatable, use a fresh session or disable memory on the agent under test:sessionId and pass it to the next prompt:
Evals
Evals score real agent replies instead of asserting exact text. Put them insrc/**/*.eval.ts and describe each suite with describeEval from @meetluno/luno/evals: every case prompts the agent with input, scores the reply with each scorer, and fails when any score (0–1) is under threshold (default 0.7). Scores and the judge’s reasons are printed per case.
src/agents/support.eval.ts
- LLM-judged scorers (
createAnswerRelevancyScorer,createRubricScorer,createToxicityScorer, …) take a judgemodel. UsegatewayModel("vendor/model")so the judge runs through the Luno OS gateway. - Code scorers such as
createKeywordCoverageScorer()(how many of the prompt’s keywords the reply covers) andcreateToneScorer()need no model.createRubricScoreris binary:1only when every required criterion holds. - A case can carry
groundTruthfor comparison scorers (similarity, completeness), andcreateScorerbuilds a custom one.timeoutsets the per-case limit (default 180 s).
Tool-call scorers
Scorers also see the tools the agent called (with their arguments), so you can check behavior, not only wording:createToolCalledScorer({ tool })—1when the agent calledtool,0otherwise.createToolArgsScorer({ tool, args })— the share of the expectedargsthe call matched (deep equality per key, extra args ignored);0when the tool was not called.- Mastra’s
createToolCallAccuracyScorerCode({ expectedTool, strictMode?, expectedToolOrder? }),createToolCallAccuracyScorerLLMandcreateTrajectoryAccuracyScorerCodeare re-exported too.
scorers:
Run and review
Run them withluno evals run, optionally filtered by file name (luno evals run support); extra arguments go to Vitest. Evals call real models (the agent and the judge) and cost money, so luno test never runs them.
When the project is linked and you are logged in, luno evals run uploads every case (input, reply, tools called, each scorer’s score and reason) as one eval run; --name nightly labels it and --no-upload keeps results local. A failed upload only warns, and the exit code is still the tests’ one. Review the history with luno evals list [--agent <slug>] and luno evals get <run-id>, or in the dashboard’s project Evals tab: the mean score per scorer over time, the runs table, and each run’s cases with the judge’s reasons.
Failure inspection
When a prompt test fails, inspect the session before changing code:/history) in the web app and filter to agent runs. The detail panel shows messages, tool calls, metadata, and trace data.
Next steps
Run agents
Prompt agents locally and inspect sessions from the CLI.
Agent runs
Debug failed sessions in the web app.
Actions as tools
Build deterministic tool contracts that are easier to test.
Deploy a project
Run tests before deploying changed agents.