jun eval on every pull request, and run jun push when the change merges. This page sets that up with GitHub Actions. Any CI that runs Node.js works the same way.
Before you start
- The config folder (here
support-agent/) is committed in your copy of the Jun repository, with its.jun-versionfile. See Agent as code. - You have an API token for CI. Create one in MCP → API tokens and name it after its use (
GitHub Actions). The token acts as the person who created it:evalworks for any member,pushneeds an owner or admin. If that person leaves the workspace, the token stops working.
Add the secrets
In your GitHub repository, open Settings → Secrets and variables → Actions and add:
With both set, the CLI skips
jun login and its ~/.jun/desk.json file.
The workflow
Save this as.github/workflows/support-agent.yml:
.github/workflows/support-agent.yml
- On a pull request that touches
support-agent/, it runs your test cases and replays recent real conversations against the branch’s files. The job fails if a test case fails or errors. The log shows each case and each replayed answer that would change. - On a merge to
main, it pushes the folder as a new live version, with the commit’s first line as the version note (notes over 500 characters are refused, so the step cuts it).
node-version: 22 installs the latest 22.x.
The commit message goes through an environment variable, not straight into the shell script, so a message with quotes or
$( ) can’t run as a command.Choosing what fails the build
jun eval exits with code 1 when:
- a test case fails,
- a test case or a replayed conversation errors,
- the run can’t start: the config has errors, the token is wrong, or the desk’s AI provider isn’t usable,
- with
--fail-on-change, any replayed answer changes in substance.
--fail-on-change for changes that shouldn’t alter any answer, such as rewording or reorganising files.
Other flags that suit CI:
Model answers vary a little from run to run even at temperature 0, and
criteria and the replay comparison are judged by a model. Keep criteria concrete (“offers at most the 50% discount”) rather than about tone, so a pass means the same thing every time.
JSON output
--json prints one JSON object per line (NDJSON), in this order: one start, then case, replay and error events as they finish, then one done.
-s (silent) stops npm from printing its own > jun banner lines into the output. npx jun or node packages/cli/src/index.ts don’t print one.
object
{ "type": "start", "liveVersion": 12, "cases": 5, "conversations": 20 }. liveVersion is null while the desk uses the built-in default config.object
One test case:
file, name, pass, failures (strings like expected outcome handoff, got answer) and result.object
One replayed conversation:
conversationId, question (the customer’s first message), verdict (same or changed), why, and the live and candidate replies.object
{ "type": "error", "scope": "tests/basics.yaml: greeting", "message": "…" }. Any error makes the exit code 1.object
{ "type": "done", "cases": { "passed": 4, "failed": 1 }, "replay": { "same": 19, "changed": 1, "errors": 0 } }.result, live, candidate) has:
answer | handoff | escalate | action
What the reply did.
string
The reply’s text. Handoffs read
(hands off: <reason>); cards, proposed website actions and flagged problems are described in parentheses.string[]
The actions called.
object
The website action proposed, with its inputs:
{ "name": "add_to_cart", "input": { "size": "M" } }.string[]
The widgets shown.
In a pipe, the step’s exit code is the last command’s (
tee) unless the shell uses pipefail. Setting shell: bash explicitly makes GitHub Actions run the step with -eo pipefail, so a failing eval still fails the step.When a push is refused
The push job fails, and nothing changes on the desk, when:-
The config has errors. The log lists each one. Since the pull request ran
evalon the same files, this is rare. -
Someone saved on the Agent page since the folder’s
.jun-version. The push won’t overwrite their edits. Pull them into a branch, merge, and merge that:Pushes from git never conflict with each other, only with desk saves.
--force to the CI push: it would silently discard edits people make on the desk.
jun CLI
Every command and flag.
Tests
Writing test cases.