Docs
View as markdown

Agent mode

How the relate CLI detects an agent harness, the two defaults agent mode changes, the row cap and its in-band report, the envelope, and agent-context.

cliagents

agent mode CLI defaults for a detected agent harness on a non-terminal stdout, or forced on: at most 100 rows printed per statement, the cut reported, TOON in a pipe. Read more in the glossary is a pair of default changes the CLI makes for a query run under an agent harness: at most 100 rows printed per statement, with the cut reported, and toon instead of jsonl as the output mode when stdout is piped. It turns on when relate detects a harness and stdout is not a terminal, or when --agent-mode or RELATE_AGENT_MODE=1 asks for it. An explicit --mode or --max-rows overrides either default. Agent mode never blocks a write, changes the envelope's shape, or changes an exit code, but its piped toon default prints no error object The JSON object a failed query run prints on stdout in the json, jsonl, envelope and speedscope modes: the class, the kind, the message, the exit code. Read more in the glossary on stdout: a failure there shows only as the stderr message and the exit code.

Synopsis

relate --agent-mode [OPTIONS] [QUERY]
relate --no-agent-mode [OPTIONS] [QUERY]
RELATE_AGENT_MODE=1 relate [OPTIONS] [QUERY]
RELATE_AGENT_MODE=0 relate [OPTIONS] [QUERY]
SettingAgent mode offAgent mode on
Output mode, stdout piped or redirected, no output flagjsonltoon
Output mode, stdout a terminal, no output flagtabletable
Rows printed per statement, no --max-rows or RELATE_MAX_ROWSunlimitedat most 100; the cut is reported on stderr and in-band
An explicit --mode or --max-rowsappliesapplies
Error object on stdout, stdout piped, no output flagprinted (jsonl)not printed: toon prints none; the stderr message and the exit code remain
Writes, the envelope's fields, the error object's fields, exit codesunchangedunchanged

What turns it on

The CLI decides once per run, from the first rule that applies:

OrderRuleApplies on a terminalAnnounced on stderr
1--agent-mode turns it on, --no-agent-mode off; of the two, the last one given winsyesyes, when on
2RELATE_AGENT_MODE=1 turns it on, RELATE_AGENT_MODE=0 off, with surrounding whitespace ignored; an empty value counts as unset; any other value exits 2yesyes, when on
3Detection: on when an agent harness is detected and stdout is not a terminalnono

The two announcements, one line on stderr before any output:

relate agent mode on: --agent-mode (--no-agent-mode to turn off)
relate agent mode on: RELATE_AGENT_MODE=1 (--no-agent-mode to turn off)

Detection is silent, so a harness that runs one statement per process sees no banner. On a terminal, an explicit on-state applies the row cap, and the output mode stays table.

Detection

An agent harness is detected when any of these environment variables is set to a non-empty value. The value itself does not matter: AI_AGENT=0 counts, and an empty value does not.

Variables
AI_AGENT, AGENT
CLAUDECODE, CLAUDE_CODE
COPILOT_AGENT, COPILOT_CLI, COPILOT_AGENT_SESSION_ID
CODEX_SANDBOX, CODEX_THREAD_ID
CURSOR_AGENT, GEMINI_CLI, OPENCODE, AUGMENT_AGENT, GOOSE_PROVIDER, REPL_ID

Three patterns also count, each matched case-insensitively:

VariableContains
EDITORdevin
TERM_PROGRAMkiro
PATH.pi/agent

Forcing it on or off

Under a detected harness with stdout piped, the output mode is toon, with no announcement:

CLAUDECODE=1 relate "UNWIND [1, 2] AS x RETURN x"
rows[2]{x}:
  1
  2
timings:
  {"elapsed_ms":0.378,"execute_ms":0.220834,"lex_parse_ms":0.031875,"lower_ms":0.031792,"parse_ms":0.1065,"plan_ms":0.041665999999999995,"result_generation_ms":0.000167,"rewrite_ms":0.0067079999999999996}
