> ## Documentation Index
> Fetch the complete documentation index at: https://docs.moda.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Using the CLI from agents and CI

> Drive the moda CLI from coding agents and CI pipelines: agent envelopes, exit-code handling, headless provisioning, and runnable examples.

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](/cli/overview#output-formats)). To force it anywhere, set `MODA_AGENT=1` or pass `--agent`.

Three output contracts matter to a machine caller:

| Invocation                              | stdout                                                                             |
| --------------------------------------- | ---------------------------------------------------------------------------------- |
| `moda <cmd> --agent` (or auto-detected) | One `moda.agent.v1` envelope                                                       |
| `moda <cmd> --agent --stream`           | NDJSON `moda.agent_event.v1` events                                                |
| `moda <cmd> --json`                     | The raw payload only (no envelope); errors still print a structured error envelope |
| `moda tail`                             | NDJSON: one JSON line per event in every output mode                               |

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:

```bash theme={"dark"}
moda manifest --json      # commands, env vars, exit codes, stream events
moda --json-schemas       # JSON Schemas for the envelope and event contracts
```

## The `moda.agent.v1` envelope

<ResponseField name="schema_version" type="string">
  Always `"moda.agent.v1"`.
</ResponseField>

<ResponseField name="command" type="string">
  The command that produced the envelope.
</ResponseField>

<ResponseField name="status" type="string">
  `ok`, `degraded` (answer produced from a lower-trust fallback), or `error`.
</ResponseField>

<ResponseField name="generated_at" type="string">
  ISO 8601 timestamp.
</ResponseField>

<ResponseField name="profile" type="string">
  Active profile name, when one is set.
</ResponseField>

<ResponseField name="summary" type="object">
  `{ text, confidence }` — one-line result summary; `confidence` is `high` | `medium` | `low` | `unknown`.
</ResponseField>

<ResponseField name="findings" type="array">
  Ranked findings, each `{ id, title, status?, severity?, description?, evidence_ref_ids? }`.
</ResponseField>

<ResponseField name="evidence_refs" type="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.
</ResponseField>

<ResponseField name="recommended_actions" type="array">
  Suggested follow-ups: `{ id, title, rationale?, command?, mutability, requires_approval }`.
</ResponseField>

<ResponseField name="next_commands" type="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.
</ResponseField>

<ResponseField name="artifacts" type="array">
  Files the command created or verified: `{ kind, path, description? }`.
</ResponseField>

<ResponseField name="warnings" type="array">
  Non-fatal warnings (strings).
</ResponseField>

<ResponseField name="errors" type="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`.
</ResponseField>

<ResponseField name="data" type="object">
  The command payload — the same JSON `--json` would print.
</ResponseField>

<ResponseField name="meta" type="object">
  `run_id` plus a `tip` string on successful envelopes.
</ResponseField>

<ResponseField name="truncation" type="object">
  Present when `data` was bounded to fit `MODA_AGENT_MAX_OUTPUT_BYTES`.
</ResponseField>

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:

| Event           | Terminal | Meaning                                                                  |
| --------------- | -------- | ------------------------------------------------------------------------ |
| `started`       | no       | First event of every stream.                                             |
| `progress`      | no       | Work continues; carries `phase` and `message`.                           |
| `warning`       | no       | Non-fatal warning or skipped action.                                     |
| `artifact`      | no       | A file was produced.                                                     |
| `heartbeat`     | no       | Liveness signal after `MODA_HEARTBEAT_MS` (default 10 s) without output. |
| `completed`     | yes      | Success; carries the full envelope as `result`.                          |
| `error`         | yes      | Failure; carries `error` and the error envelope.                         |
| `needs_input`   | yes      | Exit-and-reinvoke input request (`input_request` payload).               |
| `auth_required` | yes      | Authentication needed before retrying (`auth` payload).                  |
| `cancelled`     | yes      | Interrupted.                                                             |

Every stream shares one `run_id`, `seq` counts gaplessly from 1, and exactly one terminal event ends the stream.

## Exit-code handling

| Code  | State          | What a caller should do                                                                                      |
| ----- | -------------- | ------------------------------------------------------------------------------------------------------------ |
| `0`   | ok             | Use the result.                                                                                              |
| `1`   | error          | Read `errors[0].message`; retry only for `network_error`/`api_error` where sensible.                         |
| `3`   | degraded       | `moda ask` only: an answer exists but was synthesized locally (`data.degraded: true`). Treat as lower trust. |
| `4`   | auth required  | Run `errors[0].auth.login_command`, then re-run `errors[0].auth.resume_command`.                             |
| `5`   | input required | Pick a choice from `errors[0].input_request.choices` and re-invoke with its `resume_flags`.                  |
| `130` | cancelled      | Stop; the run was interrupted or the pipe closed.                                                            |

A complete handler:

```bash ask-moda.sh theme={"dark"}
#!/usr/bin/env bash
# Ask Moda a production question and branch on the exit code.
set +e
moda --agent ask "what should I fix first this week?" > ask.json
status=$?
set -e

case "$status" in
  0)
    jq -r '.summary.text' ask.json
    ;;
  3)
    echo "Degraded answer (Moda Cloud unavailable; synthesized from local evidence):"
    jq -r '.summary.text' ask.json
    ;;
  4)
    echo "Auth required. Run:" >&2
    jq -r '.errors[0].auth.login_command | join(" ")' ask.json >&2
    exit 4
    ;;
  5)
    echo "Input required. Choices:" >&2
    jq -r '.errors[0].input_request.choices[] | "\(.label) -> \(.resume_flags | join(" "))"' ask.json >&2
    exit 5
    ;;
  130)
    exit 130
    ;;
  *)
    jq -r '.errors[0].message // "unknown error"' ask.json >&2
    exit 1
    ;;
esac
```

## `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:

```bash theme={"dark"}
moda auth login
moda provision --tenant-id=1f7c9a2e-4b3d-4c8e-9a1b-2d3e4f5a6b7c --label=github-actions \
  | jq -r .apiKey \
  | gh secret set MODA_API_KEY
```

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`).

<Warning>
  The provision output contains a live API key. Pipe it directly into your secret store — never into build logs.
</Warning>

## 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`).

