machinemode.io
Examples · Companion to AOI‑CLI v1.0-alpha · 2026-07-30

Machine Mode on the wire

Fourteen scenarios showing exactly what a Machine Mode tool writes to stdout, one JSON object per line.

Machine Mode is an open standard for how a command-line tool behaves when an AI agent runs it. Each scenario below runs one command, shows the stream it returns and notes what a consumer can conclude from it.

The tool is called outline throughout. It and its events are illustrative (hence the com.example schema names), written to show what a conforming implementation would emit.

Every conforming tool plays one of three pipeline roles: a source emits JSONL, a transformer reads JSONL and emits JSONL, and a sink reads JSONL and performs side effects. Most scenarios show sources; § 09 and § 13 add a transformer, and § 14 shows a sink. The shell tools you already use (jq, tee, set -o pipefail) handle JSONL as it is, so there's no new toolchain.

One stream, as it arrives

A search with a limit of 3, revealed at the pace a consumer reads it. The fourteen scenarios after it are static, so you can scan them.

$ outline search "agent operable" --output jsonl --limit 3
{"type":"aoi:meta","aoi":{"seq":0,"run":"01JZQ7X4TQ9"},"tool":"outline","tool_version":"1.8.2","aoi_version":"1.0-alpha","schema_name":"com.example.outline.events","schema_version":"1.0.0","command":"search"}
{"type":"hit","aoi":{"seq":1,"run":"01JZQ7X4TQ9"},"rank":1,"id":"doc_91","title":"Agent-operable interfaces","snippet":"A conforming tool exposes a stable interface…"}
{"type":"hit","aoi":{"seq":2,"run":"01JZQ7X4TQ9"},"rank":2,"id":"doc_177","title":"Why machine mode","snippet":"Today's tools were built for humans…"}
{"type":"hit","aoi":{"seq":3,"run":"01JZQ7X4TQ9"},"rank":3,"id":"doc_2041","title":"Adopt AOI-CLI","snippet":"The conformance contract is small…"}
{"type":"aoi:summary","aoi":{"seq":4,"run":"01JZQ7X4TQ9"},"ok":true,"count":3,"truncated":true,"next_cursor":"opaque-token-abc123","elapsed_ms":612}
Live · search with a limit and a cursorstreamed as the tool would emit it
§ 01

Discover what a tool can do

A consumer can learn what a tool exposes without running a real operation, without credentials and without network access. The schema and capabilities commands each return one JSON object, because this is discovery metadata rather than a JSONL stream.

$ outline schema --output json --command search
{"$schema":"https://json-schema.org/draft/2020-12/schema","title":"Outline search event","type":"object","required":["type"],"properties":{"type":{"enum":["aoi:meta","hit","aoi:warning","aoi:error","aoi:summary"]}}}
 
$ outline capabilities --output json
{"tool":"outline","tool_version":"1.8.2","aoi_versions":["1.0-alpha"],"schemas":[{"name":"com.example.outline.events","versions":["1.0.0"],"default":"1.0.0"}],"commands":[{"name":"search","read_only":true,"bounded":true,"supports_cursor":true,"event_types":["aoi:meta","hit","aoi:summary"]},{"name":"delete","read_only":false,"destructive":true,"requires_confirm":true,"supports_dry_run":true,"supports_idempotency_key":true}]}
Schema discovery · returns a single JSON object
  • Neither command needs credentials, network access or a real query.
  • capabilities is one JSON object, so a consumer can parse it in a single read.
  • The destructive flag on delete tells a planner that this command needs extra ceremony.
§ 02

Bounded, streamed search

Every read states how much it wants, and results arrive as the tool produces them. When more exists, the terminal summary says so and hands back a cursor for the next page.

