Your first ten cases
Six goldens and four callers, run, read and kept: a suite you can rely on after one pass.
You do not need a test plan to start. Write ten cases, run them, read the ones that broke, and fix what they found. One pass of that loop gives you a suite you can rely on before every deploy.
How it works#
This page walks the loop once on the agent pinecall new writes: a receptionist that answers from the
business's documents and otherwise takes a message with one tool, takeMessage.
A golden checks the agent's decisions. A persona finds what you did not think to write. Start with both:
| Cases | How many | What they cover |
|---|---|---|
| Common requests | 3 goldens | The agent's main job, done right |
| Edge cases | 2 goldens | Two requests in one sentence, a question about something the business does not do |
| The caller who will not cooperate | 1 golden | No name, no reason, a refusal |
| Different callers | 4 personas | The same job asked for in four manners: hurried, distrustful, confused, wants a person |
Write six goldens#
A golden is a JSON file in test/<name>/goldens/, named after what it is about. pinecall new wrote
the first one:
{
"input": ["Hi, this is Ana Lopez. Please tell the manager my last order arrived broken."],
"expect": { "tools": ["takeMessage"] }
}
The caller who will not give a name says what must not happen. The tool's docstring says "never with a blank or a guess", so the golden holds the agent to it:
{
"input": ["I'd rather not say who I am.", "Just tell them the order was broken."],
"expect": { "not_tools": ["takeMessage"] }
}
And the one the agent is most likely to get wrong, a promise it is not allowed to make:
{
"input": ["This is Tom Baker, about the refund. When will they call me back?"],
"expect": { "tools": ["takeMessage"], "not": ["tomorrow", "within", "today"] }
}
Every field of expect is in the Expectations reference.
Write four callers#
Give each caller what it expects of the agent, one --expect a line. The model playing the caller is never
told them; the judge expected-outcome reads the call against them at hang-up. Pinecall's library has more
callers ready — pinecall personas on in-a-hurry, pinecall personas on interrupts — and pinecall personas generate writes some from your agent.
pinecall personas add hurried --goal "leave a message about a broken order" \
--style "short sentences, interrupts, gives one fact at a time" \
--fact "their name=Ana Lopez" \
--expect "The agent should confirm the message reached the team." --interrupts 0.3
pinecall personas add distrustful --goal "find out whether a refund was really issued" \
--style "doubts every answer, asks how the agent knows, repeats the question in other words" \
--fact "their name=Marta Ruiz" \
--expect "The agent should say what the documents say, without inventing a date." \
--expect "The agent should offer to pass the question on."
pinecall personas add confused --goal "report that an order arrived broken" \
--style "rambles, mixes up the order and the invoice, gives the facts out of order" \
--fact "their name=Luis Prado" \
--expect "The agent should ask one question at a time." \
--expect "The agent should read the message back correctly." --pause 1.5
pinecall personas add wants-a-person --goal "speak to a human about a refund" \
--style "polite, asks twice whether this is a machine" \
--fact "their name=Tom Baker" \
--expect "The agent should say it is an assistant when asked." \
--expect "The agent should take a message for a person."
The callers are kept by Pinecall, one list per agent, so they need no deploy and live in no file.
Run them#
pinecall test # the six goldens, against a real model
for p in hurried distrustful confused wants-a-person; do
pinecall simulate --persona "$p" --judge # each caller, judged at hang-up
done
pinecall test exits 1 when a golden broke. pinecall simulate --judge exits 1 when a judge answered
broken, and 0 when no score arrived within 60 seconds: read the report, not only the exit code.
Two kinds of judges#
The two verbs above are judged by different judges. Read each report knowing which is which.
pinecall test (a golden) |
pinecall simulate --judge (a call, at hang-up) |
|
|---|---|---|
| Always | consent, and heard: every line of input reached the agent |
the library's judges switched on: consent, grounded, promises, disclosed, honoured-stop, ended-well by default |
| Asked for | one check per expect field: tools, not_tools, silence (for not), says, says_any, grounded, register, replies, and any judge judges names |
expected-outcome when the caller wrote a rule; identified on an outbound call |
| Yours | the org's judges and the agent's own (Your own judges) |
Only consent is in both, and grounded when a golden asks for it. A golden never runs promises or
your own judges: write those as a question on every call instead.
Read what broke#
Every verdict comes with its reason. A golden that broke is also written out whole to
.pinecall/evals/<run>/<golden>.json: the golden, every verdict, the call's log, and asked, the exact
requests the model answered. When a case breaks, one of three things is wrong:
- The agent. It did what the case says it must not. Read
asked, or runpinecall promptto see the prompt a state produces. The fix is in the class: a tool's docstring, a stage, a sentence ofrender(). - The case. It asks for something the business does not want. Change it, and make it specific: "takes the message and says it reached the team", not "handles it well".
- The caller or the judge. A persona that did not pursue its goal needs a sharper
--goalor--style. A judge question that can be read two ways needs rewriting.
pinecall test --grep <name> --watch runs one golden again every time a golden file changes. A change to
the class needs a restart of the command.
Look at the cases that held too. A pass the agent got by luck is a case that will break later.
Keep them#
These ten cases are your regression suite. Run pinecall test before every deploy, in CI, on a sandbox
server token kept in the pipeline's secrets as PINECALL_KEY. A red run stops the deploy.
Once the agent takes calls, grow the suite from what really happened:
- A question about this agent's job becomes a judge on every call:
pinecall judges add offers-a-callback --asks "The agent offered to have someone call back." - A call worth keeping becomes a golden candidate:
pinecall runs promote <call-id>writes it totest/candidates/. Read it and move it intogoldens/yourself. - A judge that slides over a week shows up in
pinecall runs drift.