```yaml .github/workflows/moda-tool-failure-gate.yml theme={"dark"}
name: Moda tool-failure gate

on:
  schedule:
    - cron: "0 6 * * *"
  workflow_dispatch:

jobs:
  gate:
    runs-on: ubuntu-latest
    env:
      MODA_API_KEY: ${{ secrets.MODA_API_KEY }}
    steps:
      - name: Fail on new tool-failure regressions
        run: |
          npx -y @moda-ai/cli tool-failures --json --days-back=1 > failures.json

          echo "Tools with failures in the last 24h:"
          jq -r '.tools[] | "\(.tool_name): \(.failure_count)/\(.total_count) calls failed (\(.failure_rate_pct)%)"' failures.json

          # Regression rule: any tool with >= 5 failures AND a failure rate above 10%.
          regressions=$(jq '[.tools[] | select(.failure_count >= 5 and .failure_rate_pct > 10)] | length' failures.json)
          if [ "$regressions" -gt 0 ]; then
            jq -r '.tools[] | select(.failure_count >= 5 and .failure_rate_pct > 10)
                   | "::error::\(.tool_name): \(.failure_count) failures — \(.top_error)"' failures.json
            exit 1
          fi
```

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`).

<Note>
  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`.
</Note>

## 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:

```bash theme={"dark"}
# 1. Find where users hit the problem (results carry conversation_id + message_index)
moda search "refund failed" --mode=hybrid --time-range=7d --limit=5

# 2. Read the exact turns around a hit from step 1
moda context conv_5e1a9c2b7d3f4680 --msg-index=12 --window=2

# 3. Score the trajectory, and inspect what the agent believed at the failing turn
moda step-scores conv_5e1a9c2b7d3f4680
moda world-state conv_5e1a9c2b7d3f4680 --snapshot --msg-index=12

# 4. Check whether a tool is behind it (anchors + optional context windows)
moda tool-failure-detail refundPayment --include-window

# 5. Open the ranked Problem behind the pattern, with its evidence
moda problems --days-back=30
moda problem 3f2a9c1e-8b4d-4e6f-9a2b-1c3d5e7f9a0b --evidence --limit=10

# 6. Ask for a cited synthesis (exit 3 = degraded local fallback)
moda ask "why do refunds fail for enterprise users?"
```

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

* [CLI reference](/cli/reference) — every command, flag, and output shape.
* [CLI overview](/cli/overview) — install, auth, profiles, exit codes.
* [Coding agents ingestion](/ingestion/coding-agents) — send your agents' own traces to Moda.
* [Harness CI rescan](/harness/ci-rescan) — keep the harness map in sync from CI.
