Run a single goal non-interactively
cantrip run --print "<goal>" drives one goal through the agent loop with no TUI, prints progress to stdout, and exits when the work queue drains.
When to use it
Print mode replaces the TUI with a single-shot CLI invocation:
- CI charm-build jobs. The runner kicks off
cantrip run --print --json --yolo "pack and deploy", streams the NDJSON event log for the build summary, and reads the exit code to decide whether to fail the job. - Shell pipelines.
cantrip run --print --json … | jq 'select(.type == "task_updated")'surfaces only the events you care about, without writing a custom log parser for the TUI's terminal output. - One-off goals. "Add an `ops-tracing` integration to this charm" is a goal that finishes; you don't need a long-running session to express it.
If you want a conversation, stay in the TUI or
--no-tui CLI mode — print mode runs the goal once
and exits.
How to enable it
$ cantrip run --print "Add ops-tracing to this charm"
$ cantrip run -p "Audit and fix charm" /path/to/charm
$ cantrip run --print --json "Pack the charm" | tee events.ndjson
The --print flag (alias -p) takes the
goal as its argument. The positional charm-path argument still
works; combine it with --print to point at a charm in a
non-current directory.
The JSON event stream
With --json, every
cantrip.ui.events
payload writes one NDJSON line to stdout. The schema is the same one
the TUI and Web UIs subscribe to, exposed as a stable contract:
{"type": "task_updated", "data": {"id": "abc", "title": "...", "status": "active", ...}, "timestamp": 1730000000.0}
{"type": "chat_message", "data": {"role": "assistant", "content": "..."}, "timestamp": 1730000001.5}
{"type": "permission_decided", "data": {"tool_name": "run_command", "outcome": "deny", ...}, "timestamp": 1730000002.0}
{"type": "permission_auto_approved", "data": {"tool_name": "git_push", "reason": "yolo", ...}, "timestamp": 1730000003.0}
Top-level fields are always
type (the
EventType string), data (the payload
object), and timestamp (UNIX seconds, fractional). The
event types print mode emits include:
task_updated— a queued task changed status or category.chat_message— the agent or a subagent produced text.tool_invoked— one tool call boundary, with the caption and duration.permission_decided,permission_auto_approved— declarative-policy outcomes.compaction_started,compaction_completed— context manager fold points.goal_budget_exceeded,policy_rate_limited— hard stops the run encountered.snapshot_created,snapshot_restored— per-turn working-tree snapshots.
For the full list see the print-mode event schema in the CLI reference.
Confirmations refuse the run by default
Print mode is unattended by definition — nobody is watching for a CONFIRM prompt. If a previously-resumed session has unresolved confirmations, or one appears mid-run, the runner exits non-zero with the list:
Refusing to run unattended: pending confirmations would block the queue.
Re-run with --yolo to auto-approve `ask` permissions, or resolve them
interactively in the TUI/CLI mode first.
Pending confirmations:
- [c1] Approve git push origin main
The fix is one of:
- Pass
--yolo. Auto-approves every ask permission for the run. Deny rules still block. See Run Cantrip unattended for the safety story. - Resolve interactively first. Launch
cantrip run /path/to/charm, accept or reject the pending CONFIRM, then re-run print mode. - Tighten the policy. Convert the rule that
triggered the ask into an explicit
allowordenyin.cantrip/permissions.yaml; the run no longer needs--yolo.
Exit codes
0- The work queue drained with every task in
donestatus (or no tasks were created). 1- One or more tasks finished in
failedorblockedstatus, the provider returned an error, the drain timed out, or pending confirmations blocked the run. 2- CLI argument error — an empty goal, or
--printcombined with--web. 130- Interrupted by Ctrl-C.
Composing print mode in a script
A typical CI shell invocation:
$ cantrip run \
--print "Pack and deploy the charm, run integration tests" \
--json \
--yolo \
--max-iterations 50 \
/workspace/my-charm \
| tee build.ndjson \
| jq 'select(.type == "chat_message" and .data.role == "assistant") | .data.content'
The tee keeps the full event log for later
inspection; the jq filter prints only the agent's
chat replies for the build log. Exit-code propagation lets the CI
job decide whether the build counts as green.
Concrete automation examples
A GitHub Actions step can keep the full NDJSON transcript while still failing the job on a blocked or failed run:
- name: Cantrip charm gate
run: |
cantrip run \
--print "Audit the charm, fix lint issues, then pack it" \
--json \
--yolo \
"$GITHUB_WORKSPACE" \
| tee cantrip.ndjson
For scheduled maintenance, keep the same one-shot shape but swap the goal. A nightly cron job can re-audit a repo and archive the event log without opening an interactive session:
$ cantrip run \
--print "Audit this charm and report any regressions" \
--json \
/srv/charms/my-charm \
| tee /var/log/cantrip/nightly-audit.ndjson
When you need stricter governance than blanket
--yolo, pair print mode with an explicit
permissions policy and use
cantrip audit to inspect the
decisions afterwards.
Related references
- cantrip run — the
full flag list on the
runsubcommand. - Print-mode event schema — reference for every NDJSON event type.
- Run Cantrip unattended
— companion guide for
--yolo. - Configure tool permissions — the policy that decides which tool calls auto-approve under yolo.