$ outline search "agent operable" --output jsonl --limit 50
{"type":"aoi:meta","aoi":{"seq":0,"run":"01JZQ7X4TQ9"},"tool":"outline","tool_version":"1.8.2","aoi_version":"1.0-alpha","schema_name":"com.example.outline.events","schema_version":"1.0.0","command":"search"}
{"type":"hit","aoi":{"seq":1,"run":"01JZQ7X4TQ9"},"rank":1,"id":"doc_91","title":"Agent-operable interfaces","snippet":"A conforming tool exposes…"}
{"type":"hit","aoi":{"seq":2,"run":"01JZQ7X4TQ9"},"rank":2,"id":"doc_177","title":"Why machine mode","snippet":"Today's tools were built for humans…"}
…
{"type":"hit","aoi":{"seq":50,"run":"01JZQ7X4TQ9"},"rank":50,"id":"doc_2041","title":"Adopt AOI-CLI","snippet":"The conformance contract is small…"}
{"type":"aoi:summary","aoi":{"seq":51,"run":"01JZQ7X4TQ9"},"ok":true,"count":50,"truncated":true,"next_cursor":"opaque-token-abc123","elapsed_ms":612}
Bounded streaming search · exit 0, truncated, with a cursor
  • The consumer chose the limit and the tool honored it.
  • truncated: true means more results exist beyond this page.
  • next_cursor is opaque; its format belongs to the tool.
§ 03

Continue from a cursor

Pagination has one shape in every conforming tool instead of an ad hoc scheme per command. The cursor from one call goes back as --cursor on the next, and the consumer holds the state.

$ outline search "agent operable" --output jsonl --limit 50 --cursor opaque-token-abc123
{"type":"aoi:meta","aoi":{"seq":0,"run":"01JZQ8B2M1K"},"tool":"outline","aoi_version":"1.0-alpha","schema_name":"com.example.outline.events","schema_version":"1.0.0","command":"search"}
{"type":"hit","aoi":{"seq":1,"run":"01JZQ8B2M1K"},"rank":51,"id":"doc_2110","title":"…"}
…
{"type":"hit","aoi":{"seq":33,"run":"01JZQ8B2M1K"},"rank":83,"id":"doc_3441","title":"…"}
{"type":"aoi:summary","aoi":{"seq":34,"run":"01JZQ8B2M1K"},"ok":true,"count":33,"truncated":false,"next_cursor":null,"elapsed_ms":410}
Following the cursor returned in § 02
  • truncated: false with next_cursor: null marks the end of the result set.
  • A consumer never has to invent its own pagination.
§ 04

Dry-run before any write

Before a destructive operation runs, --dry-run emits one aoi:plan event for each side effect the real run would make, and makes none of them.

$ outline delete --where "stale=true" --dry-run --output jsonl
{"type":"aoi:meta","aoi":{"seq":0,"run":"01JZQ7X4TQ9"},"tool":"outline","command":"delete","properties":{"dry_run":true}}
{"type":"aoi:plan","aoi":{"seq":1,"run":"01JZQ7X4TQ9"},"action":"delete","target":"doc_44","title":"Old onboarding draft"}
{"type":"aoi:plan","aoi":{"seq":2,"run":"01JZQ7X4TQ9"},"action":"delete","target":"doc_91","title":"2024-Q4 plan archive"}
{"type":"aoi:plan","aoi":{"seq":3,"run":"01JZQ7X4TQ9"},"action":"delete","target":"doc_133","title":"Deprecated docs index"}
{"type":"aoi:summary","aoi":{"seq":4,"run":"01JZQ7X4TQ9"},"ok":true,"properties":{"would_affect":3,"executed":false}}
A delete that would touch three documents, but doesn't
  • properties.executed: false makes the no-op explicit, so a wrapper can assert on it.
  • Plan events are typed, just as the real apply events would be.
§ 05

Confirmed destructive apply

The real run requires --confirm. Bulk operations should also take --confirm-count N, which guards against a bigger blast radius than the caller expected, and every side effect that does happen produces a structured record.

