docs · guides

Logs, JSON and scripting#

hekma agent logs shows what an agent printed, and --follow keeps streaming it. For scripts, add --json to get machine-readable output on stdout, read notes and errors from stderr, and branch on the documented exit codes. Turn the update check off in CI with HEKMA_NO_UPDATE_CHECK=1 (Hekma also skips it when CI is set).

Read an agent's logs#

hekma agent logs ticker
2026-10-22T19:22:55Z [agent-out] ticker ready
2026-10-22T19:22:55Z [engine] engine: starting -> running

Each line has a UTC timestamp, the stream it came from, and the text. There are three streams:

StreamWhat it holds
agent-outThe agent's stdout.
agent-errThe agent's stderr.
engineHekma's own lifecycle notes for this agent, such as engine: starting -> running.

Without --follow, the command prints the lines Hekma has attributed for the agent and exits. Logs survive stops and restarts, so you can read them after an agent fails to find out why.

A one-shot read shows only lines that Hekma attributed while a command was supervising the agent. For a detached agent, output written while no hekma command was running stays only in the raw agent.log file (see Retention). In the output above, the agent has been running for several seconds, but only its first line was printed while a command held it.

Follow live output#

hekma agent logs ticker --follow

--follow prints what is already kept, then checks for new lines about three times a second. It exits on its own, with a note on stderr, when the agent stops, fails or pauses:

• note: Agent Instance 'ticker' is paused; no further output until it is resumed.

Press Ctrl+C to leave earlier; that is always safe and does not affect the agent.

A follow is also a supervising command: while it runs, Hekma meters the agent and enforces its budgets. That is why the Quickstart and Supervision and detached runs use it beside every detached agent.

For a detached agent, a follow streams new lines straight from the agent's raw log, starting from the moment the follow begins, and says so once:

• note: lines that follow may be un-attributed raw passthrough from agent.log: they were captured without a live engine tailer, so their timestamps are when this command read them, not when the agent wrote them

Those lines are always labelled agent-out, and their timestamps are when the follow read them. Lines the agent wrote before the follow started are not replayed; to see them, read logs/agent.log in the agent's Agent Home.

Hekma's main branch changes this: a follow first prints up to the last 200 lines written between commands, tunable with HEKMA_LOGS_BACKLOG_LINES (Unreleased: after 0.11.0).

Retention#

Hekma keeps each agent's attributed log in logs/output.log inside its Agent Home. When the file reaches 10 MB, Hekma rotates it, keeping the current file and two older ones (output.log.1, output.log.2): about 30 MB per agent. Older lines are deleted. If a rotation happens while you follow, Hekma prints a note, and a one-shot hekma agent logs re-reads everything still kept.

The same folder holds the other log files:

FileContents
output.logThe attributed lines hekma agent logs reads, as JSON lines. Rotated.
agent.logThe agent's raw stdout, as written. Not rotated.
agent-stderr.logThe agent's raw stderr. Not rotated.
instance.logEvery state change, with its cause, as JSON lines.
breaches.logEvery budget breach, as JSON lines.

The raw files grow without limit in 0.11.0. For an agent that prints a lot, clear them yourself while the agent is stopped.

Logs as JSON#

hekma agent logs ticker --json
{"schema_version":1,"instance":"ticker","stream":"agent-out","at":"2026-10-22T19:22:55Z","text":"ticker ready"}
{"schema_version":1,"instance":"ticker","stream":"engine","at":"2026-10-22T19:22:55Z","text":"engine: starting -> running"}

With --json, each line becomes one JSON object on its own line (newline-delimited JSON, or NDJSON). The shape is the same with and without --follow, so a script can stream it:

hekma agent logs ticker --follow --json | jq -r 'select(.stream == "agent-err") | .text'

Closing the pipe early, for example with | head -5, ends the command cleanly with exit code 0.

JSON from other commands#

These commands accept --json and print one JSON document on stdout:

CommandPrints
hekma agent list --jsonEvery agent with state, budget and usage, plus fleet totals.
hekma agent show NAME --jsonOne agent's runtime status.
hekma agent usage [NAME] --jsonUsage totals for one agent, or for the whole fleet.
hekma agent config get NAME [KEY] --jsonThe effective config, with the layer each value came from. Secrets stay masked.
hekma agent memory attach NAME --kind KIND --jsonThe memory backing that was attached.
hekma agent memory detach NAME --jsonConfirmation of the detach.
hekma agent logs NAME --jsonOne JSON object per log line (see above).

Every document starts with a schema_version. Check it before you rely on a field: Hekma adds fields without changing the version, and changes the version when it removes or renames one. Token counts are whole numbers, and dollar amounts are whole micro-dollars (divide by 1,000,000), always next to an estimate_label. JSON output documents every field.

register, start, stop, pause, resume, remove, send and config set have no JSON mode. Script them by their exit code.

Stdout and stderr#

Hekma keeps a command's result on stdout and everything else on stderr:

On stdoutOn stderr
Tables, JSON documents, log linesNotes (lines starting with •)
Success lines (✓ Started Agent Instance ticker) and the new stateErrors (lines starting with ✗)
Adoption lines ([hekma] NAME: adopted from a previous engine; …)
The update notice

So hekma agent list --json 2>/dev/null gives clean JSON even when Hekma has something to tell you, and redirecting stderr to a file keeps the notes for later. One exception: with no agents registered, the human hekma agent list prints its • No Agent Instances registered yet hint on stdout (with --json the hint moves to stderr). Do not parse the human tables: their columns are cut to the terminal width (set COLUMNS to widen them), and their wording can change. Use --json.

Exit codes#

Every command exits 0 on success, or with a code for the kind of failure: 1 general error, 2 invalid invocation, 3 not found, 4 wrong state, 5 not supported, 6 timed out. The codes are a stable interface; Exit codes and environment lists every condition behind each one.

A script that reports each agent's state and spend, and checks that a named agent exists:

#!/bin/sh
# Print each agent's state and estimated cumulative cost, or fail clearly.
set -u
export HEKMA_NO_UPDATE_CHECK=1

if ! fleet=$(hekma agent list --json 2>/dev/null); then
  echo "hekma agent list failed" >&2
  exit 1
fi

printf '%s\n' "$fleet" | jq -r '.instances[] |
  "\(.name)\t\(.state)\t\((.usage.cumulative_dollars // 0) / 1000000) USD (estimated)"'

hekma agent show "$1" --json >/dev/null 2>&1
case $? in
  0) echo "$1 exists" ;;
  3) echo "$1 is not registered" >&2; exit 3 ;;
  *) echo "could not read $1" >&2; exit 1 ;;
esac
spender	stopped	0.02376 USD (estimated)
spender exists

A plain start that succeeds exits 0 even though the agent stops when the command exits. Check list or show afterwards if your script needs the agent to still be running, and use start --detach for agents that should outlive the command.

Update checks in CI#

Before most commands, Hekma may check GitHub for a newer release and print a one-line notice on stderr; it never changes stdout or the exit code. Set HEKMA_NO_UPDATE_CHECK=1 to skip the check. It is already off when CI is set, which most CI services do. Turn off update checks lists the accepted values and the older variable name.

For a script that keeps its own state apart from your everyday agents, set HEKMA_STATE_DIR to a folder of its own. See Exit codes and environment for every variable.