planCache:
  miss

--no-agent-mode beats detection, so the piped default is jsonl again:

CLAUDECODE=1 relate --no-agent-mode "UNWIND [1, 2] AS x RETURN x"
{"x":1}
{"x":2}
{"planCache":"miss","timings":{"elapsed_ms":0.354916,"execute_ms":0.202834,"lex_parse_ms":0.022667000000000003,"lower_ms":0.030125,"parse_ms":0.095875,"plan_ms":0.043792,"result_generation_ms":0.00025,"rewrite_ms":0.006792}}

An explicit --mode beats agent mode's default without turning agent mode off:

CLAUDECODE=1 relate --mode jsonl "UNWIND [1, 2] AS x RETURN x"
{"x":1}
{"x":2}
{"planCache":"miss","timings":{"elapsed_ms":0.456708,"execute_ms":0.310916,"lex_parse_ms":0.022,"lower_ms":0.030458,"parse_ms":0.095333,"plan_ms":0.040917,"result_generation_ms":0.00029099999999999997,"rewrite_ms":0.006667}}

RELATE_AGENT_MODE=1 turns it on without a harness, and says so on stderr:

RELATE_AGENT_MODE=1 relate "UNWIND [1, 2] AS x RETURN x"
relate agent mode on: RELATE_AGENT_MODE=1 (--no-agent-mode to turn off)
rows[2]{x}:
  1
  2
timings:
  {"elapsed_ms":0.47974999999999995,"execute_ms":0.335833,"lex_parse_ms":0.023125,"lower_ms":0.032541,"parse_ms":0.095875,"plan_ms":0.038583,"result_generation_ms":0.00020800000000000001,"rewrite_ms":0.005667}
planCache:
  miss

A value other than 1 or 0 is a usage error, before any statement runs:

RELATE_AGENT_MODE=yes relate "RETURN 1 AS x"
echo "exit=$?"
Error: invalid RELATE_AGENT_MODE: 'yes' is neither 1 nor 0
exit=2

The row cap

With agent mode on and no --max-rows or RELATE_MAX_ROWS, a statement prints at most 100 rows. The cap limits what is printed, not what runs: the engine computes the whole result first, so a Cypher LIMIT is what bounds the work. --max-rows N sets the cap in any run, on a terminal too, and --max-rows 0 prints every row.

A cut result still exits 0. The CLI writes one line to stderr:

warning: result truncated to 100 rows (the statement returned 250); pass --max-rows 0 for all rows

The machine modes also carry the cut in-band, after the rows or inside the envelope, so a capped result never reads as a complete one:

ModeThe in-band record
envelope"truncated":true and "rowCap":{"kept":100,"total":250}
jsonlA {"rowCap":{"kept":100,"total":250}} line after the rows, before the statement's timing line
jsonA rowCap key beside data: {"data":[…],"planCache":…,"rowCap":{"kept":100,"total":250},"timings":{…}}
toonA rowCap: entry after the rows, the counts as inline JSON, before the timings: and planCache: entries
markdownA footer line > truncated: 100 of 250 rows shown
table, csv, cypherNone; the stderr line is the only report

Each statement of a multi-statement run is capped and reported on its own. Under a detected harness, the toon header names the rows kept, and the rowCap: entry after the last row names both counts. The timings: and planCache: entries close the block; the output modes page describes them, and their digits change from run to run:

CLAUDECODE=1 relate "UNWIND range(1, 250) AS x RETURN x" 2>/dev/null | head -2
CLAUDECODE=1 relate "UNWIND range(1, 250) AS x RETURN x" 2>/dev/null | tail -7
rows[100]{x}:
  1
  100
rowCap:
  {"kept":100,"total":250}
timings:
  {"elapsed_ms":0.44983399999999996,"execute_ms":0.30124999999999996,"lex_parse_ms":0.024457999999999997,"lower_ms":0.033833,"parse_ms":0.093125,"plan_ms":0.047667,"result_generation_ms":0.001209,"rewrite_ms":0.007833}