$ outline delete --where "stale=true" --output jsonl --confirm --confirm-count 3
{"type":"aoi:meta","aoi":{"seq":0,"run":"01JZQ8C6N4D"},"tool":"outline","command":"delete"}
{"type":"deleted","aoi":{"seq":1,"run":"01JZQ8C6N4D"},"target":"doc_44","at":"2026-07-30T17:42:11Z","actor":"kevin"}
{"type":"deleted","aoi":{"seq":2,"run":"01JZQ8C6N4D"},"target":"doc_91","at":"2026-07-30T17:42:11Z","actor":"kevin"}
{"type":"deleted","aoi":{"seq":3,"run":"01JZQ8C6N4D"},"target":"doc_133","at":"2026-07-30T17:42:11Z","actor":"kevin"}
{"type":"aoi:summary","aoi":{"seq":4,"run":"01JZQ8C6N4D"},"ok":true,"count":3,"elapsed_ms":204,"properties":{"executed":true}}
Same query as § 04, now committed · the count guard matches
  • If --confirm-count had been 4, the tool would have refused without touching anything.
  • deleted events carry stable IDs, so a downstream audit log can ingest them as they are.
§ 06

Idempotent writes

Send the same write twice and you get one outcome, with no duplicate. The idempotency key lets the tool recognize a retry and return the prior result instead of applying the effect again.

$ outline create-doc --title "Release notes" --idempotency-key rel-2026-05 --output jsonl
{"type":"aoi:meta","aoi":{"seq":0,"run":"01JZQ7X4TQ9"},"command":"create-doc"}
{"type":"created","aoi":{"seq":1,"run":"01JZQ7X4TQ9"},"id":"doc_9001","title":"Release notes","idempotency_key":"rel-2026-05","duplicate":false}
{"type":"aoi:summary","aoi":{"seq":2,"run":"01JZQ7X4TQ9"},"ok":true,"count":1,"properties":{"executed":true}}
 
$ outline create-doc --title "Release notes" --idempotency-key rel-2026-05 --output jsonl
{"type":"aoi:meta","aoi":{"seq":0,"run":"01JZQ8D7P3V"},"command":"create-doc"}
{"type":"created","aoi":{"seq":1,"run":"01JZQ8D7P3V"},"id":"doc_9001","title":"Release notes","idempotency_key":"rel-2026-05","duplicate":true}
{"type":"aoi:summary","aoi":{"seq":2,"run":"01JZQ8D7P3V"},"ok":true,"count":1,"properties":{"executed":false}}
Two invocations with the same key · the second is a safe replay
  • Both runs return the same id and exit 0.
  • duplicate: true on the second run is the explicit signal, so the consumer reads a field instead of guessing from elapsed time.
§ 07

Doctor: readiness as a stream

A tool reports its own health (config, credentials, dependencies and connectivity) as typed aoi:check events, and the summary rolls them up.

$ outline doctor --output jsonl
{"type":"aoi:meta","aoi":{"seq":0,"run":"01JZQ7X4TQ9"},"tool":"outline","command":"doctor"}
{"type":"aoi:check","aoi":{"seq":1,"run":"01JZQ7X4TQ9"},"name":"config_file","ok":true,"severity":"info","detail":"~/.outline.toml"}
{"type":"aoi:check","aoi":{"seq":2,"run":"01JZQ7X4TQ9"},"name":"api_token","ok":true,"severity":"info","detail":"sourced from OUTLINE_TOKEN"}
{"type":"aoi:check","aoi":{"seq":3,"run":"01JZQ7X4TQ9"},"name":"endpoint_reachable","ok":false,"severity":"error","detail":"connect ETIMEDOUT api.outline.example"}
{"type":"aoi:summary","aoi":{"seq":4,"run":"01JZQ7X4TQ9"},"ok":false,"reason":"failed","count":0,"error_count":0,"properties":{"checks_failed":1}}
Multiple typed checks · the process exits non-zero when any check fails
  • Every check has the same shape, which makes it easy for a monitoring agent to roll up.
  • aoi:summary.ok=false plus a non-zero exit makes the failure unambiguous.
§ 08

Watch: an unbounded stream

