Skip to main content
The moda CLI is built to be called by coding agents and CI jobs with zero configuration: when stdout is piped, CI is set, or an agent environment is detected (CLAUDECODE, CURSOR, MODA_AGENT), every command emits schema-versioned JSON and signals its state through exit codes. This page documents the machine protocol and gives complete, runnable recipes.

Agent mode

No flags are needed in automation — detection order is explicit flags → MODA_FORMAT → agent environment → TTY (see Output formats). To force it anywhere, set MODA_AGENT=1 or pass --agent. Three output contracts matter to a machine caller: Three commands always bypass envelopes so their stdout can be captured directly: moda provision (one JSON credentials document), moda auth token (the bare token), and moda tail (typed NDJSON records such as {"type":"conversation",...}; emotions polls also emit a tail_coverage metadata record {complete, scanned, total, coverage_pct} because the emotions endpoint is score-ranked with no cursor, and a one-time tail_dedupe_evicted record if dedupe memory rolls over. --max-events bounds every stdout record including metadata; a reserved slot keeps the coverage record in-stream, and if the bound leaves no room it is written to stderr rather than dropped). Discover the full protocol at runtime:

The moda.agent.v1 envelope

string
Always "moda.agent.v1".
string
The command that produced the envelope.
string
ok, degraded (answer produced from a lower-trust fallback), or error.
string
ISO 8601 timestamp.
string
Active profile name, when one is set.
object
{ text, confidence } — one-line result summary; confidence is high | medium | low | unknown.
array
Ranked findings, each { id, title, status?, severity?, description?, evidence_ref_ids? }.
array
Evidence behind the findings: { id, kind, label, path?, line?, href? }. href is a root-relative dashboard link — prefix the Moda base URL to open it.
Suggested follow-ups: { id, title, rationale?, command?, mutability, requires_approval }.
array
Commands the agent can run next: { command, purpose, mutability, requires_approval }. mutability is read | write | sync | unknown — treat anything other than read as needing user approval when requires_approval is true.
array
Files the command created or verified: { kind, path, description? }.
array
Non-fatal warnings (strings).
array
Structured errors: { code, message, cause?, suggested_commands?, input_request?, auth?, blocking }. Codes include input_error, api_error, network_error, auth_required, input_required, cancelled, command_error, access_denied, cloud_unavailable, unknown_error.
object
The command payload — the same JSON --json would print.
object
run_id plus a tip string on successful envelopes.
object
Present when data was bounded to fit MODA_AGENT_MAX_OUTPUT_BYTES.
Secrets are redacted from every envelope and event before they are written.

Streaming events (moda.agent_event.v1)

--agent --stream (or MODA_FORMAT=ndjson) emits one JSON event per line for long-running commands such as moda init and harness analysis: Every stream shares one run_id, seq counts gaplessly from 1, and exactly one terminal event ends the stream.

Exit-code handling

A complete handler:
ask-moda.sh

moda provision in CI

moda provision never opens a browser and never prompts, so the login happens once on a machine with a browser; CI then either stores the printed key as a secret (recommended) or reuses a copied session. Mint the key locally and store it as a repository secret in one line:
Provision prints exactly one JSON document ({apiKey, tenantId, tenantName, tenantSlug, ingestUrl, baseUrl, reused}) to stdout in every output mode, so jq -r .apiKey always works. Failure modes to script around: exit 4 = no stored session (log in first), exit 5 = multiple tenants and no --tenant-id/MODA_TENANT_ID (the error’s input_request.choices lists each tenant with resume_flags).
The provision output contains a live API key. Pipe it directly into your secret store — never into build logs.

Example: fail CI on tool-failure regressions

A scheduled job that reads the last 24 hours of tool failures and fails when any tool crosses a regression threshold. --json is passed explicitly: in CI the auto-detected mode would be agent, which wraps the payload in an envelope (you would then read .data.tools instead of .tools).
.github/workflows/moda-tool-failure-gate.yml
Adjust the thresholds to your traffic; failure_rate_pct alone spikes on low-volume tools, which is why the rule also requires an absolute failure count. The same pattern works for moda emotions --json (.summary.negative_rate_pct, the multi-family emotion model; moda frustrations remains as the legacy single-family view) and moda problems --json (.summary.open_problems).
The CLI’s daily update check is automatically skipped in CI, and usage telemetry can be disabled with MODA_CLI_TELEMETRY=0 or DO_NOT_TRACK=1.

Example: letting a coding agent investigate

A coding agent with MODA_API_KEY in its environment can investigate production behavior with read-only commands. Every command below auto-detects agent mode and returns envelopes; result rows carry conversation_id + message_index anchors that chain into the next command:
Other read-only entry points: moda emotions --family=confusion and moda hallucinations --kind=contradicted for signal triage, moda clusters --search="refunds" to resolve a use-case node by meaning, and moda tail --once --signal=all for a one-poll snapshot of live activity. When a fix ships, close the loop with moda problem-feedback <uuid> --action=mark_fixed (the one write in this family). Guidance worth encoding in your agent’s instructions:
  • Follow next_commands. Envelopes suggest the next useful invocation, with mutability and requires_approval so the agent knows what is safe to run unprompted. moda agent next-action produces the same thing from local repo state.
  • Start with moda agent context for repo-local Moda state (config, synced artifacts) and moda manifest --json for the command catalog — both work offline.
  • Branch on exit code 3 for moda ask. A degraded answer is still an answer; the envelope’s status is degraded and data.degraded is true.
  • Respect search degradation. data.search_mode is the mode that actually ran and degrade_reason says why; scores are not comparable across modes.
  • Report bad data. moda feedback "<note>" --conversation-id=... flags wrong or missing results to the Moda team; envelopes remind agents via meta.tip.

Next steps