planCache:
  miss

The envelope

--mode envelope prints one compact JSON object per result set, on one line, so a multi-statement run is JSON Lines of envelope One compact JSON object per result set, printed by --mode envelope, that keeps the column list even when there are no rows. Read more in the glossary. Agent mode changes nothing in it except the row cap's two fields. Keys are sorted alphabetically, at the top level and inside each row; columns gives the column order.

relate --mode envelope "RETURN 1 AS a, [1, 2] AS l, {k: 'v'} AS m, null AS z"
{"columnTypes":["INTEGER","LIST","MAP","ANY"],"columns":["a","l","m","z"],"notifications":[],"planCache":"miss","rows":[{"a":1,"l":[1,2],"m":{"k":"v"},"z":null}],"timings":{"elapsed_ms":0.3515,"execute_ms":0.198667,"lex_parse_ms":0.02425,"lower_ms":0.030792,"parse_ms":0.1025,"plan_ms":0.040958,"result_generation_ms":0.000334,"rewrite_ms":0.007084},"truncated":false}
FieldTypePresentMeaning
columnTypesarray of stringsalwaysOne type name per column, from the first row: STRING, INTEGER, FLOAT, BOOLEAN, NODE, RELATIONSHIP, PATH, LIST, MAP, POINT, DATETIME, DATE, TIME, DURATION, VECTOR, UUID, BINARY or ANY. A null first value, or no rows, gives ANY.
columnsarray of stringsalwaysThe result columns in order, even when there are no rows
notificationsarray of objectsalwaysThe statement's notifications; [] on a plain run
planobjectEXPLAIN and PROFILE onlyThe plan tree
planCachestringalwaysThe plan-cache outcome: miss, hit-raw-fastpath, hit-template, replan-generation-mismatch or bypassed
rowCapobjectwhen the row cap cut the rows{"kept":N,"total":M}: the rows printed and the rows the statement returned
rowsarray of objectsalwaysOne object per row, values as --json prints them
timingsobjectalwaysThe statement's phase timings in milliseconds: elapsed_ms, execute_ms, lex_parse_ms, lower_ms, parse_ms, plan_ms, result_generation_ms and rewrite_ms; the values change from run to run
truncatedbooleanalwaystrue when the row cap cut the rows

The output modes page defines each timing key and each planCache value.

An empty result keeps its columns, so no rows is told apart from no output:

relate --mode envelope "MATCH (p:Person) RETURN p"
{"columnTypes":["ANY"],"columns":["p"],"notifications":[],"planCache":"miss","rows":[],"timings":{"elapsed_ms":0.382083,"execute_ms":0.227458,"lex_parse_ms":0.027834,"lower_ms":0.04075,"parse_ms":0.098458,"plan_ms":0.048708,"result_generation_ms":0.000292,"rewrite_ms":0.007500000000000001},"truncated":false}

A cut result has rowCap and "truncated":true. In this run --max-rows 2 sets the cap; agent mode's default cut looks the same with "kept":100:

relate --mode envelope --max-rows 2 "UNWIND range(1, 5) AS x RETURN x"
warning: result truncated to 2 rows (the statement returned 5); pass --max-rows 0 for all rows
{"columnTypes":["INTEGER"],"columns":["x"],"notifications":[],"planCache":"miss","rowCap":{"kept":2,"total":5},"rows":[{"x":1},{"x":2}],"timings":{"elapsed_ms":0.34175,"execute_ms":0.20358400000000001,"lex_parse_ms":0.021709,"lower_ms":0.032666,"parse_ms":0.08629200000000001,"plan_ms":0.044333,"result_generation_ms":0.0011250000000000001,"rewrite_ms":0.007042},"truncated":true}

A failed statement in envelope, json, jsonl or speedscope prints the error object instead, with the same keys in each; its fields are on the exit codes page. New envelope fields may be added; existing ones are never renamed.