Some operations have no natural end. The tool documents that the stream is unbounded and may emit heartbeats so a consumer can tell a quiet stream from a dead one; it writes a summary only when it's cancelled.

$ outline watch --output jsonl
{"type":"aoi:meta","aoi":{"seq":0,"run":"01JZQ7X4TQ9"},"tool":"outline","command":"watch","properties":{"unbounded":true}}
{"type":"entry","aoi":{"seq":1,"run":"01JZQ7X4TQ9"},"change":"created","id":"doc_42","at":"2026-07-30T17:48:10Z"}
{"type":"entry","aoi":{"seq":2,"run":"01JZQ7X4TQ9"},"change":"updated","id":"doc_42","at":"2026-07-30T17:49:23Z"}
{"type":"aoi:heartbeat","aoi":{"seq":3,"run":"01JZQ7X4TQ9"},"at":"2026-07-30T17:50:00Z"}
{"type":"aoi:heartbeat","aoi":{"seq":4,"run":"01JZQ7X4TQ9"},"at":"2026-07-30T17:51:00Z"}
{"type":"entry","aoi":{"seq":5,"run":"01JZQ7X4TQ9"},"change":"deleted","id":"doc_19","at":"2026-07-30T17:51:42Z"}
⌃C
{"type":"aoi:summary","aoi":{"seq":6,"run":"01JZQ7X4TQ9"},"ok":false,"reason":"cancelled","count":3}
Subscription stream with periodic heartbeats
  • properties.unbounded: true on meta tells the consumer not to wait for natural completion.
  • Heartbeats let a supervisor tell a quiet stream from a dead one without relying on TCP keepalive.
  • A graceful interrupt still produces a structured summary, with reason: "cancelled".
§ 09

Compose tools in a pipeline

The output of one Machine Mode tool is the input of the next. A transformer consumes the upstream framework events and emits its own, so the end of the pipe sees one meta and one summary however many stages the chain has.

$ outline search "agent" --output jsonl --limit 20 \
$ | summarize --input-jsonl - --output jsonl \
$ | tee run.jsonl \
$ | jq -e 'select(.type=="aoi:summary") | .ok == true'
{"type":"aoi:meta","aoi":{"seq":0,"run":"01JZQ7X4TQ9"},"tool":"summarize","tool_version":"0.4.1","aoi_version":"1.0-alpha","schema_name":"com.example.summarize.events","schema_version":"1.0.0","command":"summarize"}
{"type":"abstract","aoi":{"seq":1,"run":"01JZQ7X4TQ9"},"source_id":"doc_91","text":"A typed interface…"}
…
{"type":"aoi:summary","aoi":{"seq":21,"run":"01JZQ7X4TQ9"},"ok":true,"event_count":22,"count":20,"input_count":20,"warning_count":0,"error_count":0,"upstream":{"tool":"outline","tool_version":"1.8.2","schema_name":"com.example.outline.events","schema_version":"1.0.0"}}
Search → summarize → terminal · pipefail keeps the whole chain honest
  • Exactly one aoi:meta and one aoi:summary reach the end of the pipe, and both are the transformer's. Per § 13.1, an AOI producer never forwards upstream aoi:meta or aoi:summary.
  • Upstream identity rides on aoi:summary.upstream: one object, direct upstream only. It can't live on aoi:meta, which has to go out before the transformer has read the upstream's own meta.
  • That invariant keeps EOF-without-summary a reliable crash signal, because a summary in the stream always means the last stage finished.
  • jq -e on the terminal summary turns Machine Mode into a shell-level boolean, and with set -o pipefail an upstream failure also fails the pipeline's exit status.
§ 10

Negotiate a schema version

One binary can ship more than one version of its event schema. Each consumer pins the version it supports, and the tool honors the request and reports which version it used.

$ outline search "agent" --output jsonl --schema-version 1 --limit 1
{"type":"aoi:meta","aoi":{"seq":0,"run":"01JZQ7X4TQ9"},"tool":"outline","schema_name":"com.example.outline.events","schema_version":"1.0.0"}
{"type":"hit","aoi":{"seq":1,"run":"01JZQ7X4TQ9"},"rank":1,"id":"doc_91","title":"Agent-operable"}
{"type":"aoi:summary","aoi":{"seq":2,"run":"01JZQ7X4TQ9"},"ok":true,"count":1}
 
