# remits-cli Tool Reference

> A `remits-cli` skill reference. **Load this when** you are about to call any `mcp_*` tool. Read the entry for that tool before you build its input.
>
> The table of contents below carries **real line numbers** (`- L84  Some Heading`), resolved when
> this file is installed, so they are never stale. Read the head, pick your sections, and offset-read
> only those. The entry text is the heading verbatim, so it also greps.

## Table of Contents

- [Tool Reference](#tool-reference)
  - [Execute a Tool](#execute-a-tool)
  - [`mcp_account_view`](#mcp_account_view)
  - [`mcp_account_user_admin`](#mcp_account_user_admin)
  - [`mcp_firestore_search`](#mcp_firestore_search)
  - [`mcp_firestore_patch`](#mcp_firestore_patch)
  - [`mcp_object_activity`](#mcp_object_activity)
  - [`mcp_record_listing`](#mcp_record_listing)
  - [`mcp_record_view`](#mcp_record_view)
  - [`mcp_ai_session_search`](#mcp_ai_session_search)
  - [`mcp_run_action`](#mcp_run_action)
    - [Stopping a run — `controlAction:'interrupt'`](#stopping-a-run--controlactioninterrupt)
  - [`mcp_run_agent`](#mcp_run_agent)
    - [Controlling a live agent — `pause` / `unpause` / `interrupt`](#controlling-a-live-agent--pause--unpause--interrupt)
  - [`mcp_system_logs`](#mcp_system_logs)
  - [`mcp_user_activity`](#mcp_user_activity)
  - [`mcp_performance_trace`](#mcp_performance_trace)
  - [`mcp_event_diagnostics`](#mcp_event_diagnostics)
  - [`mcp_component_view`](#mcp_component_view)
  - [`mcp_component_grep`](#mcp_component_grep)
  - [`mcp_support_ticket`](#mcp_support_ticket)
  - [Component branches](#component-branches)
  - [`mcp_cache`](#mcp_cache)
  - [`mcp_sql_query`](#mcp_sql_query)
  - [`mcp_index_search`](#mcp_index_search)
  - [`mcp_get_guide`](#mcp_get_guide)
  - [`mcp_test_fixture`](#mcp_test_fixture)
  - [`mcp_playwright_replay`](#mcp_playwright_replay)
  - [`mcp_jvm_spike_triage`](#mcp_jvm_spike_triage)
  - [`mcp_support_ticket_queue`](#mcp_support_ticket_queue)

## Tool Reference

### Execute a Tool

```bash
remits-cli tool --name "mcp_firestore_search" --input '{"accountId": 37, "collection": "invoices"}' --data-mode prod
```

Response saved to `./.remits-cli/actors/<local-agent>/tool-responses/<callId>.json`. Read the file to
see results. Legacy flat `./.remits-cli/tool-responses/<callId>.json` files remain readable as fallback;
`remits-cli doctor local-state` shows both locations.

**"Tool call succeeded" means DISPATCHED, not that the tool did what you asked.** A tool that runs
and refuses — an unmet precondition, a rejected enum value, a failed validation — returns HTTP 200
with its own `success: false` inside `result`. The CLI now prints `Tool call FAILED — the tool ran
and returned an error.` plus a `Tool error:` line and exits non-zero, and the response envelope
carries `toolSuccess` / `toolMessage`. **For any MUTATING call, confirm the tool's own verdict before
reporting the work as done** — do not grep the terminal output for "succeeded":

```bash
F=$(remits-cli tool --name mcp_support_ticket --input "$(cat payload.json)" --data-mode prod 2>&1 \
    | grep -o '[^ ]*tool-responses/[a-f0-9-]*\.json' | tail -1)
python3 -c "import json;r=json.load(open('$F'))['result'];print(r.get('success'), r.get('message'))"
```

Build non-trivial JSON into a file (e.g. with `python3 -c 'json.dumps(...)'`) and pass it as
`--input "$(cat payload.json)"`. Long inline single-quoted JSON intermittently produces no response
file at all.

For long-running Action/Agent runners, use the tool's own async mode (`executionMode:"async"`), which returns
the `actionRunId`/`agentRunId` (and, for agents, `sessionId`) immediately:

```bash
remits-cli tool --account-id 21 --as-account 37 --target-account 37 --name mcp_run_action --input '{"actionName":"Rebuild Invoice","executionMode":"async","actionRunId":"my-stable-run-id","actionInput":{"invoiceId":"abc"}}' --data-mode prod
# poll by run id:  --account-id 21 --as-account 37 --target-account 37 --input '{"controlAction":"status","actionRunId":"my-stable-run-id"}'
```

That is the CLI Action-run surface. The command is still `remits-cli tool`; this CLI does not have a
separate top-level `remits-cli action`, `remits-cli actions`, or `remits-cli run action` wrapper.

For long tools that lack their own async mode, use the CLI transport async (`--async true`), optionally with
`--wait true` to poll locally, and `remits-cli tool status --call-id <callId>`. Do not stack both mechanisms
(see `command-reference.md` → *Tool Execution Lifecycle*). Use `--timeout-ms <ms>` only to adjust the per-request client timeout; it is
not a replacement for async mode on multi-minute workflows.

**Account roles for tool calls.** Keep the repo/scope account separate from the account whose data/runtime the
tool exercises:

1. `--account-id <ID>` is the repo/scope account that bounds discovery and verification.
2. `--as-account <ID>` is the execution account, matching `test run --as-account`.
3. `--target-account <ID>` is the tool/data target when the tool operates on a specific account but should not
   change component resolution. It is sent as `input.accountId` and `input.targetAccountId` when `--input`
   omits them — `input.accountId` is the key `mcp_run_action` and friends actually read, and without it they
   fall back to the run scope.

A target does not move the run: pass `--as-account` too whenever the work should resolve as that account's
components, branch subscription and staging lane.

Legacy `input.accountId` (or `input.account_id`) is still accepted as an explicit target for older snippets,
but prefer flags for new work; the CLI warns on stderr and in `warnings[]` when it is used alone. The
response `world` block is the source of truth: check `repoAccountId`, `executionAccountId`,
`targetAccountId`, `componentBranch`, `workspace`, and `dataMode`. `world.accountId` is the execution
account.

### `mcp_account_view`
Returns complete account structure — schemas, components, relationships.

Also returns two blocks that explain how the account RESOLVES, which is what you need before comparing
its behavior against component source:

- `resolution` — `role` (`OWNER` = resolves its own component trunk, `SUBSCRIBER` = resolves another
  account's components through a branch edge) and a one-sentence `summary`; `accountId` (the account
  described — the top level of this shape is the hierarchy ROOT, so do not read identity from there);
  `databaseName` (the account's own override, often null) vs `resolvedDatabaseName` (the storage namespace
  actually in effect); `branchName` (the repo sync branch) and `lastRepoSync`; `domainName` vs
  `resolvedDomainName` (the custom host in effect, which differs when reached through an edge host);
  `authPath` / `targetPath` (login and post-login landing routes); `editMode`; and — when a component
  branch is in effect — `componentBranch` plus `componentOwnerAccountId` / `componentOwnerAccountName`.
- `resolution.relationships` — **every structural link upward**, primary first: `parentAccountId` /
  `parentAccountName` / `parentAccountCode` / `parentAccountType`, `primary` (true for the one link that
  mirrors the account's primary parent), `active`, and the three independent link-scoped properties
  `branchName` (which component-variant code runs), `databaseName` (where data lives), `domainName` (which
  host reaches the account through this link). The hierarchy tree flattens all links into one shape, so this
  is the **only** place that answers "does this account have more than one parent, and which link carries the
  branch/namespace/host?" More than one entry ⇒ this account can resolve differently depending on the path a
  request travelled — establish which one a failing request used before comparing behavior.
- `componentBranches` — the variant branches this account OWNS, with override/add/remove, subscriber, and
  drift counts. Same summary the owner's `account-info.json` carries.

Note: this tool does **not** return users, and it returns EVERYTHING about one account. For users, for the
deep account tree, or for small account/user updates, use `mcp_account_user_admin`.

| Parameter | Required | Description |
|-----------|----------|-------------|
| `accountId` | yes | Account ID |

### `mcp_account_user_admin`
The account-graph and user surface: the middle ground between `account-info.json` (which states structure
compactly, because it is read into your context every session) and `mcp_account_view` (everything about one
account).

- `action: 'hierarchy'` (default) — the descendant tree trimmed to `depth` (1-10, default 2; nodes cut off
  are marked `truncated` and still report their child count) and/or the anchored ancestor chain plus every
  edge (`direction: 'down'|'up'|'both'`). Nodes include `testAccount`. **This is how you get the deep tree
  account-info.json omits.**
- `action: 'account'` — one account's `resolution` block, including `testAccount`, plus masked
  configuration fields, without paying for the component inventory.
- `action: 'users'` — an account's users at a hierarchy `scope` (`self`/`children`/`parents`/`hierarchy`),
  optional `email` substring filter. Rows include `testUser`. Extension fields are omitted here on purpose:
  they are stored **per account** and these users are bound to their own.
- `action: 'user'` — one user (`userId` or `email`) with roles, account memberships, and extension fields
  **correctly scoped to the requested account**, plus `testUser`. It never grants membership as a side
  effect of a read, and tells you when the fields shown belong to a different account.
- `action: 'account_update'` — write Account-schema configuration `fields`, and/or `name`/`status`
  (`ACTIVE`/`ON_HOLD`/`PENDING`).
- `action: 'user_update'` — write User-schema `fields` under the named account, plus `name`/`enabled` and
  membership add/remove. Refused unless the user is a member or you pass `addAccount: true`, because the
  write would otherwise land on another account. If an email does not exist globally, `addAccount: true`
  intentionally creates that user first, then binds them to the named account before writing fields.

**Building an account hierarchy** (the structural writes — this is how a coding agent provisions accounts
without a browser):

**Data-lane rule for provisioning:** `remits-cli tool` defaults to `--data-mode test`. That is correct for
fixtures and rehearsals, but it means `mcp_account_user_admin` `action:'account_create'` creates test-lane
accounts unless the command explicitly passes `--data-mode prod`. For real platform/product/customer
provisioning, always dry-run in prod first, check the response's `dataMode`, then run the write in prod:

```bash
remits-cli tool --name mcp_account_user_admin --data-mode prod --input '{"action":"account_create","parentAccountId":4,"name":"Freto","type":"PRODUCT","dryRun":true}'
```

After the real write, verify the response (or re-read `action:'account'`) shows the command `dataMode` you
intended and `testAccount:false` for real provisioning. `testAccount:true` means you created a test-data
account, even if the name and structure look correct.

For a test rehearsal, make the opposite assertion explicit: the response should show `dataMode:'test'` and
`testAccount:true` for **newly created** accounts (or `testUser:true` for created users).

Read `reusedExisting` before reading anything into the flag. `account_create` is find-or-create, and a
**test**-lane create can legitimately match a **real** account: real accounts are visible in both lanes,
so a rehearsal for a name that already exists in prod returns `reusedExisting:true` /
`testAccount:false` and changes nothing. That is correct reuse, not a lane error. Only
`reusedExisting:false` with `testAccount:false` in a test rehearsal means the lane was not the one you
intended. (The prod direction is not symmetrical: a prod-lane create never resolves onto a test
CLIENT/PROVIDER account, so the same name can exist once per lane.)

**A test-lane account does not get the `code` the prod one will.** `code` is derived from `name` and is
globally unique, so a test-lane create with no explicit `code` is assigned `test_<code>_<parentId>`.
A namespace resolves as `databaseName ?: platform.code ?: code`, so a rehearsal **does not prove the
storage namespace** the real create will land in unless you set `databaseName` explicitly. Conversely,
passing an explicit `code` in a test rehearsal opts out of the prefix, and the later prod create then
fails on `code unique:true` — as it also will against a test account created before this rule existed.
Check the existing account's `code` before assuming a name is free.

Account/User schema `fields` are Firestore-backed extension fields. Their physical storage follows the
same data lane as the tool call: `--data-mode test` writes under `testing/<resolvedDatabaseName>/...`, while
`--data-mode prod` writes under `accounts/<resolvedDatabaseName>/...` (for modern segmented accounts). If a
test-lane rehearsal should become real provisioning, rerun the create/update in prod mode; do not assume the
test-lane Firestore fields moved.

- `action: 'account_create'` — create a child under `parentAccountId`, with its **primary relationship edge**,
  applying `type` / `databaseName` / `domainName` / `authPath` / `targetPath` / `code` /
  `repositoryNameOverride` / `branchName` / `editMode` / … **at birth**. That ordering matters: the storage
  namespace is resolved from those properties, and the parent's cascaded schema fields are written into it
  during creation. Idempotent — an existing same-name account under that parent comes back with
  `reusedExisting: true`, unchanged. The response includes `testAccount`.
- `action: 'account_structure'` — change those properties on an existing account, including account-level
  custom host, login path, and landing path.
- `action: 'edge_add'` / `'edge_update'` / `'edge_remove'` — manage a membership `AccountRelationship` edge to
  `parentAccountId`, including the three independent edge properties `branchName` (which component code runs),
  `databaseName` (a path-scoped storage-namespace override — **live**, and inherited by everything below
  that edge) and `domainName` (which host reaches the account through that edge). Cycles, self-edges, and
  removing the primary edge are all refused.
- `action: 'reparent'` — move the account's **primary** edge (and `Account.parentId`) to `parentAccountId`.

> **The namespace guard.** An account's storage namespace resolves as
> `databaseName ?: platform.code ?: code`, and `Account.setName` **regenerates `code`** — so renaming an
> account whose namespace falls through to its own code silently repoints its storage. Any write that would
> move the resolved namespace is **refused** unless you pass `confirmSegmentChange: true`, and the refusal
> names both namespaces. To rename without moving storage, pin `code` to the old value in the same call.
>
> Because the namespace follows the **path** (see *Account Structure* in `platform-overview.md`), an
> `edge_update` that sets `databaseName` repoints storage for **everything below that edge**, and the
> answer is path-specific — the same account reached through a different parent can resolve a different
> namespace. Dry-run these.

Every write supports `dryRun: true`, which reports each `from -> to` without writing. Not here by design:
component-branch subscription reporting and drift (use `remits-cli components branches` /
`remits-cli components branch <name>`), and account/user **deletion** (admin only, so the
destructive-teardown contract applies).

**Provisioning recipe** — a new product account under a platform, running its own component branch, with
client accounts beneath it:

```
1. mcp_account_user_admin  action:'account_create'   parentAccountId:<platform>  name:'Adyen'
                           type:'PLATFORM'|'PRODUCT'  [databaseName:'...']
2. git branch + push in the OWNER's repo, then `remits-cli components sync` from that checkout
   (non-trunk => writes ComponentVariant overlays only)
3. mcp_account_user_admin  action:'edge_update'  targetAccountId:<new>  parentAccountId:<platform>
                           branchName:'<branch>'
4. mcp_account_user_admin  action:'account_create'  parentAccountId:<new>  name:'<business unit>'
                           type:'CLIENT'                  # inherits the branch automatically
```

> **Put the branch on the edge that is UNAMBIGUOUSLY on the account's path up — primary or membership.**
> Inheritance walks `parentId` and, at an account that has no `parentId`, continues through its *single*
> active membership edge. So a subscriber shell with **no `parentId` and one membership edge** to its owner
> is a fully supported shape: it resolves the branch, **and so do all of its descendants**. You do not need
> to make the subscriber a structural child of its owner.
>
> Several upward links are fine when exactly one carries the branch: the account and its descendants roll up
> through that subscription. What is NOT resolved by default is genuine **ambiguity** — several links and no
> single subscription among them. That account (and its descendants) resolve nothing above it until a request
> names the path: `--as-account`, `--variant-branch`, or the edge's own host.
> An account that has a `parentId` **and** a separate membership edge carrying the branch is this case: the
> `parentId` wins, so put the branch on the link the account actually inherits through.
>
> Either way, descendants inherit the branch down the chain automatically, which is what makes step 4 free.

| Parameter | Required | Description |
|-----------|----------|-------------|
| `action` | no | `hierarchy` (default), `account`, `users`, `user`, `account_update`, `user_update` |
| `accountId` / `targetAccountId` | no | Account to act on; `targetAccountId` targets another account without moving tool resolution |
| `depth` / `direction` | no | `hierarchy` only |
| `scope` | no | `users` only |
| `userId` / `email` | no | Identify the user (`user`, `user_update`); `email` is a filter for `users` |
| `fields` / `name` / `status` / `enabled` | no | The update payload |
| `addAccount` / `removeAccount` | no | Membership changes for `user_update` |
| `dryRun` | no | Report the change without writing |

### `mcp_firestore_search`
Query Firestore documents.

| Parameter | Required | Description |
|-----------|----------|-------------|
| `accountId` | yes | Account ID |
| `collection` | yes | Collection name (snake_case plural, e.g., `invoices`) |
| `documentId` | no | Fetch single document by ID |
| `filters` | no | `[{field, operation, value}]`. Operations: `EQUALS` (or `==`), `NOT_EQUALS` (or `!=`), `GREATER_THAN` (or `>`), `GREATER_THAN_EQUALS` (or `>=`), `LESS_THAN` (or `<`), `LESS_THAN_EQUALS` (or `<=`), `IN`, `NOT_IN`, `ARRAY_CONTAINS`, `ARRAY_CONTAINS_ANY`, `IS_NULL`, `IS_NOT_NULL`. `op` is accepted as alias for `operation`. |
| `sort` | no | `[{field, direction}]` — `ASC`/`DESC`. Also accepts top-level `orderBy` + `orderDirection`. |
| `pagination` | no | `{limit, offset}`. Default limit=25, max=200. Also accepts top-level `limit`/`offset`. |
| `fields` | no | Field names to return. If omitted, auto-selects up to 20 fields. |
| `aggregation` | no | `{sum: [...], avg: [...], min: [...], max: [...], count: true}` |
| `dateRanges` | no | `[{field, startDate, endDate}]` (yyyy-MM-dd) |
| `textSearch` | no | `[{field, prefix}]` for prefix matching |
| `fallbackOnMissingIndex` | no | When `true`, a sorted read that fails on a missing composite index retries without sort and returns `warning`, `missingIndexUrl`, and `sortApplied:false`. Use when an unsorted first page is still useful. |

**HTTP audits use this same tool** — they are Firestore documents in monthly collections
(`http-audits/http-audits-YYYY-MM/entries`). Which filters to reach for, and when audits are the right
evidence at all, is in `investigation.md` → *HTTP audits*.

```bash
remits-cli tool --name "mcp_firestore_search" --input '{"accountId": 37, "collection": "http-audits/http-audits-2026-05/entries", "filters": [{"field": "direction", "operation": "EQUALS", "value": "OUTBOUND"}, {"field": "request.path", "operation": "EQUALS", "value": "/api/orders"}, {"field": "success", "operation": "EQUALS", "value": false}], "sort": [{"field": "timestamp", "direction": "DESC"}], "pagination": {"limit": 10}}' --data-mode prod
```

### `mcp_firestore_patch`
Guarded exact-document Firestore patch tool for bounded repairs. It defaults to `dryRun:true` and refuses broad
updates, wildcard collections, delete/remove operations, protected identity fields, and `_lastModified*` audit
fields.

Use it when the desired repair is mechanical and smaller than rerunning an expensive Action, for example copying
canonical fields into stale UI mirror fields. Always dry-run first and include preconditions:

```bash
remits-cli tool --name mcp_firestore_patch --data-mode prod --input '{
  "accountId":743,
  "collection":"statements",
  "documentId":"20958",
  "dryRun":true,
  "preconditions":[
    {"field":"interchangeOptimization.status","equals":"Calculated"},
    {"field":"interchangeOptimizationChecked","equals":false}
  ],
  "patch":{
    "interchangeOptimizationChecked":true,
    "feeBreakdown.interchangeOptimization":{"$copyFrom":"interchangeOptimization"},
    "feeBreakdown.interchange.optimization":{"$copyFrom":"interchangeOptimization"}
  }
}'
```

Response fields include `dataMode`, `accountId`, `collection`, `documentId`, `dryRun`, `patchedFields`, and
`diff`. Switch to `"dryRun":false` only after the diff and preconditions are exactly what you intended.

### `mcp_object_activity`
Object lifecycle timeline — metadata + recent activity.

| Parameter | Required | Description |
|-----------|----------|-------------|
| `accountId` | yes | Account ID |
| `objectId` | yes | Object ID (from `object_id` in documents) |
| `dataMode` | no | Explicit lane: `prod` or `test`. Response echoes `dataMode`. |
| `activityOptions` | no | `{limit, offset, types, start, end, order}`. Default: limit=5, order=desc. Types: `OBJECT_LOG`, `EVENT`, `ALERT`. |

Response fields include `dataMode`, `object.testMode`, and `testMode` on Event/Alert timeline entries.

### `mcp_record_listing`
List and search lifecycle records when you do not already know the record ID.

Typical use:
- find active error alerts by `type` or `status`
- search alert/event/object_log content for an error phrase from an inbound support email
- narrow candidate records before switching to `mcp_record_view`

Tool ID: `88`

| Parameter | Required | Description |
|-----------|----------|-------------|
| `accountId` | yes | Account ID used for tenant scoping |
| `recordType` | yes | `object`, `object_log`, `event`, or `alert` |
| `filters` | no | Exact-match domain-property filters following the admin `listData` model, for example `status`, `type`, `active`, `threadGroupingId`, `action`, or enum fields using `_enum` |
| `ids` | no | Exact ID filter. Accepts a comma-separated string or array of numeric IDs |
| `query` | no | Lightweight text query over key searchable fields such as alert type/content/error text or object_log description/content |
| `page` | no | 1-based page number. Default: `1` |
| `pageSize` | no | Records per page. Default: `10`, max: `100` |
| `sort` | no | Domain property to sort by. Default: `id` |
| `order` | no | Sort direction: `asc` or `desc`. Default: `desc` |
| `includeChildren` | no | When `true`, include the specified account and child accounts |
| `scanLimit` | no | When using `query`, number of filtered candidate records to scan before text matching. Default: `200`, max: `500` |
| `maxPreviewChars` | no | Override preview length for returned content/body snippets. Max: `2048` |

### `mcp_record_view`
Inspect individual lifecycle records with line-range or grep.

| Parameter | Required | Description |
|-----------|----------|-------------|
| `accountId` | yes | Account ID |
| `recordType` | yes | `object`, `object_log`, `event`, or `alert` |
| `recordId` | yes | Record primary key |
| `field` | no | `content` (default) or `body` (objects only) |
| `revisionId` | no | Envers revision ID (not for object_log). Revision history does not retain `content`/`body`; omit it to read the current payload |
| `lineRange` | no | `{start, end}` (1-based inclusive) |
| `grep` | no | `{pattern, caseSensitive, contextBefore, contextAfter}` |

### `mcp_ai_session_search`
Search AI session groupings and export grouping detail in human-readable form.

Typical use:
- find AI sessions by agent, user, account, session ID, or grouping ID
- inspect the exact prompts, system messages, tools, and responses used in a prior run
- identify tuning opportunities in Agent behavior by comparing session output with the front-stage guides and component source

Front-stage references:
- `components/agent-components.md`
- `features/ai-support.md`

| Parameter | Required | Description |
|-----------|----------|-------------|
| `action` | no | `search` (default), `detail`, or session control `pause`/`unpause`/`interrupt` |
| `dataMode` | no | Explicit execution lane: `prod` or `test`. Response echoes the lane THIS CALL ran in. A grouping's own lane is on each row as `lanes` (from the per-turn lane the platform records). |
| `testTaskId` | no | Search mode: only groupings caused by this Test suite run (`taskId` from `remits-cli test run`) |
| `lane` | no | Search mode: `test` or `prod` — the lane the grouping's AI turns ran in |
| `search` | no | Broad text match against session IDs and grouping IDs |
| `sessionId` | no | Session ID filter in search mode, or grouping/session key in detail mode |
| `groupingId` | no | Grouping ID filter in search mode, or grouping key in detail mode |
| `groupingKey` | no | Preferred explicit grouping key for detail mode |
| `user` | no | User filter |
| `account` | no | Account filter |
| `agent` | no | Agent filter |
| `scope` | no | `all`, `agents`, or `internal` |
| `status` | no | Search mode: filter by live runtime status, comma-separated (e.g. `paused,interrupted`) |
| `scanLimit` | no | Search mode: window scanned when `status` is set. Default `100`, max `500` |
| `page` | no | 1-based page number. Default: `1` |
| `pageSize` | no | Results per page. Default: `25`, max: `100` |
| `summaryOnly` | no | Detail mode: return the MAP (record index + stats + timeline) with no payloads. Same as `parts:["index"]` |
| `compact` | no | Detail mode: after reading the MAP, open selected records without repeating the `records` index and `timeline`; keeps `groupingSummary`, `representativeSession`, `stats`, selected ids, and requested payload sections |
| `payloadOnly` | no | Alias for `compact` |
| `parts` | no | Detail parts: `index`, `map`, `records`, `stats`, `transcript`, `tool_calls`, and record sections `full`, `conversation_messages`, `system`, `response`, `tools` |
| `sections` | no | Alias for `parts` |
| `recordIds` | no | Detail mode: open only these request/response record IDs |
| `toolCallIds` | no | Detail mode: return the FULL exact input/result from `ai_tool_call` for these tool-call ids (what the tool PRODUCED — see the lens caveat) |
| `consolidateContext` | no | When `true`, collapses repeated XML-like prompt context into a consolidated section |

Detail responses expose both lenses: `groupingSummary` is the authoritative grouping-wide summary
(counts, lanes, cost, status), while `representativeSession` names the concrete session used for
owner/runtime metadata. `session` remains only as a compatibility alias for older callers.

Spend: each search row carries `liveRequestCount`, `mockedRequestCount`, `lanes`, `testTaskId` and
`estimatedCost`/`estimatedCostUsd` = **live spend only** (a mocked turn is never priced, even when it replays a
recording with a cost). `pageTotals` sums the page. Turns recorded before the platform stored the mock flag are
`unclassified` — unverified, not spend. A custom `range` with an unparseable `from`/`to`, or an unknown `range`,
is refused rather than silently widened; the applied bounds are echoed as `filters.rangeStart`/`rangeEnd`.
To audit one Test run: `{"action":"search","testTaskId":"<taskId>","pageSize":100}`.

Audit flow: `action:"search"` to find the grouping → `action:"detail"` + `summaryOnly:true` for the MAP → re-call detail with `recordIds`/`toolCallIds` + `parts` to open exactly what you need, usually with `compact:true` once the map has chosen the row. Prefer the map → open flow over a full-detail dump. Remember the two-lens rule: `tool_calls`/`toolCallIds` is what the tool PRODUCED; `conversation_messages`/`transcript` is what the AI CONSUMED (after any `_offload`/`_hideResult`/`_message`/supersede/evict transform).

### `mcp_run_action`
Run an Action on a target account, with explicit prod/test data mode, optional staged branch resolution, and
staged-vs-DB provenance in the result.

Invoke it with `remits-cli tool --name mcp_run_action`. Despite the natural shorthand "run an Action", there
is no separate top-level `remits-cli action` / `actions` command and no `remits-cli run action` wrapper in
this CLI build.

Describe the Action first when the input shape is not obvious. This does not execute the Action:

```bash
remits-cli tool --account-id 21 --as-account 49 --target-account 49 --name mcp_run_action --input '{"controlAction":"describe","actionId":25,"includeInputSchema":true}' --data-mode prod
```

The describe response reports `hasInputSchema`, optional `inputSchema`, `inferredInputKeys`, and component
provenance. If `hasInputSchema:false`, treat `inferredInputKeys` as a best-effort static scan, not a contract.

Use direct mode only for quick Actions:

```bash
remits-cli tool --account-id 21 --as-account 49 --target-account 49 --name mcp_run_action --input '{"actionId":200,"executionMode":"direct","actionInput":{"sourceDocumentId":"..."}}' --data-mode prod
```

Use the tool's own async mode for long-running Action execution — it returns immediately with an
`actionRunId`. Pass your **own** `actionRunId` so you can poll deterministically without first parsing it out
of the start response. Do **not** also pass the CLI `--async` flag; that only buries these ids behind the
transport layer:

```bash
remits-cli tool --account-id 21 --as-account 49 --target-account 49 --name mcp_run_action --input '{"actionId":200,"executionMode":"async","actionRunId":"my-stable-run-id","actionInput":{"sourceDocumentId":"..."}}' --data-mode prod
```

Then poll that run with another **regular tool call** carrying `controlAction:"status"` and the same
explicit account flags + `actionRunId`:

```bash
remits-cli tool --account-id 21 --as-account 49 --target-account 49 --name mcp_run_action --input '{"controlAction":"status","actionRunId":"my-stable-run-id"}' --data-mode prod
```

> This poll is a normal `remits-cli tool --name mcp_run_action` call — **not** `remits-cli tool status`,
> which polls the CLI-transport `--async` `callId` (a different mechanism). Use `controlAction:"status"`
> (rather than `command:"status"`) inside the input so it is never conflated with the transport-level status.
> If you started the run with a different `userId`, include that same `userId` in the poll (the run's status
> is keyed by account + user + `actionRunId`; it otherwise defaults to the current user).

`input.accountId` is still accepted by older tool implementations and legacy snippets, but it is no longer
the recommended way to describe cross-account work. Prefer `--account-id` for the repo/scope account and
`--as-account`/`--target-account` for the Action's execution/data account so verification packets and
`activity inspect --scope related` can report the split world without guessing.

For job-style Actions only (`Action.job == true`), prefer `executionMode:"event"` when you want the durable
Event lifecycle, Event status, and platform recovery behavior. Event mode is inherently async; poll it the
same way (`controlAction:"status"` + `actionRunId`) — the status resolves the backing Event's terminal state:

```bash
remits-cli tool --account-id 21 --as-account 49 --target-account 49 --name mcp_run_action --input '{"actionId":200,"executionMode":"event","actionRunId":"my-stable-run-id","actionInput":{}}' --data-mode prod
```

Returned fields on the async/event start: `actionRunId`, `status:"running"`, `executionMode`,
`threadGroupingId`, `componentSource`, `componentSignature`, and (event mode) `eventId`/`eventStatus`. The
`status` poll adds `result` on completion, or `message`/`error` on failure.

Every response (describe, direct, async start, and failures) also states the world the run resolved:
`executionAccountId`, `componentOwnerAccountId`, `componentSource` (`staged` / `variant` / `db`),
`componentSignature`, `componentBranch`, `stagingLane`, `dataMode`, `workspace`, and `traceId`. Direct runs add
`aiUsage` (live vs mocked calls and live cost for the run's trace).

**A staged-only `new_` Action runs by name.** Its `actionId` is `null` and the response says why
(`componentIdNote`) — that is valid, not "missing". When a name does not resolve, the error lists the accounts
searched, the staging lane (branch + workspace) and the staged Actions it holds, the component branch, and the
closest existing names; check that the lane named there is the one you staged into.

> **In event mode the EVENT is the source of truth, not the promise.** `status` is driven by the Event's
> own state; the value the dispatch call returned is reported separately as `dispatchResult` and is **not**
> the Action's result. Read `eventStatus` for the outcome.

#### Stopping a run — `controlAction:'interrupt'`

The tool counterpart of the **Interrupt** button on the admin Events page. Use it when an investigation
turns up a run that is consuming resources and should not finish — the case this exists for is finding a
`PROCESSING` event that has been running far too long.

```bash
# the usual path: you found the event in mcp_record_listing / mcp_object_activity
remits-cli tool --account-id 21 --as-account 49 --target-account 49 --name mcp_run_action --data-mode prod --input '{"controlAction":"interrupt","eventId":19102,"reason":"runaway extraction, 45min"}'

# or stop a run you started yourself (executionMode:'event' only)
remits-cli tool --account-id 21 --as-account 49 --target-account 49 --name mcp_run_action --data-mode prod --input '{"controlAction":"interrupt","actionRunId":"my-run-id"}'
```

Aliases `cancel` / `stop` / `kill` all work. What you need to know before using it:

- **It is COOPERATIVE cancellation, not a thread kill.** It sets a flag the running work observes at its
  next checkpoint, then throws. Checkpoints are dense across everything that matters — every Firestore
  read/write, outbound HTTP call, AI turn, and front-stage DSL call — so a normal run stops promptly. A
  run blocked inside a *single* long call (one slow AI turn) stops when that call returns, not instantly.
- **Work already committed is NOT rolled back.** This stops further work; it does not undo what has run.
- **Only `PENDING`/`QUEUED`/`PROCESSING` can be interrupted.** A terminal event is reported back with its
  status rather than being silently reported as "interrupted".
- **Event-scoped.** A `direct` or `async` run has no Event and cannot be stopped this way.
- Tenant-scoped: you cannot interrupt another account's event.
- The response returns `previousStatus`, `eventStatus`, and `threadGroupingId`, so you can pivot straight
  into `mcp_performance_trace` / `mcp_system_logs` to see what it was doing when you stopped it.

**AI sessions are stopped separately** with `mcp_ai_session_search` (`action:'interrupt'`, plus
`pause`/`unpause`) — that controls an agent's conversation loop, whereas this controls an Action's Event.

### `mcp_run_agent`
Run one real Agent turn on a target account. The Agent hooks and tools execute for real against the requested
`dataMode`; pass `dataMode:"test"` for safer tuning.

Use direct mode only for short turns:

```bash
remits-cli tool --name mcp_run_agent --input '{"accountId":49,"agentName":"InvoiceAuditor","message":"Summarize this invoice context","executionMode":"direct"}' --data-mode prod
```

Use the tool's own async mode for autonomous or long Agent turns. It returns immediately with both an
`agentRunId` and a single, stable `sessionId` — the **same** id the running session uses, so you can inspect
it right away. Pass your own `agentRunId` for deterministic polling. Do **not** also pass the CLI `--async`
flag (that only delays these ids into a polled result):

```bash
remits-cli tool --name mcp_run_agent --input '{"accountId":49,"agentName":"InvoiceAuditor","message":"Audit this invoice","executionMode":"async","agentRunId":"my-stable-run-id","context":{"invoiceId":"..."}}' --data-mode prod
```

Then poll that run with another **regular tool call** carrying `controlAction:"status"` and the same
`accountId` + `agentRunId` (this is a normal `mcp_run_agent` call, **not** `remits-cli tool status`):

```bash
remits-cli tool --name mcp_run_agent --input '{"controlAction":"status","accountId":49,"agentRunId":"my-stable-run-id"}' --data-mode prod
```

Inspect the live/persisted AI session at any time using the `sessionId` returned by the start call (it is the
run's real, canonical session id):

```bash
remits-cli tool --name mcp_ai_session_search --input '{"action":"detail","sessionId":"<sessionId>","summaryOnly":true}' --data-mode prod
```

Key returned fields: `agentRunId`, `sessionId`, `status`, `threadGroupingId`, runtime pause/interruption
hints, `lastAssistantMessage`, `toolCallCount`, `result`, `message`, and `error`.

#### Controlling a live agent — `pause` / `unpause` / `interrupt`

```bash
remits-cli tool --name mcp_run_agent --data-mode prod --input '{"controlAction":"pause","accountId":49,"agentRunId":"my-run-id"}'
remits-cli tool --name mcp_run_agent --data-mode prod --input '{"controlAction":"interrupt","accountId":49,"sessionId":"<sessionId>"}'
```

Target the session with the `agentRunId` an async start returned, or the `sessionId` directly.

- **`interrupt` is TERMINAL** (aliases `stop`/`cancel`/`kill`). It clears any pending resume and pending
  guardrails and persists a terminal lifecycle status. An interrupted session **cannot be unpaused** —
  attempting it is refused with that reason rather than silently doing nothing. Use `pause` if you intend
  to resume.
- If the session is not resident on the serving node, it is rehydrated by agent name — so pass `agentName`
  (or an `agentRunId`, which carries it) when controlling a session you did not just start.
- The same controls remain available on `mcp_ai_session_search`, which is the right tool when you are
  *searching* for the session; this is the right one when you *started* the run.

### `mcp_system_logs`
Query Cloud Run service logs.

| Parameter | Required | Description |
|-----------|----------|-------------|
| `node` | * | Node name (e.g., `remitsAdmin-east5`). Auto-resolves to serviceName+region. |
| `serviceName` | * | Cloud Run service. Not needed if `node` provided. |
| `region` | * | Cloud Run region. Not needed if `node` provided. |
| `timeRange` | * | Relative time: `1h`, `4h`, `30m`, `7d`. Auto-calculates startTime. |
| `startTime` | * | ISO 8601 timestamp. Not needed if `timeRange` provided. |
| `endTime` | no | ISO 8601 upper bound (defaults to now) |
| `severity` | no | Minimum: `INFO`, `WARNING`, `ERROR`, etc. |
| `threadGroupingId` | no | Filter by processing chain ID |
| `filter` | no | Additional Cloud Logging filter (LQL). **To search message text, pass the bare phrase** — it is widened automatically to match both `jsonPayload.message` (all `log.*` output) and `textPayload` (`println`/stdout). See the warning in `component-resolution.md` → *Diagnosing which version is in play*. |
| `maxPreviewChars` | no | Per-entry truncation width. Default 512, max 8000. Raise it when an entry carries a structured payload (a serialized `RemitsTrace`, a long stack frame) that the default cuts mid-JSON. |
| `pageSize` | no | Default 25, max 100. Also accepts `limit`. |

*Provide either `node` or `serviceName`+`region`. Provide either `timeRange` or `startTime`.

**Example:**
```json
{"node": "remitsAdmin-east5", "timeRange": "4h", "severity": "ERROR"}
```

### `mcp_user_activity`
Read live user activity sessions and control focused capture. Use this before trace/log spelunking when
the report is user-centric, for example "User abc is reporting slow responses." It wraps the same
Redis-backed store as System → Activity and returns ready pivots to traces, logs, and component source.

Common flows:

```bash
# Find what one user is doing right now
remits-cli tool --name mcp_user_activity --input '{"action":"sessions","userId":3,"limit":10}' --data-mode prod

# Open a returned sessionKey and inspect its beats
remits-cli tool --name mcp_user_activity --input '{"action":"story","sessionKey":"c_46ee68bba67003a6","limit":50}' --data-mode prod

# Arm focused capture, ask the user to reproduce, then read the story again
remits-cli tool --name mcp_user_activity --input '{"action":"watch","userId":3,"minutes":30}' --data-mode prod
```

| Parameter | Required | Description |
|-----------|----------|-------------|
| `action` | no | `sessions`, `story`, `watch`, `unwatch`, `forget`, `status`, or `archive`. Default: `sessions`. |
| `userId` / `accountId` | no | Filter sessions/archive or choose the focus subject for `watch`/`unwatch`. One is required for `watch`/`unwatch`. |
| `sessionKey` | for `story`/`forget` | Salted activity session key returned by `sessions`; not a raw browser/session credential. |
| `focusedOnly` | no | For `sessions`, return only focused/watched sessions. |
| `sinceMs` | no | For `sessions`, lower bound on last-seen epoch milliseconds. Defaults to the activity TTL window. |
| `limit` | no | Session/story/archive row cap. Defaults: sessions=100, story=200, archive=50. |
| `minutes` | no | Watch TTL for `watch`; defaults to `activity.focus.ttl.minutes`. |
| `traceId` / `threadGroupingId` | no | For `archive`, narrow focused activity log pivot to one trace. |
| `lookbackHours` | no | For `archive`, Cloud Logging window in the returned `mcp_system_logs` pivot. Default 24, max 168. |
| `node` / `serviceName` / `region` | no | For `archive`, target for the returned `mcp_system_logs` pivot. `node` defaults to `remitsAdmin-east5`. |

Reading rule: use `sessions` → `story` to build the behavioral timeline, then open a slow or failed beat's
`mcp_performance_trace` pivot. Use `watch` when the user can reproduce and no live story exists. Use
`archive` only for watched/focused sessions; ordinary activity lives in Redis and expires with the activity
TTL.

### `mcp_performance_trace`
Read Remits request traces through the same `traces(...)` DSL that powers the admin Diagnostics "Request
traces" panel. Use this before raw log spelunking for slow or sluggish requests because it returns profiled
span rollups, component annotations, and retained slow-request summaries directly.

Common flows:

```bash
# User only knows it was slow this afternoon
remits-cli tool --name mcp_performance_trace --input '{"action":"slowest","lookbackHours":4,"accountId":52,"minMs":2000,"limit":10}' --data-mode prod

# You have the Diagnostics/request/Object/Event/Alert threadGroupingId
remits-cli tool --name mcp_performance_trace --input '{"action":"trace","traceId":"msf1y65n-001","lookbackHours":6,"format":"markdown"}' --data-mode prod

# Same local-node data as the Diagnostics table
remits-cli tool --name mcp_performance_trace --input '{"action":"snapshot","limit":100}' --data-mode prod
```

| Parameter | Required | Description |
|-----------|----------|-------------|
| `action` | no | `snapshot`, `slowest`, or `trace`. Default: `snapshot`. |
| `traceId` / `threadGroupingId` | for `trace` | Request/grouping id to open across local ring and Cloud Logging. |
| `lookbackHours` | no | Cloud Logging window for `trace`/`slowest`. Defaults are 6 and 4 hours. |
| `accountId` | no | Account filter for `slowest`. |
| `kind` | no | Operation kind filter for `slowest`. **Rarely what you want** — see the note below. |
| `componentType` | no | Filter `slowest` by the component that did the work: `Action`, `Reader`, `Rule`, `Embeddable`, `Tool`, `Test`. **This is the right axis for "which Actions/Rules are slow".** |
| `componentId` / `componentName` | no | Narrow `slowest` to one component. |

> **Filter by `componentType`, not `kind`.** `kind` is set by whoever OPENS the trace. An Action delivered
> by Cloud Tasks arrives over HTTP, so the trace is `kind:'web'` and the Action is a `component.Action`
> *span inside it*; a Rule fired during a request and a Tool invoked by an agent are the same. So
> `kind:'action'` matches almost nothing in production. Every component execution annotates
> `componentType`/`componentId`/`componentName` — filter on those.
| `minMs` | no | Minimum duration for `slowest`. Default: 1500. |
| `limit` | no | Result limit. |
| `format` / `markdown` | no | Set `format:"markdown"` or `markdown:true` for a rendered trace report. |

> **Tools are components, so availability is per ACCOUNT.** `Tool not found: mcp_performance_trace` does
> not mean the tool is broken or that tracing is off — it means that tool has not been synced to the
> account you are resolving against. This bites most often on **localhost** (a Test Account that has not
> pulled the System Account's tool set) and on **client accounts**. Run `remits-cli tools` for the account
> in question, or re-run against an account that owns the tool (`--account-id 4` for the System Account).
> The same is true of every `mcp_*` tool, including the component tools noted below.

Reading rule: first use `slowest` to get candidate trace ids, then call `trace` on the suspicious id, then use
`mcp_system_logs` only if you need surrounding log lines. A trace dominated by `firestore.*`, `http.*`,
`gorm.save.*`, or component spans points you at the relevant platform seam or front-stage component. Large
unaccounted wall time is itself a finding: check cold compile, queueing, blocking I/O, or missing
`measure(...)` instrumentation.

### `mcp_event_diagnostics`
Diagnose one Event's infrastructure outcome through the same `eventDiagnostics(eventId)` DSL described in
`features/observability.md`. Use this before opening Action source when an Event is stuck,
recovered, timed out, retried, or appears to have been killed.

```bash
remits-cli tool --name mcp_event_diagnostics --input '{"accountId":49,"eventId":18838}' --data-mode prod
```

| Parameter | Required | Description |
|-----------|----------|-------------|
| `accountId` | yes | Tenant scope. The Event must belong to this account unless `includeChildren:true`. |
| `eventId` / `id` | yes | Event primary key to diagnose. |
| `includeChildren` | no | Allow the Event to belong to the requested account or one of its child accounts. Default: `false`. |

Read `classification` first:

- `APPLICATION_FAILURE` — the Action failed in application code; read `event.errorMessage`, correlated
  alerts, and the producing component.
- `ORPHANED_*` / `RECOVERED_*` — the attempt was abandoned; read `abandonmentCause`.
- `REQUEST_TIMEOUT_LIKELY` — it used its whole deadline (`timing.deadlineUsed` near `1.0`). The unit of
  work is too big for one event; the fix is resumable batches, not component logic.
- `PROCESS_TERMINATED_LIKELY` — it stopped well inside its deadline (`timing.deadlineUsed` near `0`), so
  the worker was killed (memory pressure, restart). Not a logic bug; inspect JVM/node health and
  container lifecycle logs.
- `UNKNOWN_NO_DEADLINE_EVIDENCE` — no deadline was recorded, so the cause is genuinely unknown. Use the
  returned `logQuery` filters; do not assume. Events predating delivery-envelope capture always look
  like this.
- `AWAITING_DELIVERY` — the Event was never claimed; check queue delivery and action-node health.
- `IN_FLIGHT_HEALTHY` — the Event is still heartbeating. A long Action is not a stuck one; wait, and
  inspect trace/logs before interrupting.

`delivery.deliveryAttempt` above `1` means Cloud Tasks had **already** retried this event, so any
non-idempotent side effect may have run more than once.

The response returns `delivery.threadGroupingId` — the same id everything else uses — plus the full
`result` map, `diagnosisHints`, and `pivots` carrying ready-to-run inputs for `mcp_performance_trace`,
`mcp_system_logs`, `mcp_record_listing`, and `mcp_object_activity` when those handles are present.
`logQuery` carries ready-made Cloud Logging filters, including the container-lifecycle and 504 queries.
For a performance question, open the `mcp_performance_trace` pivot next; for raw failure context, open
logs and records by `threadGroupingId`.

### `mcp_component_view`
Read component field content with line numbers.

In a normal `remits-cli` coding workflow, prefer local repo files for source reads. Use this tool when the
local repo is unavailable, when confirming live DB source, or when you need staging / variant metadata that
is not present in the working tree.

| Parameter | Required | Description |
|-----------|----------|-------------|
| `accountId` | yes | Account ID |
| `componentType` | yes | `Schema`, `Reader`, `Action`, `Embeddable`, `HtmlTemplate`, `Rule`, `Agent`, `Test`, `Tool`, `Prompt` |
| `componentId` | yes | Component ID |
| `fieldName` | no | `source`, `html`, `javascript`, `css`, `schema`, `description`, `mermaid`. Also accepts `field`. Omit for metadata. |
| `offset` | no | Start line (1-indexed). Also accepts `startLine`. |
| `limit` | no | Number of lines to return |

Returns `componentVariants` when the component has committed branch variants — the branches, their state
(`current` / `drifted` / `removed`), and a warning. Non-null means some accounts run a different version
than the source you are reading, and trunk promotions can drift those variants.
`mcp_component_grep` searches trunk, so it will not match text that exists only in a variant.

### `mcp_component_grep`
Search component source code with regex.

| Parameter | Required | Description |
|-----------|----------|-------------|
| `accountId` | yes | Account ID |
| `componentType` | yes | Component type |
| `pattern` | yes | Regex to search. Also accepts `searchTerm`, `query`, `search`. |
| `fieldName` | no | Field to search (default: `source`). Also accepts `field`. |
| `componentId` | no | Specific component. If omitted, searches ALL of the type. |
| `context` | no | Lines before AND after each match. Also accepts `contextLines`. |
| `caseSensitive` | no | Default: true |

### `mcp_support_ticket`
Create and manage the full lifecycle of account-relative `support_tickets`.

| Parameter | Required | Description |
|-----------|----------|-------------|
| `accountId` | conditional | The account that owns the support ticket. **Required only for `create`.** For every other action it is optional — the tool resolves the owning account from `ticketId` (the ticket's anchor id) and returns it. Pass it only to override/disambiguate. |
| `action` | yes | `create`, `read`, `accept`, `update_status`, `complete`, `release`, `record_progress`, `add_artifact`, or `get_attachment` |
| `ticketId` | conditional | Required for every action **except** `create` (which returns the new ticket ID). Alone it is sufficient to resolve the ticket and its owning account. |
| `subject` | conditional | Short title. Required for `create`. |
| `type` | conditional | Required for `create`: `enhancement`, `defect`, `question`, `task`, or `incident` |
| `priority` | no | `low`/`medium`/`high`/`critical` for `create` (default `medium`) |
| `description` | no | Longer description of the request/issue for `create` |
| `affectedComponent` | no | Component or platform area affected (`create`) |
| `implementationAccountId` / `implementationAccountName` | no | Owning `PLATFORM`/`PRODUCT` account when the ticket concerns shared implementation (e.g. a back-stage platform fix) |
| `stepsToReproduce` / `acceptanceCriteria` / `tags` | no | Extra `create` fields for defect/enhancement tickets |
| `assignee` | no | Required for `accept`. The agent's own name by convention — `claude`, `codex`, `gemini` |
| `status` | no | Required for `update_status`. Valid values: `in_progress`, `pending_review`. **The ticket must be `accept`ed first** — otherwise the call is rejected with *"Ticket must be accepted before updating status"* |
| `resolution` | no | Required for `complete` |
| `category` / `summary` / `details` / `findings` / `nextStep` | no | Worklog fields for `record_progress` (`category` + `summary` required). `category` is a **fixed enum** — `triage`, `investigation`, `reproduction`, `fix`, `verification`, `handoff`, `other` — and any other value fails the whole call. `findings` is a **list of strings**, not a paragraph |
| `artifactType` / `artifactLabel` / `contentBase64` / `gcsPath` / `url` | no | Evidence fields for `add_artifact` (screenshot/trace/log/test_result/link) |
| `notes` | no | Optional lifecycle note stored with the ticket activity |
| `attachmentIndex` | no | Zero-based index of the attachment to download. Used with `get_attachment`. |

**Opening a ticket for your own work (`create`).** When you are asked to do work — or you discover a Remits **back-stage** defect while building front stage — and you were **not** handed an existing ticket, open one with `action:'create'` so the work is tracked end-to-end. For a platform fix, use `type:'defect'` (or `'enhancement'` for a gap), describe the seam and evidence, reference the fix PR, and set `implementationAccountId`/`implementationAccountName` to the owning `PLATFORM`/`PRODUCT` account.

**Attachments:** Support emails may include file attachments (screenshots, logs, documents). These are automatically extracted and stored in GCS when the email is ingested. The `read` action returns an `attachments` array on the ticket with metadata for each file (`index`, `filename`, `contentType`, `size`, `messageId`, `uploadedAt`). To retrieve the actual file content:

1. Use `read` to see the attachments list and their indices
2. Use `get_attachment` with the desired `attachmentIndex` to download the file content (returned base64-encoded)
3. If called without `attachmentIndex`, `get_attachment` lists all attachments with their indices

This keeps file retrieval self-contained — no separate download endpoint is needed.

**Recommended flow** — `accountId` is optional throughout; `ticketId` resolves the owning account:
1. `read` — check ticket state and any attachments
   - If `read` returns `ticket.mirrorOnly:true`, use the mirrored fields for triage context, restore the backing document first, then claim/update/complete it.
2. **`accept`** (with `assignee`) — this is a **hard precondition for `update_status`**, not just etiquette
3. `get_attachment` if attachments are present and relevant to the investigation
4. `update_status` — `in_progress` while working, `pending_review` when the fix is done but not yet deployed
5. `record_progress` as you go — one `investigation` entry for the root cause, one `verification` entry for the proof
6. investigate/fix/verify on the owning ticket account or its implementation account as appropriate
7. `complete` (with `resolution`) or `release` if handing off

**Check each mutation actually landed.** Every action above is a write that can be refused while the
CLI still reports the *call* as fine — see "Execute a Tool" for why, and read `result.success` from
the response file. A silent no-op here means telling the user a ticket moved when it did not.

**Duplicates are common.** The same defect is often filed twice — once against the `CLIENT`/subscriber
account where it was observed and once against the owning `PLATFORM`/`PRODUCT` account. Before
starting, check `mcp_support_ticket_queue` for the same subject or affected component. Close the
duplicate with a `resolution` naming the ticket that carries the real work, rather than investigating
it twice.

**Automation rule:** If a ticket is involved, you should usually:
- `read` at the start
- `accept` before substantive work
- `get_attachment` if there are attachments relevant to the issue (screenshots, error logs, etc.)
- `update_status` when actively working or blocked
- `complete` after verification
- `release` if you are handing it off or cannot continue

### Component branches
Use `remits-cli components branches` / `remits-cli components branch <name>` to inspect committed branch
variants, subscribers, and drift from a local checkout.

Common uses:
- `remits-cli components branches` — list branches this owner has variants on.
- `remits-cli components branch <name>` — show overridden / added / removed components on that branch.
- `remits-cli components branch <name> --diff <id> --component-type <kind>` — compare one variant against
  current trunk.
- `remits-cli components branch <name> --subscribers` — list accounts resolving that branch.
- `remits-cli components branch <name> --copy-to <newBranch> [--dry-run] [--force]` — seed a new variant
  branch with the source branch's stored overlays before the first safe sync of the new branch. Dry-run
  reports the copy plan without writes; force is required when the target already has overlays or live
  subscribers.

### `mcp_cache`
Bounded read-only investigation of the platform Redis keyspace — the way to see exactly what a staged
entry holds (and its TTL) or any other cache key. Read-only: no delete (use `remits-cli components clear`
to remove staged component entries).

| Parameter | Required | Description |
|-----------|----------|-------------|
| `action` | yes | `summary` (overview of matching keys), `scan` (paginated key list; add `includeValuePreview:true`), or `inspect` (one exact `key`) |
| `pattern` | no | Redis glob for summary/scan (e.g. `account:52:cli:*:components:*:reader:id:181`). Alias: `keyPattern`/`query` |
| `key` | no | Exact key for `action:'inspect'` |
| `pageSize`/`sampleSize`/`previewChars` | no | Bounding controls |

### `mcp_sql_query`
Read-only, bounded SQL against the platform database. This is the **catch-all investigation surface** for
questions the purpose-built tools do not model — above all **users and account membership**, for which there
is no dedicated tool.

| Parameter | Required | Description |
|-----------|----------|-------------|
| `query` | yes* | One read-only statement. Alias: `sql`. Must start with `SELECT`, `WITH`, `SHOW`, `DESCRIBE`/`DESC`, or `EXPLAIN`; mutation, DDL, locking, and filesystem constructs are rejected. |
| `queries` | yes* | Batch of up to 10 read-only queries (strings, or `{query, params}` objects) |
| `params` / `parameters` | no | Positional parameters — **use these instead of interpolating values** |
| `maxRows` / `limit` | no | Rows per query. Default 100, max 500. |
| `maxValueChars` | no | Truncation width per string value. Default 2000, max 20000. |
| `redact` | no | Redact secret-like columns (`password`, `token`, `secret`, `authorization`, …). **Default true — leave it on.** |

*Provide `query`/`sql` or `queries`.

Useful shapes:

```sql
-- who has access to a client account (and through which membership rows)
SELECT u.id, u.username, u.enabled FROM user u
  JOIN user_account ua ON ua.user_id = u.id WHERE ua.account_id = ?;

-- every account a user can reach directly
SELECT a.id, a.name, a.type FROM account a
  JOIN user_account ua ON ua.account_id = a.id WHERE ua.user_id = ?;

-- the account's structural links, including membership edges (`primary` is column `is_primary`)
SELECT parent_id, is_primary, branch_name, database_name, domain_name, active
  FROM account_relationship WHERE account_id = ?;
```

**This tool is lane-blind — filter the data lane yourself.** Every other record surface segments test from
prod data for you; raw SQL does not. `object`, `event`, and `alert` carry a `test_mode` column, `user`
carries `test_user`, and `account` carries `test_account`, and an unfiltered query returns **both lanes
mixed** — so "who has access to this account" silently includes throwaway test users, and a row count
silently includes test fixtures. Add the predicate explicitly:

```sql
-- prod lane only (legacy rows predate the column, so NULL counts as prod)
SELECT id, name, status FROM object
  WHERE account_id = ? AND (test_mode IS NULL OR test_mode = 0);

-- real users of an account, excluding test fixtures
SELECT u.id, u.username, u.enabled FROM user u
  JOIN user_account ua ON ua.user_id = u.id
  WHERE ua.account_id = ? AND (u.test_user IS NULL OR u.test_user = 0);
```

Prefer the purpose-built tools when one fits — they apply account scoping, data-mode segmentation, and
resolution awareness that raw SQL does not. Reach for SQL when nothing else models the question.

### `mcp_index_search`
Query and **diagnose** the Vertex AI Search index through the platform's Vertex DSL. Use it to check what a
RAG/search-backed component actually retrieves before blaming the component.

| Parameter | Required | Description |
|-----------|----------|-------------|
| `accountId` | yes | Tenant scoping |
| `action` | no | `search` (default, `index_search()`), `facets` (`index_facets()` — taxonomy/distinct values), `diagnose` (explain why strict filtering dropped matches), `inspect` (what is actually indexed for given `sourceDocumentIds`) |
| `query` | conditional | Required for `search`/`diagnose` |
| `schema` / `projectionType(s)` / `projectionSource` | no | Scope to a schema's Vertex projections |
| `filters` | no | Vertex-style structured filters on indexed metadata |
| `pageSize` / `maxResults` / `pageToken` | no | Paging and local trimming |
| `searchProfile` | no | `ai`/`agent`/`strict` (precision) vs `admin`/`default` (recall) |
| `includeMatchDiagnostics` | no | Return the applied filter/query plus the pre-strict-filter candidate set |
| `accountIds` | no | Explicit multi-account search |
| `dataMode` | no | `test` (default) or `prod` |

When a component "can't find" an obviously-present document, run `action:'inspect'` on its source document id
first — that shows what was indexed, which is usually the answer.

### `mcp_get_guide`
Load the packaged front-stage guides — the same `docs/front-stage/` set `remits-cli` syncs into a repo. Use it when
you are working **outside a repo** (or the repo's `guides/` is stale) and need the authoritative guidance
before writing a component.

| Parameter | Required | Description |
|-----------|----------|-------------|
| `guide` | conditional | Short name (`agent-components`), relative path (`features/account-management`), or full path |
| `list` | no | List available guides instead of loading one |
| `directory` | no | Scope a list to `components` or `features` |
| `contains` | no | Substring filter when listing |

Every guide is delivered with a table of contents whose entries carry **real line numbers**
(`- L412  Querying Alerts`), resolved at delivery so they are never stale. Several of these guides are
over a thousand lines: read the head, pick the sections you need, and offset-read those rather than
loading the whole file. The entry text is the heading verbatim, so it also greps.

### `mcp_test_fixture`
Seed and remove schema-backed fixture documents in the **forced test** data segment. This is how you construct
realistic conditions for verification without copying live customer data.

| Parameter | Required | Description |
|-----------|----------|-------------|
| `accountId` | yes | Account owning the target schema/collection |
| `action` | no | `create` (fails on existing id), `upsert`, or `delete` |
| `schemaName` / `collection` | conditional | Schema display name or collection name |
| `documentId` / `documentIds` | no | Explicit Firestore ids for single/batch operations |
| `data` / `documents` | conditional | Single payload, or a batch array |
| `dataMode` | no | Must be `test` — **prod-mode writes are rejected** |

### `mcp_playwright_replay`
Hosted browser automation (the `playwright-relay` service) for visual verification when local `playwright-cli`
is unavailable — e.g. an agent running remotely. Returns an accessibility snapshot and interactive refs on
every command, so you navigate iteratively.

| Parameter | Required | Description |
|-----------|----------|-------------|
| `command` | yes | `open`, `goto`, `snapshot`, `click`, `dblclick`, `hover`, `fill`, `select`, `check`, `uncheck`, `eval`, `run-code`, `screenshot`, `pdf`, `console`, `network`, `go-back`, `go-forward`, `reload`, `tab-*`, `close`, `close-all` |
| `sessionId` | conditional | Reuse an existing session. `open`/`goto` without one starts a session. |
| `url` / `target` / `value` / `expression` / `code` | conditional | Per-command inputs (`target` is an element ref or selector) |
| `compact` | no | Default true — omits bulky raw relay payloads |
| `includeSnapshot` / `includeAccessibility` / `includeRefs` / `includePage` / `includeResult` / `includeRaw` | no | Response shaping |
| `pattern` / `level` / `limit` | no | Filters for `console` / `network` |
| `ticketId` / `artifactType` / `artifactLabel` / `artifactNotes` | no | Attach a screenshot/pdf/trace/video to a support ticket |

### `mcp_jvm_spike_triage`
Production-aware triage bundle for a Cloud Run service showing a latency/memory spike. Safe by construction:
optional class-histogram sampling, **no heap dump and no JFR**.

| Parameter | Required | Description |
|-----------|----------|-------------|
| `serviceName` | yes | e.g. `remits`, `remits-actions` |
| `region` | yes | e.g. `us-east5`, `us-east1` |
| `sampleHeap` | no | Default true — one class histogram + heap composition sample (brief stop-the-world) |
| `top` | no | Top classes to return. Default 20, max 100. |
| `includeThreadDump` / `threadLimit` | no | Fuller thread dump beyond the built-in top-thread preview |
| `includeLogs` / `logLookbackMinutes` / `logLimit` | no | Recent WARNING+ log signals for the same service |

### `mcp_support_ticket_queue`
List and filter tickets **across an account and its descendants** so you can choose what to work on. Lifecycle
actions stay on `mcp_support_ticket`.

Queue rows are built from support-ticket anchor mirrors by default. A row in this list means a
support-ticket anchor exists; it does not guarantee the full Firestore document is healthy. Open the
ticket with `mcp_support_ticket read` before lifecycle work and honor `mirrorOnly` / `documentState` if present.

| Parameter | Required | Description |
|-----------|----------|-------------|
| `action` | no | `list` (default) |
| `accountId` / `accountIds` / `includeChildren` / `includeRoot` | no | Queue scope. Defaults to the current account **and its descendants**. |
| `statuses` / `status` | no | Defaults to `open`, `accepted`, `in_progress`, `pending_review`. `['all']` disables filtering. |
| `priorities` / `types` / `sources` / `tags` | no | Additional filters |
| `assignedTo` / `unassigned` | no | Assignment filters |
| `implementationAccountId` | no | Filter by owning `PLATFORM`/`PRODUCT` account |
| `workstream` / `plannedIn` / `boardStage` / `size` / `blockedBy` | no | SDLC-agnostic planning filters; exact matches on compact queue fields |
| `rank` / `minRank` / `maxRank` | no | Exact or inclusive range filters for numeric planning rank |
| `search` | no | Case-insensitive across subject, description, account, affected component, sender, tags, workstream, and planning fields |
| `sortBy` | no | `triage` (default-style: critical/high unassigned first), `updated`, `priority`, `status`, `account`, `plannedIn`, `boardStage`, `rank`, `size` |
| `sortDirection` | no | `asc` / `desc`, and it means the same thing on every axis. Natural order is `desc` for `triage`/`updated`/`priority` and `asc` for `status`/`account`/`rank`/`plannedIn`/`boardStage`/`size`, so pass it only to invert one |
| `limit` / `offset` / `scanLimitPerAccount` | no | Paging and scan bounds |
| `dataMode` | no | Normal agent work uses the **prod** ticket queue |