toon prints no error object. Under a detected harness with stdout piped, a failed statement leaves the stderr message and the exit code, and nothing on stdout:

CLAUDECODE=1 relate "RETURN 1/0 AS x"
echo "exit=$?"
type error: / by zero
exit=6

With --mode envelope or --mode jsonl, the same run prints the error object on stdout, agent mode on or off.

Notifications

A plain run produces no notifications. EXPLAIN and PROFILE derive them beside the plan: an unknown label or property key, a missing index, a pattern with no join anchor. Agent mode does not change them; the output mode decides where they go. Under agent mode's piped toon default, they print as a notifications: entry after plan:, one inline JSON array, before the timings: and planCache: entries:

CLAUDECODE=1 relate "EXPLAIN MATCH (p:Persn) RETURN p.name AS name" | tail -6 | head -2
notifications:
  [{"category":"UNRECOGNIZED","code":"Neo.ClientNotification.Statement.UnknownLabelWarning","description":"The label `Persn` does not exist in this database.","gql_status":"01N50","remedy":"Check the spelling; the label exists after the first write that creates it, and no index or constraint uses the unknown name.","severity":"WARNING","title":"Unknown label"},{"category":"UNRECOGNIZED","code":"Neo.ClientNotification.Statement.UnknownPropertyKeyWarning","description":"The property key `name` does not exist in this database.","gql_status":"01N52","remedy":"Check the spelling; the key exists after the first write that sets it.","severity":"WARNING","title":"Unknown property key"}]

Each entry has category, code, description, gql_status, remedy, severity and title, plus operator_id when it concerns one plan operator. The envelope mode carries the same list in notifications; json prints one {"notifications":[…],"plan":{…},"planCache":…,"timings":{…}} object; jsonl prints one {"notifications":[…],"plan":{…}} line, then the timing line; table and markdown print a Notifications: block; csv and cypher print none. The errors reference lists every notification.

The agent-context briefing

relate agent-context prints a compact text briefing of the installed binary: its version, one line per command, the query-run flag names, and the exit code table. relate agent-context <command> prints one command's usage line, its flags, an example, and the exit code table. The output is plain text in every run, agent mode or not, and it comes from the same command table as --help, so it describes the binary that prints it.

relate agent-context clone
clone — Copy a database into a new store and exit
Usage: relate clone [--wal-dir DIR] [--to-snapshot-format F] [--markdown-body-properties LIST] SOURCE DEST
Flags:
  --wal-dir
  --to-snapshot-format
  --markdown-body-properties
Examples:
  # Copy a database into a new store
  relate clone ./mydata ./backup
Exit codes:
  0  success: success, including an empty result
  1  fatal_error: runtime failure: store open or I/O error, capacity limit, query execution failure, internal error
  2  usage_error: unknown flag, bad or missing value, extra positional
  5  conflict: write-write conflict, or the store is locked by another opener
  6  validation_error: the statement is rejected: syntax, semantic or type error
  7  timeout: a statement exceeded --timeout

An unknown command word exits 2 with Error: agent-context: unknown command '<name>', followed by the list of commands it accepts. The briefing with no command word is recorded in the agent quickstart.

What agent mode leaves alone

Agent mode changes output defaults only. Writes go through as in any other run; the opt-ins that refuse statements are separate flags: --read-only opens the store read-only An open mode (--read-only) that never writes, checkpoints or migrates the store; writes fail, and a directory store can be read beside its writer. Read more in the glossary, and --safe refuses an unbounded MATCH read and a new named node without a label. Neither turns on by detection. Every exit code The number a relate run returns to the shell; each nonzero value names a class of failure, such as 2 for usage, 5 for conflict, 6 for a rejected statement. Read more in the glossary and the error object's fields are the same with agent mode on or off. toon, agent mode's piped default, prints no error object on stdout; --mode envelope or --mode jsonl prints it.

See also