$ outline search "agent" --output jsonl --schema-version 2 --limit 1
{"type":"aoi:meta","aoi":{"seq":0,"run":"01JZQ8F9H6N"},"tool":"outline","schema_name":"com.example.outline.events","schema_version":"2.0.0"}
{"type":"hit","aoi":{"seq":1,"run":"01JZQ8F9H6N"},"rank":1,"document":{"id":"doc_91","title":"Agent-operable","mime":"text/markdown"}}
{"type":"aoi:summary","aoi":{"seq":2,"run":"01JZQ8F9H6N"},"ok":true,"count":1}
Two consumers, same tool, different schemas, both valid
  • v2 nested the result in a document object. That's a breaking shape change, so it got a major bump.
  • A consumer built on v1 keeps working; one that wants v2 opts in explicitly.
§ 11

Errors carry their own retry semantics

An aoi:error carries category and retryable as portable fields, so a consumer can write its retry logic once and use it with every conforming tool, without learning each tool's own codes.

$ outline create-doc --title "…" --output jsonl
{"type":"aoi:meta","aoi":{"seq":0,"run":"01JZQ7X4TQ9"},"command":"create-doc"}
{"type":"aoi:error","aoi":{"seq":1,"run":"01JZQ7X4TQ9"},"code":"RATE_LIMITED","category":"rate_limited","message":"60 writes per minute exceeded","retryable":true,"retry_after_ms":7200}
{"type":"aoi:summary","aoi":{"seq":2,"run":"01JZQ7X4TQ9"},"ok":false,"reason":"failed","error_count":1,"properties":{"executed":false}}
Rate-limit error with a hint · retry is a contract, not a guess
  • category: rate_limited comes from the standard taxonomy, so a consumer can route the error without reading the tool's docs; retryable decides whether to retry at all.
  • retry_after_ms is a minimum delay, not a schedule. The consumer waits at least that long, adds jitter and applies its own attempt limit.
§ 12

The terminal summary is the success signal

Exit 0 means the process finished. Success means the work completed and was reported, and without a terminal summary, EOF is the cross-language crash signal even when the exit code is 0.

# Healthy run: exit 0, summary present, ok=true
{"type":"aoi:meta","aoi":{"seq":0,"run":"01JZQ7X4TQ9"},"command":"search"}
{"type":"hit","aoi":{"seq":1,"run":"01JZQ7X4TQ9"},"rank":1,"id":"doc_1"}
{"type":"aoi:summary","aoi":{"seq":2,"run":"01JZQ7X4TQ9"},"ok":true,"count":1}
$ echo $? # 0
 
# Crash mid-stream: exit 0 anyway, because a top-level handler swallowed the exception
{"type":"aoi:meta","aoi":{"seq":0,"run":"01JZQ8H3K8T"},"command":"search"}
{"type":"hit","aoi":{"seq":1,"run":"01JZQ8H3K8T"},"rank":1,"id":"doc_1"}
# <EOF, no summary, exit 0>
$ echo $? # 0, but the consumer MUST treat this as failure
Same exit code, different truths · only the summary is authoritative
  • A consumer that trusts the exit code alone will accept the truncated stream as success without noticing.
  • The terminal summary is the only honest success signal, and its absence is the only honest crash signal.
§ 13

A transformer in isolation

A transformer reads upstream AOI events on stdin, dispatches on event.type and emits its own typed events. The same envelope rules apply: aoi:meta opens the stream, aoi:summary ends it, and domain events get transformed in between. Upstream aoi:meta and aoi:summary are consumed and never forwarded (§ 13.1), so the stream at the end of the pipe still has exactly one of each.

$ outline search "agent operable" --output jsonl --limit 3 | summarize --input-jsonl - --output jsonl --max-tokens 60
{"type":"aoi:meta","aoi":{"seq":0,"run":"01JZQ7X4TQ9"},"tool":"summarize","tool_version":"0.4.1","aoi_version":"1.0-alpha","schema_name":"com.example.summarize.events","schema_version":"1.0.0","command":"summarize","properties":{"input_schemas":["com.example.outline.events@1.0.0"]}}
{"type":"abstract","aoi":{"seq":1,"run":"01JZQ7X4TQ9"},"source_id":"doc_91","text":"A typed interface a non-human can rely on — discoverable, bounded, safe."}
{"type":"abstract","aoi":{"seq":2,"run":"01JZQ7X4TQ9"},"source_id":"doc_177","text":"Today's tools were built for humans; agents need something safer than scraped terminal text."}
{"type":"abstract","aoi":{"seq":3,"run":"01JZQ7X4TQ9"},"source_id":"doc_2041","text":"Conformance is small: emit JSONL, expose a schema, terminate on a summary."}
{"type":"aoi:summary","aoi":{"seq":4,"run":"01JZQ7X4TQ9"},"ok":true,"count":3,"input_count":3,"warning_count":0,"error_count":0,"elapsed_ms":380}
summarize reads hit events from an upstream source and emits abstract events
  • properties.input_schemas in meta declares which upstream shapes the transformer accepts, so a downstream tool can check that the chain is type-compatible.
  • source_id preserves the upstream identity, so a consumer can match transformed events back to the originals.
  • Unknown unprefixed upstream event types are always safe to ignore. An unknown aoi: type may be ignored too, but then the transformer can't report success (§ 13).
  • An upstream aoi:error sets aoi:summary.ok=false unless --continue-on-error is passed, and its retry fields are copied into aoi:summary.caused_by so retry_after_ms survives the pipe.

A sink reads JSONL on stdin and performs side effects. Most of its output is the audit trail of what it created, updated or sent, wrapped in the meta and summary events every conforming tool emits, and it can sit at the end of an AOI pipeline.

$ cat exports.jsonl | outline import --input-jsonl - --output jsonl --confirm
{"type":"aoi:meta","aoi":{"seq":0,"run":"01JZQ7X4TQ9"},"tool":"outline","command":"import","properties":{"input_mode":"jsonl","input_schemas":["com.example.export.events@1.0.0"]}}
{"type":"created","aoi":{"seq":1,"run":"01JZQ7X4TQ9"},"target":"doc_9001","title":"Q4 retrospective","source_id":"export_44","at":"2026-07-30T18:11:02Z"}
{"type":"created","aoi":{"seq":2,"run":"01JZQ7X4TQ9"},"target":"doc_9002","title":"Postmortem template","source_id":"export_45","at":"2026-07-30T18:11:03Z"}
{"type":"aoi:error","aoi":{"seq":3,"run":"01JZQ7X4TQ9"},"scope":"item","category":"validation","code":"INPUT_JSONL_PARSE_ERROR","line_number":7,"message":"Invalid JSON on input line 7","retryable":false}
{"type":"aoi:summary","aoi":{"seq":4,"run":"01JZQ7X4TQ9"},"ok":false,"reason":"failed","count":2,"input_count":3,"error_count":1,"partial":true,"elapsed_ms":612,"properties":{"executed":true}}
outline import reads entry events, creates documents and audits each one
  • A sink still emits meta and summary; the AOI envelope is the same for every role.
  • created events carry the new resource ID and the source_id from the upstream input, so the consumer can build a forward-mapping audit log.
  • The malformed line produces an item-level error (scope: "item", pointing at its line_number) and the run carries on, which is the per-line tolerant mode from § 12.2. A tool that works this way has to document it, and for mutating imports the spec recommends failing fast instead.
  • partial: true together with properties.executed: true says the work happened and some inputs failed.

Build a tool that does this

The conformance contract is small: emit JSONL, expose a schema, end on a summary, handle pipes cleanly and redact secrets. The full checklist is in the specification.