> ## Documentation Index
> Fetch the complete documentation index at: https://comis-fix-skill-import-vetting-gate.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# JSON-RPC Methods Reference

> Reference for the JSON-RPC methods exposed by the daemon

**What this is for:** every JSON-RPC method the daemon exposes — what to send, what comes back, and which scope you need. **Who it's for:** developers building CLIs, dashboards, or external integrations against the Comis gateway.

Complete reference for the JSON-RPC methods available over the Comis gateway WebSocket and HTTP transports, covering agent execution, memory, configuration, sessions, scheduling, browser automation, context engine, workspace management, media processing, cross-channel messaging, sub-agent lifecycle, autonomy controls, platform actions, notifications, secrets, and administrative operations. Methods are accessed via the JSON-RPC 2.0 protocol over the [WebSocket endpoint](/reference/websocket) at `/ws` or the HTTP endpoint at `POST /rpc`.

The authoritative method set is `API_CONTRACTS_ORDERED` in `packages/core/src/api-contracts/`. Gateway registration is derived from that registry, and the bidirectional architecture check prevents handler/contract drift; this reference therefore avoids a hand-maintained global method count.

## Session-creation walkthrough

The most common end-to-end flow: open a connection, ping for liveness, kick off an agent execution, and check the resulting session. Every step is one JSON-RPC call.

<Steps>
  <Step title="Connect with a bearer token">
    Open the WebSocket and authenticate. If the token is invalid, the server closes with code `4001`.

    ```bash theme={}
    wscat -c ws://localhost:4766/ws -H "Authorization: Bearer $COMIS_GATEWAY_TOKEN"
    ```
  </Step>

  <Step title="Verify liveness">
    `system.ping` is the cheapest method and a good way to validate auth + transport before sending real work.

    ```json theme={}
    {"jsonrpc":"2.0","method":"system.ping","id":1}
    ```

    Response: `{"jsonrpc":"2.0","result":{"pong":true,"ts":1773313498000},"id":1}`
  </Step>

  <Step title="Execute an agent turn">
    `agent.execute` runs one full turn through the agent pipeline. The first call to a fresh `sessionKey` implicitly creates the session.

    ```json theme={}
    {
      "jsonrpc": "2.0",
      "method": "agent.execute",
      "params": {
        "agentId": "default",
        "message": "Summarize today's commits in two bullet points.",
        "sessionKey": {
          "tenantId": "default",
          "userId": "alice",
          "channelId": "cli"
        }
      },
      "id": 2
    }
    ```

    Response carries `response`, `tokensUsed`, and `finishReason`.
  </Step>

  <Step title="Inspect or list the session">
    `session.list` enumerates active sessions; `session.history` reads the full message log for one session.

    ```json theme={}
    {"jsonrpc":"2.0","method":"session.list","params":{"tenant_id":"default","agent_id":"default"},"id":3}
    ```
  </Step>

  <Step title="Clean up when done">
    Use `session.delete` (admin) to archive and remove the exact conversation partition. RAG memories are not deleted by this method; use `session.reset_conversation` with `memory: true` for that separate destructive operation.

    ```json theme={}
    {
      "jsonrpc": "2.0",
      "method": "session.delete",
      "params": {
        "tenant_id": "default",
        "agent_id": "default",
        "conversation_ref": "cv_MsDcSBa9dmYP7ptzs_Xd7CMHOi6tliEQVzCbN1oOD4o"
      },
      "id": 4
    }
    ```
  </Step>
</Steps>

That same pattern works whether you call over WebSocket or `POST /rpc`. For streaming token deltas use the SSE endpoint `POST /api/chat/stream` documented in [HTTP Gateway](/reference/http-gateway#sse-endpoints).

## Scopes

Every JSON-RPC method requires a specific scope. Tokens are configured in `gateway.tokens[]` with a `scopes` array.

| Scope   | Access Level   | Description                                                                                 |
| ------- | -------------- | ------------------------------------------------------------------------------------------- |
| `rpc`   | Standard       | Client operations: agent execution, memory search, cron management, browser, skills, graphs |
| `admin` | Administrative | Config, agent management, memory management, observability, tokens, channels                |
| `*`     | Wildcard       | Access to all methods (both `rpc` and `admin`)                                              |

A token with `scopes: ["rpc"]` can only call `rpc`-scoped methods. A token with `scopes: ["*"]` has access to all methods. Calling a method without sufficient scope returns error code `-32603` with message `"Insufficient scope: requires 'admin'"`.

## Request Format

All methods use JSON-RPC 2.0 format. See [WebSocket Protocol](/reference/websocket) for transport details.

<CodeGroup>
  ```json Request theme={}
  {
    "jsonrpc": "2.0",
    "method": "namespace.method",
    "params": { "key": "value" },
    "id": 1
  }
  ```

  ```json Response theme={}
  {
    "jsonrpc": "2.0",
    "result": { "key": "value" },
    "id": 1
  }
  ```
</CodeGroup>

The `params` object is optional and defaults to `{}` when omitted. The `id` field correlates requests with responses.

## Methods by Namespace

<AccordionGroup>
  <Accordion title="system (1 method)">
    Health check and connectivity testing.

    | Method        | Scope | Description                                   |
    | ------------- | ----- | --------------------------------------------- |
    | `system.ping` | `rpc` | Returns a pong response with server timestamp |

    ### `system.ping`

    | Property       | Value                        |
    | -------------- | ---------------------------- |
    | **Scope**      | `rpc`                        |
    | **Parameters** | None                         |
    | **Returns**    | `{ pong: true, ts: number }` |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "system.ping",
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "pong": true,
          "ts": 1773313498000
        },
        "id": 1
      }
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="agent (4 methods)">
    Agent execution for processing user messages through the LLM pipeline, cache statistics inspection, and per-operation model resolution introspection.

    | Method                     | Scope   | Description                                                |
    | -------------------------- | ------- | ---------------------------------------------------------- |
    | `agent.cacheStats`         | `admin` | Get prompt cache statistics for an agent                   |
    | `agent.execute`            | `rpc`   | Execute an agent turn (non-streaming)                      |
    | `agent.stream`             | `rpc`   | Execute an agent turn with streaming token deltas          |
    | `agent.getOperationModels` | `admin` | Show how each agent operation resolves to a concrete model |

    ### `agent.cacheStats`

    | Property       | Value                                                       |
    | -------------- | ----------------------------------------------------------- |
    | **Scope**      | `admin`                                                     |
    | **Parameters** | `{ agentId?: string }`                                      |
    | **Returns**    | Cache statistics object with hit/miss rates and cache sizes |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "agent.cacheStats",
        "params": { "agentId": "default" },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "agentId": "default",
          "cacheHits": 142,
          "cacheMisses": 23,
          "hitRate": 0.86,
          "totalTokensCached": 850000
        },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `agent.execute`

    | Property       | Value                                                                              |
    | -------------- | ---------------------------------------------------------------------------------- |
    | **Scope**      | `rpc`                                                                              |
    | **Parameters** | `{ message: string, agentId?: string, sessionKey?: object, scopes?: string[] }`    |
    | **Returns**    | `{ response: string, tokensUsed: { input, output, total }, finishReason: string }` |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "agent.execute",
        "params": {
          "message": "Hello, how are you?",
          "agentId": "default"
        },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "response": "I'm doing well! How can I help you today?",
          "tokensUsed": { "input": 42, "output": 15, "total": 57 },
          "finishReason": "stop"
        },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `agent.stream`

    Same parameters as `agent.execute`. Token-delta streaming over JSON-RPC is not currently implemented -- the gateway logs a warning and falls back to a non-streaming response. For real-time streaming use the SSE endpoint `POST /api/chat/stream` documented in [HTTP Gateway](/reference/http-gateway#sse-endpoints).

    ### `agent.getOperationModels`

    Inspect how each agent operation (chat, classifier, summarizer, etc.) resolves to a concrete provider+model. Returns the agent's primary model and provider family, whether tiered model routing is active, and a per-operation breakdown including the resolved model, source (config or default), per-operation timeout, cross-provider flag, and whether the API key for the resolved provider is configured.

    | Property       | Value                                                                                                                                                                                |
    | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
    | **Scope**      | `admin`                                                                                                                                                                              |
    | **Parameters** | `{ agentId: string }`                                                                                                                                                                |
    | **Returns**    | `{ agentId, primaryModel, providerFamily, tieringActive, operations: Array<{ operationType, model, provider, source, tieringActive, timeoutMs, crossProvider, apiKeyConfigured }> }` |
  </Accordion>

  <Accordion title="config (10 methods)">
    Configuration reading, writing, schema inspection, history, and rollback. Read-only methods and mutations for runtime config management.

    | Method            | Scope   | Description                                             |
    | ----------------- | ------- | ------------------------------------------------------- |
    | `config.apply`    | `admin` | Apply full config object                                |
    | `config.diff`     | `admin` | Get unified diff between config versions                |
    | `config.gc`       | `admin` | Run config garbage collection                           |
    | `config.get`      | `admin` | Read a config section (core method)                     |
    | `config.history`  | `admin` | Get git-backed config change history                    |
    | `config.patch`    | `admin` | Patch config value with persistence                     |
    | `config.read`     | `admin` | Read config with section filtering and secret redaction |
    | `config.rollback` | `admin` | Rollback config to a previous version                   |
    | `config.schema`   | `admin` | Get JSON Schema for config sections                     |
    | `config.set`      | `admin` | Write a config value (core method)                      |

    ### `config.read`

    Read the current configuration, optionally filtered to a specific section. Secret values are automatically redacted in the response.

    | Property       | Value                                                                            |
    | -------------- | -------------------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                                          |
    | **Parameters** | `{ section?: string }`                                                           |
    | **Returns**    | Section object (if section specified) or `{ config: {...}, sections: string[] }` |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "config.read",
        "params": { "section": "gateway" },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "enabled": true,
          "host": "0.0.0.0",
          "port": 4766
        },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `config.schema`

    | Property       | Value                                                      |
    | -------------- | ---------------------------------------------------------- |
    | **Scope**      | `admin`                                                    |
    | **Parameters** | `{ section?: string }`                                     |
    | **Returns**    | `{ schema: object, sections: string[], section?: string }` |

    ### `config.patch`

    | Property       | Value                                          |
    | -------------- | ---------------------------------------------- |
    | **Scope**      | `admin`                                        |
    | **Parameters** | Patch object (deep-merged with current config) |
    | **Returns**    | Updated config acknowledgment                  |

    ### `config.apply`

    | Property       | Value                         |
    | -------------- | ----------------------------- |
    | **Scope**      | `admin`                       |
    | **Parameters** | Full config object to apply   |
    | **Returns**    | Applied config acknowledgment |

    ### `config.diff`

    | Property       | Value                                                           |
    | -------------- | --------------------------------------------------------------- |
    | **Scope**      | `admin`                                                         |
    | **Parameters** | `{ sha?: string }` -- compare against a specific config version |
    | **Returns**    | `{ diff: string }` -- unified diff of config changes            |

    ### `config.gc`

    | Property       | Value                     |
    | -------------- | ------------------------- |
    | **Scope**      | `admin`                   |
    | **Parameters** | None                      |
    | **Returns**    | Garbage collection result |

    ### `config.history`

    | Property       | Value                                                       |
    | -------------- | ----------------------------------------------------------- |
    | **Scope**      | `admin`                                                     |
    | **Parameters** | `{ limit?: number }`                                        |
    | **Returns**    | `{ entries: object[] }` -- git-backed config change history |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "config.history",
        "params": { "limit": 5 },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "entries": [
            { "sha": "abc1234", "date": "2026-04-10T12:00:00Z", "message": "Update gateway port" }
          ]
        },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `config.rollback`

    | Property       | Value                                               |
    | -------------- | --------------------------------------------------- |
    | **Scope**      | `admin`                                             |
    | **Parameters** | `{ sha: string }` -- the version SHA to rollback to |
    | **Returns**    | Rollback confirmation                               |

    ### `config.get`

    Read a raw config section. Unlike `config.read`, this is a core gateway method that returns the section without secret redaction. Use `config.read` from application code; `config.get` exists for internal gateway wiring.

    | Property       | Value                                                                                                   |
    | -------------- | ------------------------------------------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                                                                 |
    | **Parameters** | `{ section?: string }`                                                                                  |
    | **Returns**    | `Record<string, unknown>` -- the requested section, or the full sanitized config if no section is given |

    ### `config.set`

    Write a single config value. This is a core gateway method that forwards to `config.patch` internally. For most use cases, `config.patch` gives you more control over the merge behavior.

    | Property       | Value                                              |
    | -------------- | -------------------------------------------------- |
    | **Scope**      | `admin`                                            |
    | **Parameters** | `{ section: string, key: string, value: unknown }` |
    | **Returns**    | `{ ok: true, previous?: unknown }`                 |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "config.set",
        "params": {
          "section": "gateway",
          "key": "port",
          "value": 4767
        },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "ok": true,
          "previous": 4766
        },
        "id": 1
      }
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="gateway (2 methods)">
    Gateway server status, process information, and restart.

    | Method            | Scope   | Description                                                      |
    | ----------------- | ------- | ---------------------------------------------------------------- |
    | `gateway.restart` | `admin` | Restart the gateway server                                       |
    | `gateway.status`  | `admin` | Process info: PID, uptime, memory, Node.js version, config paths |

    ### `gateway.restart`

    | Property       | Value                |
    | -------------- | -------------------- |
    | **Scope**      | `admin`              |
    | **Parameters** | None                 |
    | **Returns**    | Restart confirmation |

    ### `gateway.status`

    | Property       | Value                                                                                                                  |
    | -------------- | ---------------------------------------------------------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                                                                                |
    | **Parameters** | None                                                                                                                   |
    | **Returns**    | `{ pid: number, uptime: number, memoryUsage: number, nodeVersion: string, configPaths: string[], sections: string[] }` |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "gateway.status",
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "pid": 12345,
          "uptime": 3600.5,
          "memoryUsage": 104857600,
          "nodeVersion": "v22.19.0",
          "configPaths": ["/home/user/.comis/config.yaml"],
          "sections": ["tenantId", "logLevel", "dataDir", "agents", "channels", "gateway"]
        },
        "id": 1
      }
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="obs (16 methods)">
    Observability methods for diagnostics, billing estimation, channel activity tracking, message delivery tracing, context engine inspection, prompt cache statistics, and data management. All methods require `admin` scope **except `obs.diagnostics`, which is `rpc`-scoped** (read-only, scrubbed digests on a single-tenant daemon, so an agent's `obs_query` can self-diagnose its own sessions).

    | Method                   | Scope   | Description                                                                    |
    | ------------------------ | ------- | ------------------------------------------------------------------------------ |
    | `obs.billing.byAgent`    | `admin` | Token usage for a specific agent                                               |
    | `obs.billing.byProvider` | `admin` | Token usage breakdown by LLM provider                                          |
    | `obs.billing.bySession`  | `admin` | Token usage for a specific session                                             |
    | `obs.billing.total`      | `admin` | Total billing estimate                                                         |
    | `obs.billing.usage24h`   | `admin` | Last 24 hours usage summary                                                    |
    | `obs.channels.all`       | `admin` | Activity data for all channels                                                 |
    | `obs.channels.get`       | `admin` | Activity data for a single channel                                             |
    | `obs.channels.stale`     | `admin` | Channels with no recent activity                                               |
    | `obs.context.dag`        | `admin` | Get DAG state snapshot for an agent                                            |
    | `obs.context.pipeline`   | `admin` | Get context pipeline state for an agent                                        |
    | `obs.delivery.recent`    | `admin` | Recent message delivery traces                                                 |
    | `obs.delivery.stats`     | `admin` | Aggregate delivery statistics                                                  |
    | `obs.diagnostics`        | `rpc`   | Recent diagnostic events with category counts (self-observable; no admin gate) |
    | `obs.getCacheStats`      | `admin` | Get prompt cache statistics                                                    |
    | `obs.reset`              | `admin` | Reset all observability data (metrics, billing, delivery, channel snapshots)   |
    | `obs.reset.table`        | `admin` | Reset a specific observability table                                           |

    ### `obs.diagnostics`

    | Property       | Value                                                           |
    | -------------- | --------------------------------------------------------------- |
    | **Scope**      | `rpc` (self-observable; no in-handler admin gate)               |
    | **Parameters** | `{ category?: string, limit?: number, sinceMs?: number }`       |
    | **Returns**    | `{ events: DiagnosticEvent[], counts: Record<string, number> }` |

    ### `obs.billing.total`

    Get the total billing estimate across all agents and providers, combining in-memory data from the current daemon session with historical data from the SQLite observability store.

    | Property       | Value                                                                                    |
    | -------------- | ---------------------------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                                                  |
    | **Parameters** | `{ sinceMs?: number }`                                                                   |
    | **Returns**    | `{ totalCost: number, totalTokens: number, callCount: number, totalCacheSaved: number }` |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "obs.billing.total",
        "params": { "sinceMs": 86400000 },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "totalCost": 1.24,
          "totalTokens": 980000,
          "callCount": 342,
          "totalCacheSaved": 0.38
        },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `obs.billing.usage24h`

    Get an hourly breakdown of token usage for the last 24 hours. Returns one bucket per hour-of-day (0–23) with the aggregate token count for that hour.

    | Property       | Value                                     |
    | -------------- | ----------------------------------------- |
    | **Scope**      | `admin`                                   |
    | **Parameters** | None                                      |
    | **Returns**    | `Array<{ hour: number, tokens: number }>` |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "obs.billing.usage24h",
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": [
          { "hour": 9,  "tokens": 42000 },
          { "hour": 10, "tokens": 88000 },
          { "hour": 11, "tokens": 61000 }
        ],
        "id": 1
      }
      ```
    </CodeGroup>

    ### `obs.billing.byProvider`

    Get token usage and estimated cost broken down by LLM provider. Combines in-memory data from the current session with historical SQLite data for a complete picture.

    | Property       | Value                                                                                                             |
    | -------------- | ----------------------------------------------------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                                                                           |
    | **Parameters** | `{ sinceMs?: number }`                                                                                            |
    | **Returns**    | `{ providers: Array<{ provider: string, inputTokens: number, outputTokens: number, estimatedCostUsd: number }> }` |

    ### `obs.billing.byAgent`

    | Property       | Value                                                                                    |
    | -------------- | ---------------------------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                                                  |
    | **Parameters** | `{ agentId: string, sinceMs?: number }` (agentId required)                               |
    | **Returns**    | `{ totalCost: number, totalTokens: number, callCount: number, totalCacheSaved: number }` |

    ### `obs.billing.bySession`

    | Property       | Value                                                                                    |
    | -------------- | ---------------------------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                                                  |
    | **Parameters** | `{ sessionKey: string, sinceMs?: number }` (sessionKey required)                         |
    | **Returns**    | `{ totalCost: number, totalTokens: number, callCount: number, totalCacheSaved: number }` |

    ### `obs.channels.all`

    Get activity data for all channels, merging live in-memory data with historical SQLite snapshots. In-memory data is authoritative for currently-active channels; SQLite fills in channels from previous daemon sessions.

    | Property       | Value                                                                                           |
    | -------------- | ----------------------------------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                                                         |
    | **Parameters** | None                                                                                            |
    | **Returns**    | `{ channels: Array<{ channelId, channelType, lastActiveAt, messagesSent, messagesReceived }> }` |

    ### `obs.channels.stale`

    | Property       | Value                                                    |
    | -------------- | -------------------------------------------------------- |
    | **Scope**      | `admin`                                                  |
    | **Parameters** | `{ thresholdMs?: number }` (default: 300000 / 5 minutes) |
    | **Returns**    | `{ stale: ChannelActivity[] }`                           |

    ### `obs.channels.get`

    | Property       | Value                                                        |
    | -------------- | ------------------------------------------------------------ |
    | **Scope**      | `admin`                                                      |
    | **Parameters** | `{ channelType: string, channelId: string }` (both required) |
    | **Returns**    | `{ channel: ChannelActivity \| null }`                       |

    ### `obs.delivery.recent`

    | Property       | Value                                                                                                        |
    | -------------- | ------------------------------------------------------------------------------------------------------------ |
    | **Scope**      | `admin`                                                                                                      |
    | **Parameters** | `{ sinceMs?: number, limit?: number, channelId?: string, channelType?: string }`                             |
    | **Returns**    | `{ deliveries: DeliveryTrace[] }` where every trace carries required source and target channel types and IDs |

    ### `obs.delivery.stats`

    Get aggregate message-lifecycle statistics across daemon restarts. `total` includes every completed lifecycle, while `attempted` is the delivery-health denominator (`success + error + timeout`). Intentional filtering and user cancellation are reported separately and do not depress the success rate. Average latency uses attempted deliveries only. Pass `sinceMs` as a duration-ago window; omit it for the complete retained history.

    | Property       | Value                                                                                                                                            |
    | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
    | **Scope**      | `admin`                                                                                                                                          |
    | **Parameters** | `{ sinceMs?: number }` (non-negative integer duration in milliseconds)                                                                           |
    | **Returns**    | `{ total: number, attempted: number, success: number, error: number, timeout: number, filtered: number, aborted: number, avgLatencyMs: number }` |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "obs.delivery.stats",
        "params": { "sinceMs": 86400000 },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "total": 1212,
          "attempted": 1204,
          "success": 1198,
          "error": 4,
          "timeout": 2,
          "filtered": 7,
          "aborted": 1,
          "avgLatencyMs": 142
        },
        "id": 1
      }
      ```
    </CodeGroup>

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "obs.billing.byProvider",
        "params": { "sinceMs": 86400000 },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "providers": [
            { "provider": "anthropic", "inputTokens": 50000, "outputTokens": 12000, "estimatedCostUsd": 0.42 }
          ]
        },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `obs.context.dag`

    | Property       | Value                                                               |
    | -------------- | ------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                             |
    | **Parameters** | `{ agentId?: string }`                                              |
    | **Returns**    | DAG state snapshot with node counts, depths, and compaction history |

    ### `obs.context.pipeline`

    | Property       | Value                                                                  |
    | -------------- | ---------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                                |
    | **Parameters** | `{ agentId?: string }`                                                 |
    | **Returns**    | Context pipeline state including layer configuration and token budgets |

    ### `obs.getCacheStats`

    | Property       | Value                                                                                       |
    | -------------- | ------------------------------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                                                     |
    | **Parameters** | None                                                                                        |
    | **Returns**    | Prompt cache statistics with hit/miss rates, savings estimates, and per-provider breakdowns |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "obs.getCacheStats",
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "totalHits": 1523,
          "totalMisses": 234,
          "hitRate": 0.87,
          "estimatedSavingsUsd": 12.45,
          "providers": {
            "anthropic": { "hits": 1200, "misses": 180 },
            "google": { "hits": 323, "misses": 54 }
          }
        },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `obs.trace.search`

    Correlate trace rows from the last two days of session-index records. Look up by `messageId` (an O(1) LRU resolves it to a `traceId` first), by `traceId` directly, by `chatId`, or by a `since` window with an optional `where` filter. Results are capped at `limit` (max 1000, default 200).

    Synthetic/test-harness session rows are excluded by default; pass `includeSynthetic: true` to include them.

    | Property       | Value                                                                                                                                                                                                                                              |
    | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                                                                                                                                                                                                            |
    | **Parameters** | `{ messageId?: string, traceId?: string, chatId?: string, since?: string, where?: string, limit?: number, includeSynthetic?: boolean }` -- `includeSynthetic` is optional; include synthetic/test-harness sessions, which are excluded by default. |
    | **Returns**    | `{ rows: Array<Record<string, unknown>> }` -- matching session-index rows (newest-first within each day file), capped at `limit`.                                                                                                                  |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "obs.trace.search",
        "params": { "traceId": "f942d38c-e372-43cc-99f1-ead4f0b8582f" },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "rows": [
            { "traceId": "f942d38c-e372-43cc-99f1-ead4f0b8582f", "sessionId": "678314278", "event": "turn_completed", "durationMs": 247740 }
          ]
        },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `obs.explain`

    Assemble a self-contained, redaction-safe post-mortem (an `IncidentReport`) for a single agent session: outcome, cost, timing, per-tool stats, the normalized failures (newest-first), the circuit-breaker timeline, large-result offloads, a one-line summary, and a deterministic `likelyRootCause`. The report is derived from log evidence only -- no LLM is invoked -- so it reproduces the same verdict from the same session forever.

    `outcome.endReason` names the terminal cause. Three degradation causes are named individually rather than collapsing into a generic `error` (and drive `likelyRootCause`): **`context_exhausted`** (the context-window pre-flight guard aborted the run before the model could continue), **`output_starved`** (the final response was truncated at the model's max output tokens; the fix is to raise the agent's `maxTokens` or enable `contextEngine.outputEscalation`), and **`timeout`** (the prompt-level stall budget or makespan ceiling killed the turn; `likelyRootCause` code `prompt_timeout` -- when the trajectory carries the enriched `execution.prompt_timeout` record the verdict is numbers-backed: the stall variant names the binding knob, e.g. `agents.<id>.promptTimeout.promptTimeoutMs`, with the configured budget and elapsed time, and the makespan variant names `agents.<id>.promptTimeout.stallCeilingMultiplier` for a streaming runaway; sessions whose trajectory lacks that record fall back to a generic knob suggestion). A tool-failure root cause still out-ranks them -- they explain the terminal state, not a tool crash. The same causes are aggregated deployment-wide by [`obs.system.health`](#obs-system-health)'s `degradedByCause`.

    An `outcome.endReason` of **`background_pending`** means the foreground turn
    deliberately ended after promoting work; the terminal user outcome belongs to
    the durable background completion and exact-origin delivery lifecycle.
    `likelyRootCause.code` is `background_pending` and its steps point to the
    `background_task:promoted` → `background_task:completed` → either
    `background_task:notified` (including `live_turn_consumed` and fallback routes)
    or `background_task:reentered`. If protected restart recovery could not
    durably reset a task transition, the higher-priority
    `background_recovery_retry_required` verdict points to the retained
    reconciliation authority and the
    `health_signal:background_task_recovery_failed` system-health signal.

    For `context_exhausted` sessions the report carries an optional **`contextBudget`** section -- the terminal per-LLM-call budget equation extracted from the trajectory's `context.budget` records (last record wins): `{ windowTokens, rawContextWindowTokens, windowCapSource, systemTokens, freshTailTokens, budgetedHistoryTokens, keptCount, assembledInputTokens, outputHeadroom, verdict }`. `windowCapSource` names what clamped the effective window below the model's configured `contextWindow`: the exact `contextEngine.budget.*` knob (`effectiveContextCapSmall` / `effectiveContextCapNano`), `served` (the Ollama-served `num_ctx` bound the window -- fix with `OLLAMA_CONTEXT_LENGTH=<configured> ollama serve` or a Modelfile `PARAMETER num_ctx`), `capabilityClass` (the operator's `providers.entries.<id>.capabilities.capabilityClass` pin bound the window -- pin a higher class or remove the pin; the `contextEngine.budget.*` caps do not move this bind), or `none`. `verdict` is the fit-check outcome (`fits` / `downshifted` / `exhausted`). When present, `likelyRootCause` is numbers-backed: it states the assembled-vs-window totals, the binding cap, the system+tools share of the window, and the kept-history count, and its suggested steps name the binding lever (the budget knob to raise, the Ollama serve knobs, or the `capabilityClass` pin) or recommend reducing the active tool surface when tool schemas dominate. The section is absent when the trajectory carries no `context.budget` records; the verdict then falls back to the generic wording.

    When the session ran any memory recalls the report carries an optional **`recall`** section -- the memory-recall outcome aggregated over the trajectory's `memory.recalled` records: `{ recalls, zeroHits, lastLanes, lastFinalCount, rerankerAvailable, crossUserRecalls?, lastCrossUserCount? }` (counts and booleans only -- never the query text or any recalled memory body). `recalls` is how many recalls ran, `zeroHits` how many returned **no** injected memories (a recall miss), and the `last*` fields describe the terminal recall (its lane count and final injected-memory count). **`crossUserRecalls`** (present only when `> 0`) is how many recalls injected at least one memory scoped to a **different user** than the conversation -- the cross-sender privacy signal for agent-scoped recall (one shared agent surfacing sender A's memory into sender B's turn), so "was cross-sender data injected into this turn's context?" is answerable from the always-on report without pre-enabling the opt-in `diagnostics.recallTrace` artifact; **`lastCrossUserCount`** is the terminal recall's cross-user injected count. Counts only -- never the user-ids or bodies. This drives a dedicated **`recall_miss`** `likelyRootCause` verdict: a degraded session whose recalls **all** missed (`zeroHits === recalls`) and that matched no tool/context/breaker cause is root-caused as a recall miss, pointing the operator at the recall **scope** (agent- vs user-scoped) and, for non-Latin queries, the trigram-twin lanes (see [`comis system-health`](#obs-system-health) `health_signal` / `config_posture` embedder). A zero-hit recall on a **healthy** turn is benign -- the agent simply did not need memory -- and never names a cause. The section is absent for sessions with no recall records.

    When the session ran a transcription or synthesis the report carries an optional **`voice`** block reconstructing that turn from the trajectory's `media.stt.*` / `media.tts.*` records: `{ provider, keyless, model?, durationMs?, costUsd?, source?, outcome, errorKind? }`. `keyless` is whether the resolved provider ran without a credential; `source` is the resolved selection rung (`keyless-local` / `follow-main-key` / `fallback` / `explicit`); `outcome` is `"ok"` / `"failed"`; `errorKind` is the domain voice error (`no_keyless_engine` / `auth_required` / `model_load_failed` / `model_download_failed` / `timeout` / `network` / `dependency`) on a failure. `costUsd` is `0` for a keyless turn (free is **visible**) and **omitted** for a keyed turn — the STT/TTS ports return no per-call cost, so Comis never fabricates one. The block is content-free (ids/labels/numbers/booleans only — never the audio or transcript text) and absent for sessions with no voice records. See [Voice → Observability and cost](/media/voice#observability-and-cost).

    When the **Verified Learning** outcome signal observed the turn the report carries an optional **`learning`** block reconstructed from the trajectory's `learning.outcome_observed` records: `{ outcomeResolved, outcome?, sources, skillsUsed, skillFailures, synthesisAbstained, skillsPromoted?, skillsDemoted?, failuresAttributed? }` (counts / ids / closed enums only — never a message body, a confidence value, or a recalled/skill id). The three optional counts surface the session's learning *transitions* in one call: `skillsPromoted`/`skillsDemoted` (candidate→active promotions / demotions, from `learning.skill_promoted`/`skill_demoted`) and `failuresAttributed` (memories that accrued a **corroborated** failure this session — the eviction-causation precursor, from `learning.memory_failure_attributed`); each is present only when it fired (additive, schemaVersion 1). `outcomeResolved` is `false` when the learning shadow saw a **finished** trajectory but **no** signal tier produced a resolvable outcome; `outcome` is the fused verdict (`success` / `failure` / `corrected` / `unknown`) when one resolved; `sources` are the contributing signal sources (`tool` / `pipeline` / `correction` / `judge` / `reaction` / `explicit`, deduped). This drives a dedicated **`outcome_unresolved`** `likelyRootCause` verdict — a **benign, lowest-priority** diagnostic that fires only when `outcomeResolved` is `false` and **no** acute cause matched (Defer ≠ Retry: an unresolved outcome is not a failure, so every tool-failure cause out-ranks it). It is **distinct** from an explicit `unknown` outcome (which IS a resolution); the verdict means the signal was never resolvable — e.g. a conversational turn with no deterministic tool/pipeline signal AND no judge verdict (the cost-gated LLM judge fallback is off, or it too abstained `unknown`). The **procedural-skill** fields are populated when the [`learning`](/reference/config-yaml#learning-agents-learning) layer is enabled: `skillsUsed` is the ids of the learned procedures whose `<location>` a `read` in the turn matched, `skillFailures` is the used-doc ids in a **failed / corrected** trajectory (a learned procedure implicated in a bad outcome — content-free ids only), and `synthesisAbstained` is `true` when the offline reflection cron deferred (the agent's model tier was below the capability gate). They drive two **benign, low-priority** `likelyRootCause` verdicts ranked below every acute cause but above the generic `outcome_unresolved`: **`learned_skill_failing`** (fires on a non-empty `skillFailures`; names the failing skill ids and points at [`comis memory skills`](/reference/cli#comis-memory)) and **`synthesis_abstained_low_capability`** (fires on `synthesisAbstained`; **benign** — Defer ≠ Retry, an abstain is never a failure or a retry). A learned procedure repeatedly used in failed/corrected reuse is **demoted** (`active` → `stale` → `archived`, corroboration-gated), which drops it from the read-only surface; its lifecycle emits two **counts-only** trajectory events — **`learning:skill_promoted`** (a candidate crossed the proof bar to `active`) and **`learning:skill_demoted`** (an active procedure weakened to `stale`) — both carrying `{ agentId, count, timestamp }` only. The block is absent for sessions with no learning records (the layer is per-agent default-off); the per-agent outcome coverage is aggregated by [`comis memory learning`](/reference/cli#comis-memory) and the admission funnel by [`comis memory skills`](/reference/cli#comis-memory).

    The **reflection cron** itself emits two **counts-only** trajectory events, surfaced through `comis explain` / `comis system-health` and bridged so a reflection run is reconstructable per session — never a doc body:

    * **`reflect:admitted`** — `{ agentId, count, timestamp }`: the number of candidate docs admitted to the store this run.
    * **`reflect:funnel`** — `{ agentId, synthesized, validated, admitted, maxClusterCardinality, distinctTopicKeys, untrustedDrops, nameLengthRejections, skipped, sourceTrajectoryCount, totalSourceChars, admissionOutcome, timestamp }`: the whole admission funnel as counts plus one content-free closed enum. `maxClusterCardinality` is the largest distinct `(session, sender)` corroboration size (a value of `1` means a single uncorroborated instance, so admission correctly refused — the same conservatism that defeats poisoning). `distinctTopicKeys` is the under-merge **discriminator** — `synthesized > 1` with `distinctTopicKeys > 1` and `maxClusterCardinality < 2` means the successes landed on *separate* topicKeys (under-merge → the LLM-tag-fallback trigger), vs `distinctTopicKeys: 1, maxClusterCardinality ≥ 2` = genuinely corroborated. `untrustedDrops` is the magnitude behind an `untrusted_origin` verdict; `sourceTrajectoryCount`/`totalSourceChars` distinguish an empty-source wiring gap (0 chars in) from an LLM-yield (real chars in, nothing admitted). `admissionOutcome` is the **`reflectOutcome`** verdict — a closed string union, one of: `admitted`, `no_successes` (no trusted-origin success cleared SELECT), `untrusted_origin` (all successes dropped at SELECT for an external-trust source), `uncorroborated` (`cardinality < 2`), `empty_reflection`, `rejected_name_length` (a doc name over the cap), or `rejected_validation` — so `comis explain` answers "why was 0 admitted" from one field.

    The wrongness-based **forgetting** sweep emits its own trajectory signals, surfaced through the same `comis explain` / `comis system-health` lenses and **counts only** — never a memory body: **`learning:memory_demoted`** / **`learning:memory_evicted`** (the per-sweep soft-eviction counts), plus a once-per-run **`learning:lifecycle_swept`** (`{ agentId, scanned, promoted, demoted, evicted, timestamp }`) summary. The summary is folded onto the cron run history (`cron.runs jobName "Memory lifecycle"` shows `scanned/evicted/demoted` per sweep) and rolled into a daemon-wide **`memory_lifecycle`** `comis system-health` finding (run count + summed evicted/demoted; `evicted=0` is usually healthy — no corroborated-wrong / dormant candidates — not a fault). The corroborated-failure **accrual** that *precedes* eviction emits **`learning:memory_failure_attributed`** (`{ agentId, count, timestamp }`) so "why did/didn't this memory evict" has an event trail. They are bridged onto the trajectory so an eviction sweep is reconstructable per session; they are part of the one learning layer (`agents.<id>.learning.enabled`, force-disabled by `memory.enabled: false`) and read `learning.forget.*`. Recall ranking is the fixed `rag.scoring` fusion — there is no learned recall weight. The per-memory recall **score breakdown** (the `recall` records) still carries a `usefulnessOutcomeShare` annotation — the outcome-attributed component of a memory's usefulness factor (the bounded `recordUsage`/`recordFailure` feed), distinct from its lexical relevance base (a derived, normalized share — never a raw learned weight).

    > **One reflection cron.** There are no standalone user-representation or consolidation/reasoning crons and no dedicated revision/generalization telemetry events: that work runs inside the one reflection cron as `kind:"profile"` / `kind:"topic"` docs (see [memory config](/agents/memory-config#the-one-learning-reflection-engine)). A profile revision surfaces through the `reflect:*` funnel above, not a dedicated event.

    For an **unattended coding-CLI drive** (a webhook/cron turn that opens a `claude`/terminal drive), two dedicated `likelyRootCause` verdicts diagnose the two ways such a drive strands — both derived from the bridged terminal-drive trajectory records (counts / closed enums only, never screen text): **`terminal_drive_opened_without_task`** fires when a drive was created (`terminal_session_create.ok ≥ 1`) but NO task was ever delivered (zero `terminal_session_send_text` successes) — the driven CLI sat idle and the build never started (it cites the backgrounding reason from `terminal.drive_promoted` when present, and out-ranks the `completed_with_tool_errors` catch-all since a stray failure during the stall is incidental). **`terminal_drive_evicted`** fires when the reaper cut a drive short — folded from `terminal.session_evicted` into a `terminalDriveEvicted { reason, idleMs, wasProducing }` signal — but ONLY on the `idle` and `wall_clock` caps (a drive quiet past `worker.idleTtlMs`, or older than the entry's `limits.wallClockMs`), never the benign `max_sessions` LRU or the deliberate `max_interactions` budget. `wasProducing` is derived (no new event) from whether a `producing` `terminal.drive_promoted` preceded the eviction: an idle-reap of a drive that HAD been producing is the acute canary that the producing-drive keep-alive regressed, and the verdict names it as such. Both fire regardless of `endReason` (the turn that opened the drive often ended `success`), so a reaper-killed autonomous drive is a one-call diagnosis instead of a hand-correlation of the daemon WARN against the last activity.

    When the session ran any [`orchestrate`](/agent-tools/orchestrate) scripts the report carries an optional **`orchestrate`** array — one content-free entry per run, reconstructed from the run's `orchestrate.run_summary` + per-run `capability.audited` trajectory records: `{ runId, leaseId?, outcome, durationMs, exitCode, failureClass?, toolCalls[], resultRefs: { count, bytes }, savings? }` (ids, closed enums, counts, and token **estimates** only — never the script body, stdout, tool args, or the stderr tail). `outcome` derives from `exitCode` (`0` → `success`); `failureClass` is a closed enum (`timeout` / `stdout_cap` / `nonzero_exit` / `spawn_fail` / `lease_absent`) present only on a failed run; each `toolCalls` entry (`{ tool, capability, decision, count }`) is attributed to the run by its **per-run child leaseId**, so an in-jail `decision: "deny"` groups under the run that attempted it, not the shared assembly lease that spans the session; `savings` (`{ estSavedTokens, savedRatio }`) is the measured token-savings estimate, present only when the run materialized ResultRefs. This drives a deterministic **`orchestrate_failed`** `likelyRootCause` verdict — it fires when any run's `outcome` is `failure` **or** any `toolCalls` entry is a `deny`, names the failed-of-total run count with the de-duplicated `failureClass` / `exitCode` / denied-capability sets, and (keyed on a signal absent from a non-autonomy session) never reorders the other verdicts. Note that `capability:audited` records the **authorization** decision only — an *allowed* tool call whose result then failed is not in that stream; the run-level `failureClass` + the failing tool's own bounded stderr tail cover that half. The daemon-wide savings roll-up is [`obs.system.health`](#obs-system-health)'s `orchestrate_efficiency` finding. The section is absent for sessions that ran no orchestrate scripts.

    Accepts ONE of `sessionKey` / `traceId` / `rootRunId` (at least one is required). A `traceId` *or* a `rootRunId` is canonicalized to its `sessionKey` first, so by-trace, by-session, and by-run produce the identical report. `rootRunId` is an autonomy run's id — the synthetic in-process `root-session-<sessionKey>` (resolved by a pure prefix-strip) or a real spawned/socket root (resolved by scanning the day-keyed session-index for the run's `capability.audited` record). It is the [`obs.system.health`](#obs-system-health) → `obs.explain` drill-down ref: pass the system report's `autonomy.worstRootRunId` to render that run's `spawnTree`. An unresolvable `rootRunId` (or `traceId`) yields the honest `session_not_found` marker naming the ref that missed — never a clean-looking empty report. There is **no by-job-id form**: a cron [wake-gate](/agent-tools/scheduling#wake-gate-run-the-model-only-when-it-matters) skip opens no session, so a cron **job id** is not a valid input — read a job's skipped fires from [`cron.runs`](#cron-runs) instead.

    When a scheduled fire's wake-gate **woke** the model, the resulting session's report folds an optional content-free `cronWakeGate` fact — `{ jobId, wake, durationMs, toolCalls, estTurnsSaved }` (ids and counts only, never the gate's gathered finding or script). It is present only when the woke fire ran in a session the recorder had already opened; a **skipped** fire runs no model and reaches no report. The cross-job skip-rate / turns-saved rollup is on [`obs.system.health`](#obs-system-health)'s `cronWakeGate` block.

    `depth: "summary"` (the default) bounds the serialized report to roughly 6 KB (about 1,500 tokens) and caps the failure list; `depth: "full"` relaxes the array caps. At BOTH depths the report is digest-only: no raw tool-output body is ever inlined (oversized previews and untrusted external content collapse to a `resultDigest` fingerprint), and an honest `truncations[]` ledger records anything the bounding pass dropped.

    An optional `coverage` block reports READ-coverage -- whether the trajectory, rollup, and each offload pointer were actually located and resolved (`{ trajectory: { found, records }, rollup: { present }, offloads: { pointersResolved, pointersTotal } }`) -- distinct from `truncations[]`, which records what the size-bounding pass dropped. A degraded report is self-evident from it (e.g. `records: 0` or `pointersResolved < pointersTotal`) rather than masquerading as a clean session. When the session resolved to real on-disk artifacts, `coverage.sources` carries the source **paths** (pointers, never bodies): `{ session, trajectory }`. The distinction is load-bearing for numeric/value reconciliation -- the `.trajectory.jsonl` holds tool-call **provenance** only (`toolName` / `success` / `durationMs`; the result body is deliberately kept out of the event stream for secret-egress safety), while the co-located raw session `.jsonl` (`sources.session`) holds the tool-result **values** the model actually saw. To reconcile a reported figure to the tool result that produced it, read `session` (values), not `trajectory` (provenance); large results additionally offload to disk (`offloads[].diskPathRel`).

    `coverage.toolStats` reconciles this report's per-tool `toolStats` (the **whole-session** trajectory union) against the persisted per-session rollup that [`obs.system.health`](#obs-system-health) reads (the **latest-execution** rollup -- it is built per execution and the session metadata is overwritten each turn). The two lenses read structurally-different sources, so they CAN differ for the same session -- but only in one direction: the rollup is a subset of the trajectory (`rollup.{ok,failed} <= trajectory.{ok,failed}` per tool). The block (`{ reconciled, rollupSource: "last-execution", divergentTools: [{ tool, rollup: { ok, failed }, trajectory: { ok, failed } }] }`) makes that gap transparent so `comis explain` and `comis system-health` can never silently contradict: `divergentTools[]` names each tool whose persisted rollup differs from the trajectory with both count pairs, and `reconciled` is the directional invariant (a rollup that OVER-counts the trajectory -- the forbidden direction, including a tool present in the rollup but absent from the trajectory -- flips it to `false` and surfaces the offending tool instead of hiding it).

    | Property       | Value                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
    | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
    | **Parameters** | `{ sessionKey?: string, traceId?: string, rootRunId?: string, depth?: "summary" \| "full", includeSynthetic?: boolean }` -- at least one of `sessionKey` / `traceId` / `rootRunId` required. `rootRunId` is an autonomy run's id, canonicalized to its session before assembly (a `root-` synthetic root resolves by prefix-strip; a real spawned root by a session-index scan) and renders the run's `spawnTree`. `includeSynthetic` is optional; include synthetic/test-harness sessions, which are excluded by default. |
    | **Returns**    | An `IncidentReport`: `{ schemaVersion, sessionKey, traceId, agentId, channel, outcome: { endReason, degraded, severity }, cost: { costUsd, totalTokens, cacheReadRatio }, timing: { durationMs, turnCount }, toolStats, failures[], breakerTimeline[], offloads[], contextBudget?, recall?, voice?, learning?, orchestrate?, cronWakeGate?: { jobId, wake, durationMs, toolCalls, estTurnsSaved }, summary, likelyRootCause, suggestedNextSteps[], truncations[], coverage? }`                                             |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "obs.explain",
        "params": { "sessionKey": "default:agent:default:user_a:telegram:peer:user_a", "depth": "summary" },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "schemaVersion": 1,
          "sessionKey": "default:agent:default:user_a:telegram:peer:user_a",
          "outcome": { "endReason": "completed_with_tool_errors", "degraded": true, "severity": "degraded" },
          "cost": { "costUsd": 1.320669, "totalTokens": 735800, "cacheReadRatio": 0 },
          "timing": { "durationMs": 247740, "turnCount": 25 },
          "toolStats": { "web_fetch": { "ok": 2, "failed": 8, "topErrorKind": "dependency" } },
          "failures": [
            { "seq": 11, "toolName": "web_fetch", "errorKind": "dependency", "resultDigest": "386bf5565d09", "resultBytes": 1500, "errorPreview": "[redacted:untrusted-content digest:386bf5565d09]" }
          ],
          "breakerTimeline": [ { "seq": 20, "event": "opened", "toolName": "web_fetch" } ],
          "voice": { "provider": "local", "keyless": true, "model": "base", "durationMs": 1180, "costUsd": 0, "source": "keyless-local", "outcome": "ok" },
          "summary": "14 tool failures across 25 turns; endReason=completed_with_tool_errors",
          "likelyRootCause": {
            "code": "content_heuristic_misclassification",
            "detail": "content-heuristic misclassification (tool=web_fetch, token=status): a substring match in a large tool body flipped status-200 successes to failures",
            "suggestedNextSteps": ["audit the web_fetch failureDetector rule for the matched token 'status'", "obs.explain depth=full"]
          },
          "truncations": [],
          "coverage": {
            "trajectory": { "found": true, "records": 25 },
            "rollup": { "present": true },
            "offloads": { "pointersResolved": 1, "pointersTotal": 1 },
            "toolStats": { "reconciled": true, "rollupSource": "last-execution", "divergentTools": [ { "tool": "web_fetch", "rollup": { "ok": 2, "failed": 3 }, "trajectory": { "ok": 2, "failed": 8 } } ] }
          }
        },
        "id": 1
      }
      ```
    </CodeGroup>

    <Note>
      The `comis explain` CLI command (see the [CLI reference](/reference/cli)) and the `obs_query` agent `explain` / `session_report` actions surface this same report.
    </Note>

    ### `obs.system.health`

    Assemble a bounded, redaction-safe **cross-session** system-health triage (a `SystemHealthReport`) over a recent time window: how many sessions ran and how many degraded, the degraded sessions bucketed by named cause (`degradedByCause`), the merged top error kinds, the total circuit-breaker trips, a per-tool ok/failed rollup, the window cost, the recent activity (agents, channels, exit reasons), the recurring `findings[]` (counts + short codes + hints only -- no raw WARN bodies), and a deterministic `likelyRootCause`. Like `obs.explain`, the report is derived from log + diagnostics evidence only -- no LLM is invoked -- so the same window reproduces the same verdict.

    `degradedByCause` is the system-level **degradation detector**: a bounded `{ <endReason>: count }` map (counts only) over the degraded sessions in the window, keyed by the named terminal cause -- e.g. `{ "context_exhausted": 5, "output_starved": 2 }` answers "how many sessions degraded, and *why*" at a glance. Only degraded sessions contribute; a session whose cause is missing folds into `"unknown"`. Two named degradation causes are worth calling out -- `context_exhausted` (the context-window pre-flight guard aborted the run; run `obs.explain` on the session for the numbers-backed `contextBudget` verdict) and `output_starved` (the final response was truncated at the model's max output tokens) -- both named here and in [`obs.explain`](#obs-explain)'s `outcome.endReason` + `likelyRootCause`, so neither collapses into a generic `error`.

    `likelyRootCause` ranks **acute over chronic**: any degraded session with a named cause (`system_acute_degradation`, naming the dominant `degradedByCause` entry) out-ranks the standing config-posture (`system_config_posture`) and recurring-health-signal (`system_recurring_health_signal`) verdicts; only a deployment-wide degradation spike (`system_high_degraded_rate`) ranks above it. A degraded **autonomy** run (an unattended run orphaned on restart, revoked/killed, or aborted by the capability-denial breaker) ranks **first** of all -- `system_autonomy_degradation` names the worst run's `rootRunId` so you can paste it straight into `comis explain` (the unattended-mode signal out-prioritizes the session-level rules). Routine `session_rebase` continuations (a restart resuming at the store's max seq) ingest at `info` severity, so they never inflate the warning findings.

    The boot-time `config_posture` diagnostic row carries `servedBelowConfiguredCount` in its details JSON -- the number of providers whose Ollama-served window (`num_ctx`) was below the configured `contextWindow` at that boot (a **count only**, never provider names; the posture record severity is `warning` when it is > 0). When the **latest** posture row's count is non-zero, `findings[]` gains the dedicated `config_posture:served_below_configured` code with that count -- latest-row semantics because posture is standing state, not cumulative (a healthy restart clears the finding). The hint names the `OLLAMA_CONTEXT_LENGTH` / Modelfile `PARAMETER num_ctx` knobs; a malformed posture row (or one missing the count field) folds to 0 defensively (no finding, never an assembly error). For triaging this finding on a local deployment -- model choice, window sizing, and the full knob map -- see the [Local models playbook](/operations/local-models).

    When transcriptions/syntheses degraded across the window, `findings[]` gains a `voice_health` code: the **count of degraded STT/TTS turns** and the **dominant voice `errorKind`** in the `detail` (e.g. `model_load_failed`, `auth_required`), with a hint pointing at `comis explain` and -- for the keyless-engine load/download failures -- the whisper model cache + disk + the local engine probe. The finding rolls up the `voice_degraded` `health_signal` diagnostic rows the daemon voice path emits on a failure; like every system finding it carries **counts + a short code + a closed errorKind label + a static hint only -- no raw provider body or secret, safe to paste**. A malformed row folds to a no-count defensively (never an assembly error), and there is no finding when no voice turn degraded. See [Voice → Observability and cost](/media/voice#observability-and-cost).

    When small/local-tier models author pipeline DAGs over the window, `findings[]` gains a `pipeline_authoring` code reporting the **small-model pipeline-authoring failure rate** -- the count of small/nano-tier authorings that failed schema validation over the small/nano-tier total, plus the rate. It rolls up the `pipeline_authoring` `health_signal` diagnostic rows the daemon emits from each [`pipeline:authored`](/developer-guide/event-bus) event (`define`/`execute`); like every system finding it carries **counts + a short code + a static hint only -- no pipeline body, no node `type_config`, no secret, safe to paste**. The hint names the small-model-DAG-authoring build/defer gate. The finding fires only when at least one small/nano-tier authoring ran in the window; `mid`- and `unknown`-tier authorings are in neither cohort. The denominator counts every contract-parse-reachable authoring invocation -- a present-but-malformed call that fails the request-contract parse **or** the graph parse/validate step is counted as invalid; only the bespoke pre-checks (a `define` with no `nodes`, an `execute` rejected by the agent-to-agent policy gate) are excluded, since neither is an authoring attempt.

    This finding is the metric behind a **pre-committed, measure-first decision rule**. The report carries a deterministic `pipelineAuthoringGate` verdict `{ buildAuthor, reason }`, computed by a pure reducer over the windowed authoring rows -- the same window always reproduces the same verdict (no LLM, no clock). **Native small-model-authorable DAG support is built only when this rule fires `buildAuthor: true`**, which happens only when both gates pass: small-tier invocations are non-trivial (**at least 20** over the window) AND small-tier author-validity is materially below frontier (**at least 15 percentage points** below). Otherwise it defers -- and with no telemetry, the default state for a fresh deployment, it returns `buildAuthor: false` with a reason naming insufficient telemetry. The thresholds are pinned in code so a silent retune is caught; the verdict is auditable from `comis system-health` once the daemon has accumulated authoring telemetry.

    When the daemon ran [`orchestrate`](/agent-tools/orchestrate) scripts over the window, `findings[]` gains an **`orchestrate_efficiency`** code — the **measured token savings** from those runs materializing high-volume tool results as ResultRefs instead of re-entering them into context. It rolls up the content-free `orchestrate_efficiency` `health_signal` rows the daemon emits from each `orchestrate:run_summary` event: the `detail` names the run count and the summed **estimated** tokens saved (plus a degraded-run count when any run recorded a `failureClass`), and the `count` is the run count. Like every system finding it carries **counts + a short code + token estimates + a static hint only — no script body, stdout, or secret, safe to paste**; the hint points at `comis explain` for the per-run savings breakdown. A completed run — success or a classified failure — is standing efficiency signal, not a system degrade, so it rides `info` severity and never inflates the degraded count; the finding fires only when at least one orchestrate run ran in the window.

    When the daemon runs **unattended/durable** runs, the report carries an optional **`autonomy`** block -- the cross-run autonomy-health slice: `{ runs: { total, degraded, degradedRate }, orphaned, resumed, revoked, killed, breakerTrips, denialBreakerTrips, budgetBreaches, costUsd, worstRootRunId? }` (**counts + the worst run's id only** -- never a lease bearer, an orphan reason body, or any secret; safe to paste). The run counts + degraded rate come from the **crash-surviving** `durable_run_checkpoints` table (`countByStatus` -- autonomy runs *are* durable runs by construction, so this survives a hard crash that would lose an in-process event), where `degraded = orphaned + revoked`. `resumed` (healthy crash-recovery) and `killed` are recovered from the event-sourced `health_signal` rows -- `killed` is separable from `revoked` *only* because a hard `run.kill` and a cooperative `lease.revoke` (which both flip the durable status to `revoked` indistinguishably in the table) emit **distinct** events. `breakerTrips` is the **tool-failure** breaker count (the synthetic-excluded `breakerTripTotal` read-back -- summed per-session `breakerTripCount`). `denialBreakerTrips` is the distinct **capability-denial** breaker count -- N consecutive capability/quota floor-blocks aborted + killed a run tree. It is **event-sourced** from the content-free `autonomy_denial_breaker` `health_signal` rows, NOT a session rollup: a denial-breaker abort is never a session `endReason` and never a `breakerTripCount`, so `breakerTrips` can never see it, and the aborted run lands in durable status `completed` (so it is also 0 in `orphaned`/`revoked`/`killed`) -- this separable count is its **only** system surface. `budgetBreaches` is the per-node token-budget breach count; `costUsd` is the window's autonomy-inclusive cost. `worstRootRunId` is the highest-severity degraded run (orphaned > denial-breaker > killed > revoked) -- paste it into [`comis explain`](#obs-explain) for its spawn-tree. The same labels also surface as dedicated `findings[]` codes (`durable_orphaned` / `autonomy_revoked` / `autonomy_killed` / `autonomy_denial_breaker`). The block is **absent** when the durable store is unwired -- e.g. the daemon-less `comis system-health --offline` CLI, or a non-durability boot -- an honest coverage degradation, not a silent zero.

    When cron jobs ran a [wake-gate](/agent-tools/scheduling#wake-gate-run-the-model-only-when-it-matters) over the window, the report carries an optional **`cronWakeGate`** efficiency block -- `{ fires: { total, skipped, skipRate, failedOpen, failOpenRate }, turnsSaved, toolCalls, perAgent: [{ agentId, fires, skipped, skipRate, failedOpen, failOpenRate, turnsSaved, toolCalls }] }` (**counts + agent ids only** -- never the gate's gathered finding, its script source, or a secret; safe to paste) -- rolled up from the content-free `cron_wake_gate` diagnostic rows one gated fire writes each. It carries three legibility properties: **suppression** (a `perAgent[].skipRate` of `1.0` is a gate that never wakes the model -- either working hard or silently poisoned, visible either way); **broken-gate** (`failOpenRate` -- the share of fires that FAILED OPEN, i.e. the gate crashed / timed out / over-capped / mint-faulted and the model was woken defensively; a gate failing open every fire saves nothing and costs its own run, yet otherwise reads `skipRate 0` like a busy monitor -- the signal symmetric to a 100% skip-rate); and **net cost** (`toolCalls`, the gate's own cap-call cost, sits beside `turnsSaved`, the avoided model turns, so a gate that costs more than it saves is legible). The same rollup drives the `cron_wake_gate_efficiency` `findings[]` code, whose hint names `cron.runs jobName "<job>"` for the per-fire decisions and flags a 100% skip-rate on an expected monitor, a high `failedOpen` count, or `toolCalls` exceeding `turnsSaved`, as the signals to inspect. The block is **absent** when no gated fire ran in the window (an honest omit, not a zero).

    This is the cross-session **sibling** of [`obs.explain`](#obs-explain): `obs.explain` post-mortems ONE session; `obs.system.health` rolls up a WINDOW of sessions.

    `sinceHours` is optional; the **24h** default is applied in the handler. The report is digest-only: `findings[]` is capped top-N and `topErrorKinds` is merged + capped (counts only), with an honest `truncations[]` ledger recording anything the bounding pass dropped. An optional `coverage` block reports READ-coverage of the system sources -- the session-summary store (`{ found, rows }`), the multi-day session index (`{ daysRead, daysMissing }`), and the billing source (`{ present }`) -- so a silently-empty window (e.g. `rows: 0` or `daysMissing > 0`) is self-evident rather than masquerading as a clean zero-activity system.

    | Property       | Value                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
    | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
    | **Parameters** | `{ sinceHours?: number }` -- the aggregation window in hours (positive). Optional; defaults to `24` in the handler.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
    | **Returns**    | A `SystemHealthReport`: `{ schemaVersion, windowHours, sessions: { total, degraded, degradedRate }, degradedByCause, topErrorKinds[], breakerTripTotal, toolStats, cost: { costUsd, totalTokens }, activity: { activeAgents[], activeChannels[], exitReasons, turnTotal, tokenTotal }, findings[], likelyRootCause, pipelineAuthoringGate?: { buildAuthor, reason }, autonomy?: { runs: { total, degraded, degradedRate }, orphaned, resumed, revoked, killed, breakerTrips, denialBreakerTrips, budgetBreaches, costUsd, worstRootRunId? }, cronWakeGate?: { fires: { total, skipped, skipRate }, turnsSaved, toolCalls, perAgent[] }, suggestedNextSteps[], truncations[], coverage? }` |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "obs.system.health",
        "params": { "sinceHours": 24 },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "schemaVersion": 1,
          "windowHours": 24,
          "sessions": { "total": 40, "degraded": 22, "degradedRate": 0.55 },
          "degradedByCause": { "context_exhausted": 12, "completed_with_tool_errors": 7, "output_starved": 3 },
          "topErrorKinds": [ { "kind": "dependency", "count": 18 } ],
          "breakerTripTotal": 3,
          "toolStats": { "web_fetch": { "ok": 2, "failed": 8 } },
          "cost": { "costUsd": 1.320669, "totalTokens": 735800 },
          "activity": {
            "activeAgents": ["default"],
            "activeChannels": ["telegram"],
            "exitReasons": { "completed_with_tool_errors": 22 },
            "turnTotal": 500,
            "tokenTotal": 735800
          },
          "findings": [
            { "code": "system_recurring_health_signal", "detail": "lcd_divergence recurred across the window", "count": 14, "hint": "inspect the LCD compaction path" },
            { "code": "voice_health", "detail": "6 degraded STT/TTS turn(s); top errorKind model_load_failed", "count": 6, "hint": "run `comis explain` on an affected voice session; for model_load_failed/model_download_failed check the whisper model cache + disk + the local engine probe (a keyless engine), or set the provider's audio API key for auth_required" },
            { "code": "pipeline_authoring", "detail": "3/12 small-tier pipeline authorings invalid (25.0%)", "count": 3, "hint": "small-model pipeline-authoring failure rate; gates native small-model-authorable DAGs -- see pipelineAuthoringGate" }
          ],
          "likelyRootCause": {
            "code": "system_high_degraded_rate",
            "detail": "over half the window's sessions degraded",
            "suggestedNextSteps": ["obs.explain the worst session", "raise the breaker threshold"]
          },
          "pipelineAuthoringGate": { "buildAuthor": false, "reason": "insufficient telemetry: 12 small-tier invocations (< 20)" },
          "autonomy": { "runs": { "total": 20, "degraded": 3, "degradedRate": 0.15 }, "orphaned": 2, "resumed": 4, "revoked": 1, "killed": 0, "breakerTrips": 1, "denialBreakerTrips": 2, "budgetBreaches": 0, "costUsd": 0.42, "worstRootRunId": "root-2f9c1a" },
          "suggestedNextSteps": ["raise the breaker threshold"],
          "truncations": [],
          "coverage": { "sessionSummary": { "found": true, "rows": 40 }, "sessionIndex": { "daysRead": 1, "daysMissing": 0 }, "billing": { "present": true } }
        },
        "id": 1
      }
      ```
    </CodeGroup>

    <Note>
      The `comis system-health` CLI command (see the [CLI reference](/reference/cli)) and the `obs_query` agent `system_health` action surface this same report. It is the cross-session sibling of `comis explain` -- NOT to be confused with `comis health`, the LOCAL daemon doctor (which runs no RPC).
    </Note>

    ### `obs.audit.query`

    Query the **durable** security-decision audit -- the `obs_audit_events` SQLite table the daemon persists (secret access, injection detection, injection-rate breach, canary leaks, implied-tool-call isolation, command blocks, sandbox-downgrade refusals, and classified `audit:event` actions). Admin-scoped, deterministic, and **content-free**: each row carries ids, the closed-enum fields (`kind` / `classification` / `outcome` / `severity`), and a scrubbed `refs` blob -- **never a secret value** (the rows are scrubbed at write). Every filter is optional; an absent filter widens the scan. `limit` defaults to `200` and is clamped to `1000` (bounded reports).

    The same query is surfaced by the `comis security audit-log` CLI and the `obs_query` agent tool's `audit` action -- all three are the read surface onto the audit sink. This is the durability payoff: unlike the live `audit:event` SSE feed, these events survive a daemon restart.

    | Property       | Value                                                                                                                                                                                                                        |
    | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                                                                                                                                                                                      |
    | **Parameters** | `{ kind?, classification?, agentId?, tenant?, outcome?, since?: number, until?: number, limit?: number }` -- `tenant: ""` matches the system-scoped (tenant-less) events; `since`/`until` are inclusive epoch-ms bounds.     |
    | **Returns**    | `{ rows: AuditEventRow[] }`, ORDER BY ts DESC. Each `AuditEventRow` = `{ id, tenantId, agentId, ts, kind, classification, action, actor, outcome, severity, traceId, refs }` (content-free; `refs` is a scrubbed JSON blob). |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "obs.audit.query",
        "params": { "kind": "secret_access", "agentId": "customer-support", "limit": 20 },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "rows": [
            {
              "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
              "tenantId": "default",
              "agentId": "customer-support",
              "ts": 1717000000000,
              "kind": "secret_access",
              "classification": "read",
              "action": "secrets.get",
              "actor": null,
              "outcome": "success",
              "severity": "info",
              "traceId": "trace-7e8f9a0b1c2d",
              "refs": "{\"secretName\":\"OPENAI_API_KEY\"}"
            }
          ]
        },
        "id": 1
      }
      ```
    </CodeGroup>

    <Note>
      The `comis security audit-log` CLI command (see the [CLI reference](/reference/cli)) and the `obs_query` agent `audit` action surface this same query. The durable sink + the greppable `~/.comis/logs/security-audit.jsonl` are documented in [Audit Logging](/security/audit).
    </Note>

    ### `obs.reset`

    | Property       | Value                                                                                                           |
    | -------------- | --------------------------------------------------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                                                                         |
    | **Parameters** | None                                                                                                            |
    | **Returns**    | `{ reset: true, rowsDeleted: { tokenUsage: number, delivery: number, diagnostics: number, channels: number } }` |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "obs.reset",
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "reset": true,
          "rowsDeleted": {
            "tokenUsage": 1523,
            "delivery": 892,
            "diagnostics": 341,
            "channels": 12
          }
        },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `obs.reset.table`

    | Property       | Value                                                                                             |
    | -------------- | ------------------------------------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                                                           |
    | **Parameters** | `{ table: string }` -- valid values: `"token_usage"`, `"delivery"`, `"diagnostics"`, `"channels"` |
    | **Returns**    | `{ reset: true, table: string, rowsDeleted: number }`                                             |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "obs.reset.table",
        "params": { "table": "delivery" },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "reset": true,
          "table": "delivery",
          "rowsDeleted": 892
        },
        "id": 1
      }
      ```
    </CodeGroup>

    <Warning>
      These methods permanently delete observability data. The web console requires typing RESET as confirmation before calling `obs.reset`.
    </Warning>
  </Accordion>

  <Accordion title="session (12 methods)">
    Session management for agent conversations. Client-facing methods use `rpc` scope; administrative operations use `admin` scope.

    | Method                       | Scope   | Description                                                                                            |
    | ---------------------------- | ------- | ------------------------------------------------------------------------------------------------------ |
    | `session.send`               | `rpc`   | Send a message to an existing session                                                                  |
    | `session.spawn`              | `rpc`   | Spawn a sub-agent session                                                                              |
    | `session.status`             | `rpc`   | Get session status                                                                                     |
    | `session.history`            | `rpc`   | Get session conversation history                                                                       |
    | `session.search`             | `rpc`   | Search session content by query                                                                        |
    | `session.run_status`         | `rpc`   | Get live status of a specific sub-agent run                                                            |
    | `session.list`               | `rpc`   | List sessions in an exact tenant and agent scope                                                       |
    | `session.delete`             | `admin` | Delete a session                                                                                       |
    | `session.reset`              | `rpc`   | Reset one exact conversation while preserving identity                                                 |
    | `session.export`             | `admin` | Export session data                                                                                    |
    | `session.compact`            | `rpc`   | Compact one exact conversation                                                                         |
    | `session.reset_conversation` | `admin` | Complete cross-mode conversation reset (LCD history + working session transcript + pi runtime session) |

    ### `session.status`

    Get the current resource usage and configuration summary for the calling agent's session: model, token consumption, steps executed, and the configured step limit.

    | Property       | Value                                                                                                                   |
    | -------------- | ----------------------------------------------------------------------------------------------------------------------- |
    | **Scope**      | `rpc`                                                                                                                   |
    | **Parameters** | None (resolved from the calling agent context)                                                                          |
    | **Returns**    | `{ model: string, agentName: string, tokensUsed: { totalTokens, totalCost }, stepsExecuted: number, maxSteps: number }` |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "session.status",
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "model": "claude-sonnet-4-5-20250929",
          "agentName": "Comis",
          "tokensUsed": { "totalTokens": 48200, "totalCost": 0.14 },
          "stepsExecuted": 12,
          "maxSteps": 25
        },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `session.history`

    Retrieve the conversation history for a session. Returns the raw message array stored in the session, including both user and assistant turns.

    `ChannelEndpoint` is the complete exact routing identity used by session,
    message, notification, and platform-action RPCs:
    `{ channelType, channelInstanceId, conversationId, threadId?, conversationKind: "direct" | "shared" }`.
    It is projected from the conversation's authoritative scope and is omitted only
    when that scope is not endpoint-bound.

    | Property       | Value                                                                                                                                                                                                                                                                                              |
    | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | **Scope**      | `rpc`                                                                                                                                                                                                                                                                                              |
    | **Parameters** | `{ tenant_id: string, agent_id: string, conversation_ref: string, offset?: number, limit?: number }`                                                                                                                                                                                               |
    | **Returns**    | `{ session: { key, agentId, channelType, endpoint?: ChannelEndpoint, messageCount, totalTokens, inputTokens, outputTokens, toolCalls, compactions, resetCount, createdAt, lastActiveAt, label? }, messages: Array<{ role, content, timestamp, deliveryStatus? }>, total, offset, limit, hasMore }` |

    ### `session.list`

    List sessions in an exact tenant and agent partition, including sessions stored in SQLite, JSONL transcript files, and workspace session files. Optionally filter by recency or session kind.

    | Property       | Value                                                                                                                                         |
    | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
    | **Scope**      | `rpc`                                                                                                                                         |
    | **Parameters** | `{ tenant_id: string, agent_id: string, kind?: "all" \| "sub-agent" \| "group" \| "dm", since_minutes?: number }`                             |
    | **Returns**    | `{ sessions: Array<{ conversationRef, agentId, kind, endpoint?: ChannelEndpoint, messageCount, totalTokens, updatedAt, createdAt }>, total }` |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "session.list",
        "params": { "tenant_id": "default", "agent_id": "default", "kind": "dm", "since_minutes": 60 },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "sessions": [
            {
              "conversationRef": "cv_MsDcSBa9dmYP7ptzs_Xd7CMHOi6tliEQVzCbN1oOD4o",
              "agentId": "default",
              "kind": "dm",
              "endpoint": {
                "channelType": "telegram",
                "channelInstanceId": "primary",
                "conversationId": "user_a",
                "conversationKind": "direct"
              },
              "messageCount": 42,
              "totalTokens": 21000,
              "updatedAt": 1773313498000,
              "createdAt": 1773100000000
            }
          ],
          "total": 1
        },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `session.send`

    Send text to an exact durable conversation under the agent-to-agent policy and principal-isolation gates. Agent-origin calls are confined to the caller's tenant and principal and may target only the same agent or an exact delegated child.

    | Property       | Value                                                                                                                                                                         |
    | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | **Scope**      | `rpc`                                                                                                                                                                         |
    | **Parameters** | `{ tenant_id: string, agent_id: string, conversation_ref: string, text: string, mode?: "fire-and-forget" \| "wait" \| "ping-pong", timeout_ms?: number, max_turns?: number }` |
    | **Returns**    | Mode-specific delivery object: immediate delivery state for `fire-and-forget`, or the bounded target response for `wait` and `ping-pong`.                                     |

    ### `session.spawn`

    Start a background sub-agent and return its run ID immediately. The optional `async` field is accepted but does not select a synchronous path. Agent-origin calls bind result delivery to the immutable requester origin; only an authenticated control-plane caller without an agent principal may provide both announcement-route fields explicitly.

    | Property       | Value                                                                                                                                                                                                                                                                                                                                                                              |
    | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | **Scope**      | `rpc`                                                                                                                                                                                                                                                                                                                                                                              |
    | **Parameters** | `{ task: string, agent?: string, async?: boolean, max_steps?: number, model?: string, expected_outputs?: string[], artifact_refs?: string[], objective?: string, domain_knowledge?: string[], tool_groups?: string[], required_tools?: string[], include_parent_history?: "none" \| "summary", announce_channel_type?: string, announce_channel_id?: string, worktree?: boolean }` |
    | **Returns**    | `{ runId, async: true, inProgress: true, noteType: "background_running", queued?, deduped?, existingRunId?, dedupAgeMs? }`                                                                                                                                                                                                                                                         |

    ### `session.delete`

    | Property       | Value                                                                                          |
    | -------------- | ---------------------------------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                                                        |
    | **Parameters** | `{ tenant_id: string, agent_id: string, conversation_ref: string }`                            |
    | **Returns**    | `{ conversationRef: string, deleted: true, transcript: { messages, metadata, messageCount } }` |

    The deleted session's transcript is returned in the response so the caller has a final copy. This operation also clears the conversation's working, LCD, runtime, approval-cache, and delivery-mirror state on a best-effort basis; it does not delete RAG memories.

    ### `session.reset`

    Clear the working and runtime transcripts while preserving the conversation identity. It also clears cached approval decisions and pending delivery-mirror rows. LCD durable history is not cleared; use `session.reset_conversation` when every prompt-bearing layer must be forgotten.

    | Property       | Value                                                                    |
    | -------------- | ------------------------------------------------------------------------ |
    | **Scope**      | `rpc`                                                                    |
    | **Parameters** | `{ tenant_id: string, agent_id: string, conversation_ref: string }`      |
    | **Returns**    | `{ conversationRef: string, reset: true, previousMessageCount: number }` |

    ### `session.export`

    Export a session's full message history and metadata as a JSON object. Useful for archiving, debugging, or migrating sessions between instances.

    | Property       | Value                                                                         |
    | -------------- | ----------------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                                       |
    | **Parameters** | `{ tenant_id: string, agent_id: string, conversation_ref: string }`           |
    | **Returns**    | `{ conversationRef, messages, metadata, messageCount, createdAt, updatedAt }` |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "session.export",
        "params": {
          "tenant_id": "default",
          "agent_id": "default",
          "conversation_ref": "cv_MsDcSBa9dmYP7ptzs_Xd7CMHOi6tliEQVzCbN1oOD4o"
        },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "conversationRef": "cv_MsDcSBa9dmYP7ptzs_Xd7CMHOi6tliEQVzCbN1oOD4o",
          "messageCount": 42,
          "createdAt": 1773100000000,
          "updatedAt": 1773313498000,
          "metadata": { "agentId": "default", "channelType": "telegram" },
          "messages": []
        },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `session.run_status`

    Get the owner-authorized, content-bounded status of a sub-agent run. Raw task text, provider output, display session keys, and free-form errors never cross this surface.

    Agent callers receive only `endReason`, `completedAtMs`, and failure `errorKind` inside `completion`. An authorized administrator may additionally receive the bounded `summary` and `resultRef` retained on the run.

    | Property       | Value                                                                                                                                                                                                              |
    | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
    | **Scope**      | `rpc`                                                                                                                                                                                                              |
    | **Parameters** | `{ run_id: string }`                                                                                                                                                                                               |
    | **Returns**    | Closed union: `queued` carries `queuedAt`; `running` carries `startedAt`; `completed` carries a success `completion` and content-free `telemetry`; `failed` carries a failure `completion` and optional telemetry. |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "session.run_status",
        "params": { "run_id": "run_abc123" },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "runId": "run_abc123",
          "status": "completed",
          "agentId": "researcher",
          "startedAt": 1773313450000,
          "runtimeMs": 48000,
          "completion": {
            "endReason": "completed",
            "completedAtMs": 1773313498000
          },
          "telemetry": {
            "tokensUsedTotal": 6200,
            "costTotal": 0.0186,
            "finishReason": "stop",
            "stepsExecuted": 4,
            "cacheReadTokens": 0,
            "cacheWriteTokens": 0
          }
        },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `session.search`

    | Property       | Value                                                                                                                                                                                                         |
    | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | **Scope**      | `rpc`                                                                                                                                                                                                         |
    | **Parameters** | `{ tenant_id: string, agent_id: string, query?: string, scope?: "all" \| "user" \| "assistant" \| "tool", summarize?: boolean, limit?: number }`                                                              |
    | **Returns**    | With no query: `{ mode: "recent", sessions, total }`. With a query: `{ mode: "search", results: Array<{ conversationRef, agentId, channelType, snippet, rawSnippet?, summary?, score, timestamp }>, total }`. |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "session.search",
        "params": { "tenant_id": "default", "agent_id": "default", "query": "deployment instructions", "limit": 5 },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "mode": "search",
          "results": [
            {
              "conversationRef": "cv_MsDcSBa9dmYP7ptzs_Xd7CMHOi6tliEQVzCbN1oOD4o",
              "agentId": "default",
              "channelType": "dm",
              "snippet": "...deploy using the production service...",
              "score": 1,
              "timestamp": 1773313498000
            }
          ],
          "total": 1
        },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `session.compact`

    | Property       | Value                                                                                                         |
    | -------------- | ------------------------------------------------------------------------------------------------------------- |
    | **Scope**      | `rpc`                                                                                                         |
    | **Parameters** | `{ tenant_id: string, agent_id: string, conversation_ref: string, instructions?: string }`                    |
    | **Returns**    | `{ conversationRef, messageCount, estimatedTokens, compactionTriggered: true, instructions: string \| null }` |

    ### `session.reset_conversation`

    Complete cross-mode conversation reset. Clears **all four prompt-bearing layers**: the delivery mirror, LCD durable history, daemon sessionStore working transcript, and pi runtime session. **Destructive and irreversible.** Admin-scoped — requires an admin token.

    This is the explicit forget command when the session identity should remain addressable. Unlike `session.reset`, it also clears the LCD store used by dag mode; `memory: true` additionally removes source memories. The runtime layer matters: without it, the surviving runtime JSONL is re-ingested wholesale on the next turn (LCD epoch rebase) and the "forgotten" conversation resurrects.

    The operation is serialized against live LCD ingest (safe to call while the daemon is running). Returns count-only — no message content is returned or logged. Best-effort on each layer: absent sessionStore entry or zero LCD rows is not an error.

    | Property       | Value                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
    | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
    | **Parameters** | `{ tenant_id: string, agent_id: string, conversation_ref: string, memory?: boolean, purge_derived?: boolean }` — `memory` defaults to `false`; when `true`, the conversation's RAG memories are also deleted by source conversation (both paired-conversation **and** LCD-distilled episodic memories) for the exact agent + tenant scope, and the deleted sources are unlinked from consolidated observations (orphan observation → deleted; multi-source observation → kept with the reference removed). `purge_derived` (default `false`, only meaningful with `memory: true`) escalates to deleting **every** observation derived from this session, even those corroborated by other sessions. |
    | **Returns**    | `{ conversationRef: string, lcdRowsDeleted: number, sessionMessagesCleared: number, memoriesDeleted?: number, resolvedAgentId?: string, runtimeSessionDestroyed?: boolean }` — `memoriesDeleted` is the count of deleted RAG memory rows when `memory: true` and a memory store is wired. It is **omitted** (`undefined`) only when `memory` was not requested, or when the daemon has no memory store wired (the LCD + session transcript are still cleared). `runtimeSessionDestroyed` reports whether the pi runtime session was destroyed; `false` means that layer was unavailable and the conversation may resurrect on the next turn (a WARN with the consequence is logged).                |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "session.reset_conversation",
        "params": {
          "tenant_id": "default",
          "agent_id": "default",
          "conversation_ref": "cv_MsDcSBa9dmYP7ptzs_Xd7CMHOi6tliEQVzCbN1oOD4o",
          "memory": true
        },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "conversationRef": "cv_MsDcSBa9dmYP7ptzs_Xd7CMHOi6tliEQVzCbN1oOD4o",
          "lcdRowsDeleted": 42,
          "sessionMessagesCleared": 18,
          "memoriesDeleted": 5,
          "runtimeSessionDestroyed": true
        },
        "id": 1
      }
      ```
    </CodeGroup>

    <Note>
      A RAG-memory clear failure is non-fatal: if the memory delete fails, the LCD + session-transcript reset that already succeeded is preserved and the failure is logged (the response then reports `memoriesDeleted: 0`). `purge_derived` is destructive and cannot be undone — an observation learned from multiple sessions is deleted in full, not merely unlinked. `memory` / `purge_derived` are the only parameters that **delete** memories; on the recall side, paired-conversation memories overlapping a distilled LCD summary from the same session are automatically **down-weighted** (never deleted), so a distilled summary does not double-count with its own source rows.
    </Note>

    After a reset, the next conversation turn starts with a complete clean slate in both dag and pipeline modes. For the full durability model (LCD vs. JSONL vs. RAG memory), see [Data Directory](/operations/data-directory).
  </Accordion>

  <Accordion title="cron (8 methods)">
    Strict scheduled-job authoring, authority inspection, immutable execution history, manual submission, and guarded authority reset. Unknown request and response fields are rejected.

    | Method        | Scope   | Description                                    |
    | ------------- | ------- | ---------------------------------------------- |
    | `cron.add`    | `rpc`   | Add an authored job                            |
    | `cron.list`   | `rpc`   | List authored and built-in jobs                |
    | `cron.update` | `rpc`   | Update an authored job                         |
    | `cron.status` | `rpc`   | Inspect scheduler and authority state          |
    | `cron.runs`   | `rpc`   | Read immutable execution groups                |
    | `cron.remove` | `rpc`   | Remove an authored job                         |
    | `cron.run`    | `rpc`   | Submit manual or overdue execution             |
    | `cron.reset`  | `admin` | Compare-and-set reset of scheduler authorities |

    ### `cron.add`

    Creates one authored job. The request is `{ name, agentId?, schedule, payload, sessionPolicy?, continuationMode?, deliveryTarget?, wakeGate?, cacheRetention?, toolPolicy?, maxConsecutiveDependencyErrors? }`.

    Request fields are `name`, `agentId`, `schedule`, `payload`, `sessionPolicy`, `continuationMode`, `deliveryTarget`, `wakeGate`, `cacheRetention`, `toolPolicy`, and `maxConsecutiveDependencyErrors`. Schedule kinds are `cron`, `every`, `at`, and `in`; authorable payload kinds are `heartbeat_event`, `delivery`, and `agent_turn`.

    * `schedule` is exactly one of `{ kind: "cron", expr, tz? }`, `{ kind: "every", everyMs, anchorMs? }`, `{ kind: "at", at, tz?, fold?: "earlier" | "later" }`, or `{ kind: "in", seconds }`. `everyMs` and `seconds` are positive safe integers. The persisted response projects an `in` or `at` request as `{ kind: "at", atMs }`.
    * `payload` is exactly one of `{ kind: "heartbeat_event", text, wakeMode: "now" | "next-heartbeat" }`, `{ kind: "delivery", text }`, or `{ kind: "agent_turn", message, model?, timeoutSeconds? }`.
    * `sessionPolicy` is `{ strategy: "fresh" }` or `{ strategy: "rolling", maxHistoryTurns }`; `continuationMode` is `none`, `heartbeat_excerpt`, or `origin_history`.
    * `deliveryTarget` is `{ conversation, destinationEndpoint }`. `conversation` carries a canonical `conversationScope` and matching `conversationRef`; `destinationEndpoint` carries `channelType`, `channelInstanceId`, `conversationId`, optional `threadId`, and `conversationKind: "direct" | "shared"`.
    * `wakeGate` is `{ script, language: "js" | "ts", timeoutSeconds }`; `cacheRetention` is `none`, `short`, or `long`; `toolPolicy` is `{ profile, allow, deny }`, where `profile` is `minimal`, `coding`, `messaging`, `supervisor`, or `full`.

    The response fields are `jobId`, `name`, and `schedule`, with the schedule in persisted `cron`, `every`, or `at` form.

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "cron.add",
        "params": {
          "name": "weekday-briefing",
          "agentId": "default",
          "schedule": { "kind": "cron", "expr": "0 9 * * 1-5", "tz": "America/New_York" },
          "payload": {
            "kind": "agent_turn",
            "message": "Prepare the weekday briefing.",
            "timeoutSeconds": 300
          },
          "sessionPolicy": { "strategy": "fresh" },
          "continuationMode": "none",
          "cacheRetention": "short",
          "maxConsecutiveDependencyErrors": 3
        },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "jobId": "job_a1b2c3",
          "name": "weekday-briefing",
          "schedule": { "kind": "cron", "expr": "0 9 * * 1-5", "tz": "America/New_York" }
        },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `cron.list`

    The request is `{ agentId? }`. The response is `{ jobs }`; every job contains `id`, `name`, `agentId`, `source`, `schedule`, `lifecycle`, and `payload`, plus only the optional policy fields that apply: `maxConsecutiveDependencyErrors`, `sessionPolicy`, `continuationMode`, `deliveryTarget`, `wakeGate`, `cacheRetention`, and `toolPolicy`.

    `source` is `authored` or `built_in`. Persisted `schedule` is `cron`, `every`, or `at`. `lifecycle` is one of:

    * `{ status: "scheduled", nextRunAtMs, consecutiveDependencyErrors }`
    * `{ status: "paused", nextRunAtMs, consecutiveDependencyErrors, reason: "operator" | "dependency_errors" }`
    * `{ status: "one_shot_claimed", executionId, scheduledForMs, claimedAtMs }`
    * `{ status: "one_shot_terminal", terminalExecutionId, terminalAtMs }`

    Projected `payload.kind` is `heartbeat_event`, `delivery`, `agent_turn`, or `internal_action`; built-in internal actions expose `action: "memory_review" | "memory_lifecycle" | "reflection"`.

    ```json Request theme={}
    {"jsonrpc":"2.0","method":"cron.list","params":{"agentId":"default"},"id":1}
    ```

    ### `cron.update`

    Updates an authored job selected by `jobId` or `jobName`; at least one selector is required. The strict request is `{ jobId?, jobName?, name?, schedule?, payload?, sessionPolicy?, continuationMode?, deliveryTarget?, wakeGate?, cacheRetention?, toolPolicy?, maxConsecutiveDependencyErrors?, paused? }`. `schedule` and `payload` use the same closed unions as `cron.add`. Nullable `deliveryTarget`, `wakeGate`, `cacheRetention`, `toolPolicy`, and `maxConsecutiveDependencyErrors` clear those settings. The response is `{ jobName, updated }`.

    Mutation vocabulary is `jobId`, `jobName`, `name`, `schedule`, `payload`, and `paused`; this response reports `updated`, while `cron.remove` reports the separate `removed` flag.

    ```json Request theme={}
    {"jsonrpc":"2.0","method":"cron.update","params":{"jobName":"weekday-briefing","paused":true},"id":1}
    ```

    ### `cron.status`

    Returns scheduler-wide authority state for the resolved agent, not one job. The request is `{ agentId? }`. The response is `{ state, configuredEnabled, running, strictAuthoritiesValid, ownershipReconciled, jobCount, activeClaimCount, resolvedAgentId, store, ledger, intent, lastError? }`.

    Response fields are `state`, `configuredEnabled`, `running`, `strictAuthoritiesValid`, `ownershipReconciled`, `jobCount`, `activeClaimCount`, `resolvedAgentId`, `store`, `ledger`, `intent`, and optional `lastError`; the request optionally selects `agentId`.

    * `state` is `initializing`, `disabled`, `ready`, `active`, `maintenance`, or `failed`.
    * `store` and `ledger` are `{ exists, bytes, digest }`; `digest` is a lowercase SHA-256 value or `null`, and the three fields must agree about whether the authority exists.
    * `intent` is `{ status: "none" }`, `{ status: "pending", operationId, target, phase, digest }`, or `{ status: "invalid", digest }`. Pending `target` is `store`, `ledger`, or `all`; `phase` is `prepared`, `archives_recorded`, `replacements_recorded`, or `completion_recorded`.
    * `lastError`, when present, is `{ code, errorKind }`, using the closed maintenance error and platform error-kind vocabularies.

    ### `cron.runs`

    Reads immutable start/terminal groups for one job. The request is `{ jobName, limit?, agentId? }`; `limit` is a positive safe integer capped at 10,000. The response is `{ runs }`. Each run contains `executionId`, `jobId`, `agentId`, `scheduledForMs`, `trigger`, `workKind`, `rootRunId`, `startedAtMs`, optional `terminalAtMs` and `durationMs`, `status`, `deliveryStatus`, optional `errorKind`, and optional `counters`.

    Request fields are `jobName`, `limit`, and optional `agentId`.

    * `trigger` is `scheduled`, `catchup`, or `manual`; `workKind` is `agent_turn`, `heartbeat_event`, `internal_action`, or `delivery_only`.
    * `status` is `started`, `dispatched`, `completed`, `failed`, `aborted`, `skipped`, or `unknown`.
    * `deliveryStatus` is `not_requested`, `suppressed`, `pre_send_failed`, `accepted`, `partial`, `rejected`, or `unknown`.
    * `counters` appears only on terminal `internal_action` outcomes. It contains at most 32 content-free `{ name, value }` records. A name matches `^[a-z][a-z0-9_]*$`, is at most 64 characters, and each value is a nonnegative safe integer.

    <CodeGroup>
      ```json Request theme={}
      {"jsonrpc":"2.0","method":"cron.runs","params":{"jobName":"Reflection","limit":5,"agentId":"default"},"id":1}
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "runs": [{
            "executionId": "execution-reflection-a",
            "jobId": "reflection-default",
            "agentId": "default",
            "scheduledForMs": 1800000000000,
            "trigger": "scheduled",
            "workKind": "internal_action",
            "rootRunId": "root-cron-execution-reflection-a",
            "startedAtMs": 1800000000000,
            "terminalAtMs": 1800000000050,
            "durationMs": 50,
            "status": "failed",
            "deliveryStatus": "not_requested",
            "errorKind": "dependency",
            "counters": [
              { "name": "selected", "value": 8 },
              { "name": "dependency_failures", "value": 1 }
            ]
          }]
        },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `cron.remove`

    Removes an authored job selected by `jobId` or `jobName`; at least one selector is required. The request is `{ jobId?, jobName? }`. The response is `{ jobName, removed }`.

    ### `cron.run`

    Submits work through the normal scheduler execution path. The request is `{ jobName?, mode?: "force" | "due", agentId? }`; omitted `mode` means `force`. `force` requires a resolvable `jobName` and returns `{ triggered, mode: "force", jobName, resolvedAgentId, executionId }`. `due` evaluates all overdue occurrences for the resolved agent and returns `{ triggered, mode: "due", resolvedAgentId, executionIds }`.

    The closed request/response vocabulary is `jobName`, `mode`, `force`, `due`, `agentId`, `triggered`, `resolvedAgentId`, `executionId`, and `executionIds`.

    A manual `force` submission does not change the job's scheduled lifecycle or dependency-breaker state. It creates a new manual occurrence; it does not replay or reinterpret an earlier uncertain occurrence.

    ### `cron.reset`

    `admin`-only compare-and-set maintenance for raw scheduler authorities. The request is one of `{ target: "store", expectedDigests: { store }, confirmed: true, agentId? }`, `{ target: "ledger", expectedDigests: { ledger }, confirmed: true, agentId? }`, or `{ target: "all", expectedDigests: { store, ledger }, confirmed: true, agentId? }`. Each expected digest is a lowercase SHA-256 value or `null` when that authority is absent. The exact digest prevents resetting state that changed after inspection.

    The response is `{ operationId, target, resolvedAgentId, beforeDigests, afterDigests, state, reactivated }`. `beforeDigests` and `afterDigests` each contain nullable `store` and `ledger` digests; `state` is `disabled`, `ready`, or `active`.

    Reset fields are `target`, `expectedDigests`, `confirmed`, optional `agentId`, `operationId`, `beforeDigests`, `afterDigests`, `state`, and `reactivated`.
  </Accordion>

  <Accordion title="browser (13 methods)">
    Browser automation via Chrome DevTools Protocol (CDP). All methods use `rpc` scope and bridge to the browser automation subsystem.

    | Method               | Scope | Description                                      |
    | -------------------- | ----- | ------------------------------------------------ |
    | `browser.status`     | `rpc` | Browser session status (running, URL, tab count) |
    | `browser.start`      | `rpc` | Start a browser session                          |
    | `browser.stop`       | `rpc` | Stop the browser session                         |
    | `browser.navigate`   | `rpc` | Navigate to a URL                                |
    | `browser.snapshot`   | `rpc` | Take a DOM snapshot (accessibility tree)         |
    | `browser.screenshot` | `rpc` | Take a screenshot (returns base64 PNG)           |
    | `browser.pdf`        | `rpc` | Generate a PDF of the current page               |
    | `browser.act`        | `rpc` | Execute a browser action (click, type, scroll)   |
    | `browser.tabs`       | `rpc` | List open browser tabs                           |
    | `browser.open`       | `rpc` | Open a new tab                                   |
    | `browser.focus`      | `rpc` | Focus a specific tab                             |
    | `browser.close`      | `rpc` | Close a tab                                      |
    | `browser.console`    | `rpc` | Get console log entries                          |

    ### `browser.status`

    Get the current browser session status: whether a session is running, the current URL, and the number of open tabs.

    | Property       | Value                                                   |
    | -------------- | ------------------------------------------------------- |
    | **Scope**      | `rpc`                                                   |
    | **Parameters** | None                                                    |
    | **Returns**    | `{ running: boolean, url?: string, tabCount?: number }` |

    ### `browser.start`

    Start a browser session. A headless Chromium instance is launched and connected via CDP. Does nothing if a session is already running.

    | Property       | Value               |
    | -------------- | ------------------- |
    | **Scope**      | `rpc`               |
    | **Parameters** | None                |
    | **Returns**    | `{ started: true }` |

    ### `browser.stop`

    Stop the active browser session and close all tabs. Any ongoing automation is interrupted.

    | Property       | Value               |
    | -------------- | ------------------- |
    | **Scope**      | `rpc`               |
    | **Parameters** | None                |
    | **Returns**    | `{ stopped: true }` |

    ### `browser.navigate`

    | Property       | Value                            |
    | -------------- | -------------------------------- |
    | **Scope**      | `rpc`                            |
    | **Parameters** | `{ url: string }`                |
    | **Returns**    | Navigation result with final URL |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "browser.navigate",
        "params": { "url": "https://example.com" },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "url": "https://example.com",
          "title": "Example Domain"
        },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `browser.snapshot`

    Take an accessibility-tree DOM snapshot of the current page. Returns a structured representation of interactive elements — more reliable for automation than raw HTML.

    | Property       | Value                              |
    | -------------- | ---------------------------------- |
    | **Scope**      | `rpc`                              |
    | **Parameters** | None                               |
    | **Returns**    | Accessibility tree snapshot object |

    ### `browser.screenshot`

    | Property       | Value                                   |
    | -------------- | --------------------------------------- |
    | **Scope**      | `rpc`                                   |
    | **Parameters** | `{ fullPage?: boolean }`                |
    | **Returns**    | `{ data: string }` (base64-encoded PNG) |

    ### `browser.pdf`

    Generate a PDF of the current page and return it as base64-encoded data.

    | Property       | Value                                   |
    | -------------- | --------------------------------------- |
    | **Scope**      | `rpc`                                   |
    | **Parameters** | None                                    |
    | **Returns**    | `{ data: string }` (base64-encoded PDF) |

    ### `browser.act`

    | Property       | Value                                                                  |
    | -------------- | ---------------------------------------------------------------------- |
    | **Scope**      | `rpc`                                                                  |
    | **Parameters** | `{ action: string, selector?: string, text?: string, value?: string }` |
    | **Returns**    | Action result                                                          |

    ### `browser.tabs`

    List all currently open browser tabs with their IDs, URLs, and titles.

    | Property       | Value                                                         |
    | -------------- | ------------------------------------------------------------- |
    | **Scope**      | `rpc`                                                         |
    | **Parameters** | None                                                          |
    | **Returns**    | `{ tabs: Array<{ id: string, url: string, title: string }> }` |

    ### `browser.open`

    Open a new browser tab, optionally navigating to a URL immediately.

    | Property       | Value               |
    | -------------- | ------------------- |
    | **Scope**      | `rpc`               |
    | **Parameters** | `{ url?: string }`  |
    | **Returns**    | `{ tabId: string }` |

    ### `browser.focus`

    Focus a specific tab by its ID, making it the active tab for subsequent commands.

    | Property       | Value                              |
    | -------------- | ---------------------------------- |
    | **Scope**      | `rpc`                              |
    | **Parameters** | `{ tabId: string }`                |
    | **Returns**    | `{ focused: true, tabId: string }` |

    ### `browser.close`

    Close a specific tab by its ID.

    | Property       | Value                             |
    | -------------- | --------------------------------- |
    | **Scope**      | `rpc`                             |
    | **Parameters** | `{ tabId: string }`               |
    | **Returns**    | `{ closed: true, tabId: string }` |

    ### `browser.console`

    Retrieve console log entries captured since the browser session started. Useful for debugging page-level JavaScript errors during automation.

    | Property       | Value                                                                    |
    | -------------- | ------------------------------------------------------------------------ |
    | **Scope**      | `rpc`                                                                    |
    | **Parameters** | None                                                                     |
    | **Returns**    | `{ entries: Array<{ level: string, text: string, timestamp: number }> }` |
  </Accordion>

  <Accordion title="audio (1 method)">
    Speech-to-text transcription using the configured STT provider.

    | Method             | Scope | Description              |
    | ------------------ | ----- | ------------------------ |
    | `audio.transcribe` | `rpc` | Transcribe audio to text |

    ### `audio.transcribe`

    | Property       | Value                                                                                                                       |
    | -------------- | --------------------------------------------------------------------------------------------------------------------------- |
    | **Scope**      | `rpc`                                                                                                                       |
    | **Parameters** | `{ audio: string, mimeType?: string, language?: string }` (`audio` is base64-encoded, `mimeType` defaults to `"audio/ogg"`) |
    | **Returns**    | `{ text: string, language?: string, durationMs?: number }`                                                                  |

    Returns `{ error: "..." }` if STT is not configured or transcription fails.

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "audio.transcribe",
        "params": {
          "audio": "T2dnUwACAAAAAAA...",
          "mimeType": "audio/ogg",
          "language": "en"
        },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "text": "Hello, how are you?",
          "language": "en",
          "durationMs": 2340
        },
        "id": 1
      }
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="admin.approval (4 methods)">
    Approval gate management for action confirmation workflows.

    | Method                            | Scope   | Description                                              |
    | --------------------------------- | ------- | -------------------------------------------------------- |
    | `admin.approval.clearDenialCache` | `admin` | Clear the denial cache for approval gates                |
    | `admin.approval.pending`          | `admin` | List pending approval requests                           |
    | `admin.approval.resolve`          | `admin` | Approve or deny a pending request                        |
    | `admin.approval.resolveAll`       | `admin` | Bulk-resolve all pending approval requests for a session |

    ### `admin.approval.clearDenialCache`

    | Property       | Value               |
    | -------------- | ------------------- |
    | **Scope**      | `admin`             |
    | **Parameters** | None                |
    | **Returns**    | `{ cleared: true }` |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "admin.approval.clearDenialCache",
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "cleared": true
        },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `admin.approval.pending`

    | Property       | Value                             |
    | -------------- | --------------------------------- |
    | **Scope**      | `admin`                           |
    | **Parameters** | `{}`                              |
    | **Returns**    | List of pending approval requests |

    ### `admin.approval.resolve`

    | Property       | Value                                                |
    | -------------- | ---------------------------------------------------- |
    | **Scope**      | `admin`                                              |
    | **Parameters** | `{ id: string, approved: boolean, reason?: string }` |
    | **Returns**    | Resolution confirmation                              |

    ### `admin.approval.resolveAll`

    Bulk-approve or bulk-deny every pending request at once, optionally scoped to a single session. Useful during incident response when many requests have queued up and you need to unblock or cancel them all in one call.

    | Property       | Value                                                                              |
    | -------------- | ---------------------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                                            |
    | **Parameters** | `{ approved: boolean, sessionKey?: string, approvedBy?: string, reason?: string }` |
    | **Returns**    | `{ resolved: number, requestIds: string[] }`                                       |

    `sessionKey` filters to only the requests belonging to that session. Omit it to resolve all pending requests across every session. `approvedBy` defaults to `"operator"` when omitted.

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "admin.approval.resolveAll",
        "params": { "approved": false, "reason": "Cancelled by operator" },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "resolved": 3,
          "requestIds": ["req_a1", "req_b2", "req_c3"]
        },
        "id": 1
      }
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="agents (7 methods)">
    Agent lifecycle management: create, read, update, delete, suspend, and resume agents.

    | Method           | Scope   | Description                        |
    | ---------------- | ------- | ---------------------------------- |
    | `agents.list`    | `admin` | List all configured agents         |
    | `agents.create`  | `admin` | Create a new agent                 |
    | `agents.get`     | `admin` | Get agent configuration            |
    | `agents.update`  | `admin` | Update agent configuration         |
    | `agents.delete`  | `admin` | Delete an agent                    |
    | `agents.suspend` | `admin` | Suspend an agent (stop processing) |
    | `agents.resume`  | `admin` | Resume a suspended agent           |

    ### `agents.list`

    List all configured agent IDs. This is a lightweight call that returns only the IDs — use `agents.get` for full configuration details on a specific agent.

    | Property       | Value                  |
    | -------------- | ---------------------- |
    | **Scope**      | `admin`                |
    | **Parameters** | None                   |
    | **Returns**    | `{ agents: string[] }` |

    ### `agents.create`

    | Property       | Value                                                            |
    | -------------- | ---------------------------------------------------------------- |
    | **Scope**      | `admin`                                                          |
    | **Parameters** | `{ name: string, provider?: string, model?: string, ...config }` |
    | **Returns**    | Created agent configuration                                      |

    ### `agents.get`

    | Property       | Value                      |
    | -------------- | -------------------------- |
    | **Scope**      | `admin`                    |
    | **Parameters** | `{ agentId: string }`      |
    | **Returns**    | Agent configuration object |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "agents.list",
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "agents": [
            { "id": "default", "name": "Comis", "provider": "anthropic", "model": "claude-sonnet-4-5-20250929" }
          ]
        },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `agents.update`

    Update an existing agent's configuration. Only the fields you provide are changed; everything else stays as-is. The agent continues running with the new settings immediately — no restart required.

    Pass `dryRun: true` to validate a configuration patch **without** applying it: the daemon runs the full validation path (schema parse, OAuth-profile existence checks, and the provider credential guard/probe when the provider changes) and returns the would-be result, but does **not** persist to `config.yaml`/`config.last-good.yaml` and does **not** hot-apply the change to the running agent. The web dashboard's "Validate" button uses this so checking a config never mutates a live agent.

    | Property       | Value                                                                                                |
    | -------------- | ---------------------------------------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                                                              |
    | **Parameters** | `{ agentId: string, name?: string, provider?: string, model?: string, dryRun?: boolean, ...config }` |
    | **Returns**    | Updated agent configuration (or, with `dryRun: true`, the validation outcome with no side effects)   |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "agents.update",
        "params": {
          "agentId": "researcher",
          "model": "claude-opus-4-5-20250929"
        },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "agentId": "researcher",
          "updated": true
        },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `agents.delete`

    Permanently remove an agent from the configuration. Any active sessions belonging to the agent are left intact in the session store but will fail to execute new turns after deletion.

    | Property       | Value                                |
    | -------------- | ------------------------------------ |
    | **Scope**      | `admin`                              |
    | **Parameters** | `{ agentId: string }`                |
    | **Returns**    | `{ agentId: string, deleted: true }` |

    ### `agents.suspend`

    Suspend an agent so it stops accepting new messages. Existing in-flight executions are allowed to finish. Useful for taking an agent offline temporarily without deleting its configuration.

    | Property       | Value                                  |
    | -------------- | -------------------------------------- |
    | **Scope**      | `admin`                                |
    | **Parameters** | `{ agentId: string }`                  |
    | **Returns**    | `{ agentId: string, suspended: true }` |

    ### `agents.resume`

    Resume a previously suspended agent, allowing it to accept messages again.

    | Property       | Value                                |
    | -------------- | ------------------------------------ |
    | **Scope**      | `admin`                              |
    | **Parameters** | `{ agentId: string }`                |
    | **Returns**    | `{ agentId: string, resumed: true }` |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "agents.suspend",
        "params": { "agentId": "researcher" },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "agentId": "researcher",
          "suspended": true
        },
        "id": 1
      }
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="memory (9 methods)">
    Memory system operations: search, inspect, browse, manage, store, and export. Core search and inspect use `rpc` scope; management methods use `admin` scope.

    | Method                  | Scope   | Description                                |
    | ----------------------- | ------- | ------------------------------------------ |
    | `memory.browse`         | `admin` | Browse memory entries with pagination      |
    | `memory.delete`         | `admin` | Delete a memory entry                      |
    | `memory.embeddingCache` | `admin` | Get embedding cache statistics             |
    | `memory.export`         | `admin` | Export memory database                     |
    | `memory.flush`          | `admin` | Flush all memory entries                   |
    | `memory.inspect`        | `rpc`   | Inspect memory entry or statistics         |
    | `memory.search`         | `rpc`   | Search memory entries (full-text + vector) |
    | `memory.stats`          | `admin` | Memory system statistics                   |
    | `memory.store`          | `admin` | Store a new memory entry                   |

    ### `memory.search`

    | Property       | Value                               |
    | -------------- | ----------------------------------- |
    | **Scope**      | `rpc`                               |
    | **Parameters** | `{ query: string, limit?: number }` |
    | **Returns**    | `{ results: MemoryEntry[] }`        |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "memory.search",
        "params": { "query": "deployment instructions", "limit": 5 },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "results": [
            { "id": "mem_abc123", "content": "Deploy using pm2...", "score": 0.92, "timestamp": 1773313498000 }
          ]
        },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `memory.browse`

    | Property       | Value                                 |
    | -------------- | ------------------------------------- |
    | **Scope**      | `admin`                               |
    | **Parameters** | `{ offset?: number, limit?: number }` |
    | **Returns**    | Paginated memory entries              |

    ### `memory.delete`

    | Property       | Value                 |
    | -------------- | --------------------- |
    | **Scope**      | `admin`               |
    | **Parameters** | `{ id: string }`      |
    | **Returns**    | Deletion confirmation |

    ### `memory.embeddingCache`

    | Property       | Value                                        |
    | -------------- | -------------------------------------------- |
    | **Scope**      | `admin`                                      |
    | **Parameters** | None                                         |
    | **Returns**    | Cache statistics (size, hit rate, evictions) |

    ### `memory.inspect`

    Inspect a single memory entry by ID, or retrieve overall memory system statistics when no ID is given.

    | Property       | Value                                                                        |
    | -------------- | ---------------------------------------------------------------------------- |
    | **Scope**      | `rpc`                                                                        |
    | **Parameters** | `{ id?: string, tenantId?: string }`                                         |
    | **Returns**    | `{ entry?: MemoryEntry }` (with `id`) or `{ stats?: object }` (without `id`) |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "memory.inspect",
        "params": { "id": "mem_abc123" },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "entry": {
            "id": "mem_abc123",
            "content": "Deploy using pm2 for production...",
            "memoryType": "semantic",
            "trustLevel": "high",
            "score": null,
            "createdAt": 1773313498000
          }
        },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `memory.stats`

    Get aggregate statistics for the memory system: total entry count, database size, embedding cache hit rate, and provider information.

    | Property       | Value                                                                                              |
    | -------------- | -------------------------------------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                                                            |
    | **Parameters** | None                                                                                               |
    | **Returns**    | `{ totalEntries: number, dbSizeBytes: number, embeddingProvider?: string, cacheHitRate?: number }` |

    ### `memory.flush`

    Delete all memory entries permanently. This is a destructive operation with no undo — all stored memories are wiped from both the vector index and the database.

    | Property       | Value                                     |
    | -------------- | ----------------------------------------- |
    | **Scope**      | `admin`                                   |
    | **Parameters** | None                                      |
    | **Returns**    | `{ flushed: true, deletedCount: number }` |

    <Warning>
      `memory.flush` is irreversible. Export your memories with `memory.export` before flushing if you want a backup.
    </Warning>

    ### `memory.export`

    Export the entire memory database as a portable JSON snapshot. Useful for backups, migration between instances, or offline analysis.

    | Property       | Value                                                                  |
    | -------------- | ---------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                                |
    | **Parameters** | None                                                                   |
    | **Returns**    | `{ entries: MemoryEntry[], exportedAt: number, totalEntries: number }` |

    ### `memory.store`

    | Property       | Value                                    |
    | -------------- | ---------------------------------------- |
    | **Scope**      | `admin`                                  |
    | **Parameters** | `{ content: string, metadata?: object }` |
    | **Returns**    | Stored entry ID                          |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "memory.store",
        "params": {
          "content": "Important note about deployment procedure",
          "metadata": { "category": "ops", "priority": "high" }
        },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "id": "mem_xyz789",
          "stored": true
        },
        "id": 1
      }
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="models (3 methods)">
    Model catalog and configuration inspection. The catalog comes from the installed [`@earendil-works/pi-ai`](https://www.npmjs.com/package/@earendil-works/pi-ai) SDK — `getProviders()` for the provider list and `getModels(provider)` for per-provider model details. These methods do not query provider APIs.

    | Method                  | Scope   | Description                                                                  |
    | ----------------------- | ------- | ---------------------------------------------------------------------------- |
    | `models.list_providers` | `admin` | List all providers known to the pi-ai catalog                                |
    | `models.list`           | `admin` | List available models from the catalog (optionally filtered to one provider) |
    | `models.test`           | `admin` | Inspect one provider's local catalog and agent configuration                 |

    ### `models.list_providers`

    List all providers in the pi-ai SDK catalog. Returns provider ids and a count. The CLI setup flow uses this list for provider selection.

    | Property       | Value                                    |
    | -------------- | ---------------------------------------- |
    | **Scope**      | `admin`                                  |
    | **Parameters** | None                                     |
    | **Returns**    | `{ providers: string[], count: number }` |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "models.list_providers",
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "providers": [
            "amazon-bedrock", "ant-ling", "anthropic", "azure-openai-responses", "cerebras",
            "cloudflare-ai-gateway", "cloudflare-workers-ai", "deepseek", "fireworks",
            "github-copilot", "google", "google-vertex", "groq", "huggingface",
            "kimi-coding", "minimax", "minimax-cn", "mistral", "moonshotai",
            "moonshotai-cn", "nvidia", "openai", "openai-codex", "opencode",
            "opencode-go", "openrouter", "qwen-token-plan", "qwen-token-plan-cn",
            "together", "vercel-ai-gateway", "xai", "xiaomi",
            "xiaomi-token-plan-ams", "xiaomi-token-plan-cn",
            "xiaomi-token-plan-sgp", "zai", "zai-coding-cn"
          ],
          "count": 37
        },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `models.list`

    List all models available across the pi-ai catalog (optionally filtered to one provider). Returns the model ID, provider name, display name, baseUrl, context window, max tokens, input modalities, reasoning support, and cost per million tokens. Use this to discover what models you can assign to agents.

    | Property       | Value                                                                                                                                                                    |
    | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
    | **Scope**      | `admin`                                                                                                                                                                  |
    | **Parameters** | `{ provider?: string }` (omit for all providers)                                                                                                                         |
    | **Returns**    | `Array<{ provider, modelId, displayName, contextWindow, maxTokens, baseUrl, input, reasoning, cost: { input, output, cacheRead, cacheWrite }, validated, validatedAt }>` |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "models.list",
        "params": { "provider": "anthropic" },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": [
          {
            "provider": "anthropic",
            "modelId": "claude-sonnet-4-5",
            "displayName": "Claude Sonnet 4.5 (latest)",
            "baseUrl": "https://api.anthropic.com",
            "contextWindow": 200000,
            "maxTokens": 64000,
            "input": ["text", "image"],
            "reasoning": true,
            "cost": { "input": 3.0, "output": 15.0, "cacheRead": 0.3, "cacheWrite": 3.75 },
            "validated": false,
            "validatedAt": 0
          }
        ],
        "id": 1
      }
      ```
    </CodeGroup>

    ### `models.test`

    Inspect whether a provider is used by configured agents and whether its models exist in the local catalog (or a configured custom-provider entry). No network request, credential verification, or inference is performed.

    | Property       | Value                                                                                                   |
    | -------------- | ------------------------------------------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                                                                 |
    | **Parameters** | `{ provider: string }`                                                                                  |
    | **Returns**    | Provider status such as `{ provider, status, modelsAvailable?, validatedModels?, agentsUsing?, hint? }` |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "models.test",
        "params": { "provider": "anthropic" },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "provider": "anthropic",
          "status": "available",
          "modelsAvailable": 14,
          "validatedModels": 0,
          "agentsUsing": [
            { "agentId": "default", "model": "claude-sonnet-4-5-20250929" }
          ]
        },
        "id": 1
      }
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="tokens (4 methods)">
    Gateway token management for API authentication.

    | Method          | Scope   | Description                                |
    | --------------- | ------- | ------------------------------------------ |
    | `tokens.list`   | `admin` | List all gateway tokens (secrets redacted) |
    | `tokens.create` | `admin` | Create a new gateway token                 |
    | `tokens.revoke` | `admin` | Revoke a gateway token                     |
    | `tokens.rotate` | `admin` | Rotate a token (revoke old, create new)    |

    ### `tokens.list`

    List all configured gateway tokens. Token secrets are always redacted — you can see token IDs, scopes, and creation timestamps, but never the raw secret.

    | Property       | Value                                                                    |
    | -------------- | ------------------------------------------------------------------------ |
    | **Scope**      | `admin`                                                                  |
    | **Parameters** | None                                                                     |
    | **Returns**    | `{ tokens: Array<{ id: string, scopes: string[], createdAt: string }> }` |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "tokens.list",
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "tokens": [
            { "id": "default", "scopes": ["*"], "createdAt": "2026-01-01T00:00:00Z" },
            { "id": "dashboard", "scopes": ["rpc"], "createdAt": "2026-03-15T08:00:00Z" }
          ]
        },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `tokens.create`

    Create a new gateway token. The secret is returned only in this response — it cannot be retrieved again. Store it securely immediately.

    | Property       | Value                                              |
    | -------------- | -------------------------------------------------- |
    | **Scope**      | `admin`                                            |
    | **Parameters** | `{ id: string, scopes: string[] }`                 |
    | **Returns**    | `{ id: string, secret: string, scopes: string[] }` |

    ### `tokens.revoke`

    Permanently revoke a gateway token. Any client currently authenticated with this token will be disconnected at their next request.

    | Property       | Value                           |
    | -------------- | ------------------------------- |
    | **Scope**      | `admin`                         |
    | **Parameters** | `{ id: string }`                |
    | **Returns**    | `{ id: string, revoked: true }` |

    ### `tokens.rotate`

    Revoke an existing token and create a replacement with the same ID and scopes in a single atomic operation. The new secret is returned once and cannot be retrieved again.

    | Property       | Value                                                             |
    | -------------- | ----------------------------------------------------------------- |
    | **Scope**      | `admin`                                                           |
    | **Parameters** | `{ id: string }`                                                  |
    | **Returns**    | `{ id: string, secret: string, scopes: string[], rotated: true }` |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "tokens.rotate",
        "params": { "id": "dashboard" },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "id": "dashboard",
          "secret": "ctk_newSecretValue",
          "scopes": ["rpc"],
          "rotated": true
        },
        "id": 1
      }
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="channels (7 methods)">
    Channel adapter management: list, inspect, enable, disable, restart, capabilities, and health for chat platform connections.

    | Method                  | Scope   | Description                              |
    | ----------------------- | ------- | ---------------------------------------- |
    | `channels.capabilities` | `rpc`   | Get per-channel capability matrix        |
    | `channels.disable`      | `admin` | Disable a channel adapter                |
    | `channels.enable`       | `admin` | Enable a channel adapter                 |
    | `channels.get`          | `admin` | Get detailed channel information         |
    | `channels.health`       | `rpc`   | Get channel health summary               |
    | `channels.list`         | `admin` | List all configured channels with status |
    | `channels.restart`      | `admin` | Restart a channel adapter connection     |

    ### `channels.list`

    List all configured channel adapters with their current status. Includes both running adapters and channels that are configured but not currently active.

    | Property       | Value                                                                                             |
    | -------------- | ------------------------------------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                                                           |
    | **Parameters** | None                                                                                              |
    | **Returns**    | `{ channels: Array<{ channelType, channelId?, status: "running" \| "stopped" }>, total: number }` |

    ### `channels.capabilities`

    | Property       | Value                                                                                                   |
    | -------------- | ------------------------------------------------------------------------------------------------------- |
    | **Scope**      | `rpc`                                                                                                   |
    | **Parameters** | `{ channel_type: string }`                                                                              |
    | **Returns**    | `{ channelType: string, features: object }` — the feature capability map for the specified channel type |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "channels.capabilities",
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "telegram": { "threads": true, "reactions": true, "editing": true, "buttons": true },
          "discord": { "threads": true, "reactions": true, "editing": true, "embeds": true }
        },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `channels.get`

    | Property       | Value                                                                               |
    | -------------- | ----------------------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                                             |
    | **Parameters** | `{ channel_type: string }`                                                          |
    | **Returns**    | `{ channelType, channelId?, status: "running" \| "stopped", configured?: boolean }` |

    ### `channels.health`

    | Property       | Value                                           |
    | -------------- | ----------------------------------------------- |
    | **Scope**      | `rpc`                                           |
    | **Parameters** | None                                            |
    | **Returns**    | Channel health summary with connectivity status |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "channels.health",
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "channels": [
            { "id": "telegram", "status": "connected", "latencyMs": 42, "lastActivity": 1773313498000 },
            { "id": "discord", "status": "connected", "latencyMs": 28, "lastActivity": 1773313495000 }
          ]
        },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `channels.enable`

    Start a stopped channel adapter. The adapter must already be configured — this restarts its connection without touching the config file. The health monitor is notified automatically.

    | Property       | Value                                                         |
    | -------------- | ------------------------------------------------------------- |
    | **Scope**      | `admin`                                                       |
    | **Parameters** | `{ channel_type: string }`                                    |
    | **Returns**    | `{ channelType: string, status: "running", message: string }` |

    ### `channels.disable`

    Stop a running channel adapter. The adapter is shut down cleanly and removed from the health monitor. The config file is updated to reflect the new `enabled: false` state.

    | Property       | Value                                                         |
    | -------------- | ------------------------------------------------------------- |
    | **Scope**      | `admin`                                                       |
    | **Parameters** | `{ channel_type: string }`                                    |
    | **Returns**    | `{ channelType: string, status: "stopped", message: string }` |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "channels.disable",
        "params": { "channel_type": "telegram" },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "channelType": "telegram",
          "status": "stopped",
          "message": "Channel adapter stopped"
        },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `channels.restart`

    Stop then immediately restart a channel adapter. Useful when a connection has gone stale and you want to force a clean reconnect without disabling the channel.

    | Property       | Value                                                         |
    | -------------- | ------------------------------------------------------------- |
    | **Scope**      | `admin`                                                       |
    | **Parameters** | `{ channel_type: string }`                                    |
    | **Returns**    | `{ channelType: string, status: "running", message: string }` |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "channels.list",
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "channels": [
            { "id": "telegram", "type": "telegram", "enabled": true, "status": "connected" },
            { "id": "discord", "type": "discord", "enabled": true, "status": "connected" }
          ]
        },
        "id": 1
      }
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="mcp (6 methods)">
    Model Context Protocol (MCP) server management: list, connect, disconnect, and test MCP tool servers.

    | Method           | Scope   | Description                      |
    | ---------------- | ------- | -------------------------------- |
    | `mcp.list`       | `admin` | List all configured MCP servers  |
    | `mcp.status`     | `admin` | Get MCP server connection status |
    | `mcp.connect`    | `admin` | Connect to an MCP server         |
    | `mcp.disconnect` | `admin` | Disconnect from an MCP server    |
    | `mcp.reconnect`  | `admin` | Reconnect to an MCP server       |
    | `mcp.test`       | `admin` | Test MCP server connectivity     |

    ### `mcp.list`

    List all configured MCP servers with their current connection status and tool counts.

    | Property       | Value                                                               |
    | -------------- | ------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                             |
    | **Parameters** | None                                                                |
    | **Returns**    | `{ servers: Array<{ id: string, status: string, tools: number }> }` |

    ### `mcp.status`

    Get the current connection status of a specific MCP server, including which tools it exposes.

    | Property       | Value                                                                                                                                                                                                                                                                      |
    | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                                                                                                                                                                                                                                    |
    | **Parameters** | `{ serverId: string }`                                                                                                                                                                                                                                                     |
    | **Returns**    | `{ name, status, toolCount, tools: Array<{ name, qualifiedName, callableName, description? }>, error?: string, … }`. `callableName` (`mcp__<server>--<tool>`) is the name the agent must **invoke** the tool by; `qualifiedName` (`mcp:<server>/<tool>`) is advisory only. |

    ### `mcp.connect`

    Connect to an MCP server. If the server is already connected this is a no-op. On success the response includes the list of tool names the server exposes.

    | Property       | Value                                  |
    | -------------- | -------------------------------------- |
    | **Scope**      | `admin`                                |
    | **Parameters** | `{ serverId: string }`                 |
    | **Returns**    | Connection result with available tools |

    ### `mcp.disconnect`

    Gracefully disconnect from an MCP server. The server remains in the configuration but will not be used until you reconnect.

    | Property       | Value                                      |
    | -------------- | ------------------------------------------ |
    | **Scope**      | `admin`                                    |
    | **Parameters** | `{ serverId: string }`                     |
    | **Returns**    | `{ serverId: string, disconnected: true }` |

    ### `mcp.reconnect`

    Disconnect then immediately reconnect to an MCP server. Use this to pick up configuration changes or recover from a stale connection without editing the config file.

    | Property       | Value                                        |
    | -------------- | -------------------------------------------- |
    | **Scope**      | `admin`                                      |
    | **Parameters** | `{ serverId: string }`                       |
    | **Returns**    | Reconnection result with refreshed tool list |

    ### `mcp.test`

    Test connectivity to an MCP server without changing its state. Returns the round-trip latency and confirms the server is reachable.

    | Property       | Value                                                                  |
    | -------------- | ---------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                                |
    | **Parameters** | `{ serverId: string }`                                                 |
    | **Returns**    | `{ serverId: string, ok: boolean, latencyMs: number, error?: string }` |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "mcp.list",
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "servers": [
            { "id": "filesystem", "status": "connected", "tools": 5 }
          ]
        },
        "id": 1
      }
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="skills (6 methods)">
    Prompt skill management: list, create, upload, import from GitHub, update, and delete skills.

    | Method          | Scope   | Description                                                     |
    | --------------- | ------- | --------------------------------------------------------------- |
    | `skills.list`   | `rpc`   | List prompt skills for an agent                                 |
    | `skills.create` | `admin` | Create a new skill from SKILL.md content (single-file shortcut) |
    | `skills.upload` | `admin` | Upload a skill folder (files + SKILL.md)                        |
    | `skills.import` | `admin` | Import a skill from a GitHub repository URL                     |
    | `skills.update` | `admin` | Update an existing skill's SKILL.md content                     |
    | `skills.delete` | `admin` | Delete a skill                                                  |

    ### `skills.list`

    | Property       | Value                            |
    | -------------- | -------------------------------- |
    | **Scope**      | `rpc`                            |
    | **Parameters** | `{ agentId?: string }`           |
    | **Returns**    | `{ skills: SkillDescription[] }` |

    ### Install vetting

    `skills.upload`, `skills.import`, `skills.create`, and `skills.update` all run the same pre-write vetting gate: every text file in the bundle is scanned, the bundle's structure and manifest are checked, and nothing is written unless the result is acceptable for the skill's trust tier. See [Security Scanning](/skills/security-scanning) for the tier × verdict matrix.

    Two failure shapes are distinct and both surface as JSON-RPC errors:

    * `[skill_vet_confirm:<verdict>]` — the install needs an explicit acknowledgement. The message names every finding and its location. Re-run the identical call with `force: true` to proceed.
    * `[skill_vet_rejected:<verdict>]` — the install is refused. `force` does **not** override this.

    `force` therefore means "I read the findings and accept them", never "skip the scan". On `skills.upload` and `skills.import` it additionally archives a conflicting bundled-MCP entry under `_bundleArchive`.

    ### `skills.upload`

    | Property       | Value                                                                                                                               |
    | -------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                                                                                             |
    | **Parameters** | `{ name: string, agentId?: string, scope?: "local" \| "shared", force?: boolean, files: Array<{ path: string, content: string }> }` |
    | **Returns**    | `{ ok: true, path: string }`                                                                                                        |

    The `name` must be 1-64 characters, lowercase alphanumeric with hyphens. The `files` array must include a `SKILL.md` file.

    The optional `scope` parameter controls where the skill is written. `"local"` (default) writes to the calling agent's workspace skills directory. `"shared"` writes to the global skills directory visible to all agents. Only the default agent (set via `routing.defaultAgentId`) can use `scope: "shared"`.

    <CodeGroup>
      ```json Request (local scope) theme={}
      {
        "jsonrpc": "2.0",
        "method": "skills.upload",
        "params": {
          "name": "my-skill",
          "agentId": "researcher",
          "scope": "local",
          "files": [
            { "path": "SKILL.md", "content": "---\nname: my-skill\ndescription: A custom skill\n---\nInstructions here." }
          ]
        },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": { "ok": true, "path": "/home/user/.comis/workspace-researcher/skills/my-skill" },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `skills.import`

    | Property       | Value                                                                             |
    | -------------- | --------------------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                                           |
    | **Parameters** | `{ url: string, agentId?: string, scope?: "local" \| "shared", force?: boolean }` |
    | **Returns**    | `{ ok: true, path: string, name: string, fileCount: number }`                     |

    Accepts GitHub directory URLs in the format: `https://github.com/{owner}/{repo}/tree/{branch}/{path}`

    The optional scope parameter works the same as in skills.upload -- "local" (default) imports to the agent's workspace, "shared" imports to the global directory (default agent only).

    ### `skills.create`

    Create a new skill by providing its full SKILL.md content directly — a convenient shortcut over `skills.upload` when your skill is a single file. The content is vetted before being written to disk (see [Install vetting](#install-vetting) below). Fails if a skill with that name already exists; use `skills.update` to modify an existing skill.

    | Property       | Value                                                                                               |
    | -------------- | --------------------------------------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                                                             |
    | **Parameters** | `{ name: string, content: string, agentId?: string, scope?: "local" \| "shared", force?: boolean }` |
    | **Returns**    | `{ ok: true, path: string, name: string }`                                                          |

    `name` must be 1–64 characters, lowercase alphanumeric with hyphens, no leading/trailing/consecutive hyphens. `content` must be a valid SKILL.md string and passes through the same security scanner as `skills.upload`.

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "skills.create",
        "params": {
          "name": "haiku-writer",
          "agentId": "poet",
          "content": "---\nname: haiku-writer\ndescription: Compose haiku on any topic\n---\nWrite a three-line haiku about the user's topic."
        },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "ok": true,
          "path": "/home/user/.comis/workspace-poet/skills/haiku-writer",
          "name": "haiku-writer"
        },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `skills.update`

    Replace the SKILL.md content of an existing skill. The skill directory must already exist — use `skills.create` or `skills.upload` for new skills. The updated content is security-scanned before being written.

    | Property       | Value                                                                                               |
    | -------------- | --------------------------------------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                                                             |
    | **Parameters** | `{ name: string, content: string, agentId?: string, scope?: "local" \| "shared", force?: boolean }` |
    | **Returns**    | `{ ok: true, path: string, name: string }`                                                          |

    ### `skills.delete`

    | Property       | Value                                                             |
    | -------------- | ----------------------------------------------------------------- |
    | **Scope**      | `admin`                                                           |
    | **Parameters** | `{ name: string, agentId?: string, scope?: "local" \| "shared" }` |
    | **Returns**    | `{ ok: true }`                                                    |

    When deleting with `scope: "local"` (default), the skill must exist in the agent's workspace directory. Attempting to delete a shared skill with local scope returns an error with guidance to use `scope: "shared"`. Only the default agent can delete shared skills.
  </Accordion>

  <Accordion title="graph (12 methods)">
    Graph execution engine for multi-step agent pipelines (DAGs).

    | Method            | Scope | Description                                    |
    | ----------------- | ----- | ---------------------------------------------- |
    | `graph.cancel`    | `rpc` | Cancel a running graph execution               |
    | `graph.define`    | `rpc` | Define an execution graph                      |
    | `graph.delete`    | `rpc` | Delete a saved graph definition                |
    | `graph.deleteRun` | `rpc` | Delete a graph execution run and its artifacts |
    | `graph.execute`   | `rpc` | Execute a defined graph                        |
    | `graph.list`      | `rpc` | List saved graph definitions                   |
    | `graph.load`      | `rpc` | Load a named graph definition                  |
    | `graph.outputs`   | `rpc` | Retrieve node output values from a graph       |
    | `graph.runDetail` | `rpc` | Get detailed info for a specific execution run |
    | `graph.runs`      | `rpc` | List recent graph execution runs               |
    | `graph.save`      | `rpc` | Save a named graph definition                  |
    | `graph.status`    | `rpc` | Get graph execution status                     |

    ### `graph.define`

    | Property       | Value                                                  |
    | -------------- | ------------------------------------------------------ |
    | **Scope**      | `rpc`                                                  |
    | **Parameters** | `{ nodes: NodeDefinition[], edges: EdgeDefinition[] }` |
    | **Returns**    | `{ graphId: string }`                                  |

    Each `NodeDefinition` includes `node_id`, `task`, and optional fields: `depends_on`, `agent`, `model`, `timeout_ms`, `max_steps`, `barrier_mode`, `retries`, `type_id`, `type_config`, `context_mode`. When `type_id` is set, `type_config` is validated against the driver's config schema. See [Node Types](/developer-guide/pipelines#node-types) for available types.

    **typeConfig validation errors:** When `type_id` is set and `type_config` does not match the driver's schema, the method returns an error with a `schemaToExample` hint showing the expected config shape. This hint helps LLMs self-correct invalid configurations.

    ```json theme={}
    {
      "error": {
        "code": -32602,
        "message": "Node \"evaluate\" type_config invalid: agents: Required. Expected: {\"agents\":\"array\",\"rounds\":\"number (optional)\",\"synthesizer\":\"string (optional)\"}"
      }
    }
    ```

    The `Expected:` suffix is generated by `schemaToExample()` and shows each field's expected type with optionality markers.

    **Graph warnings for typed nodes:** Both `graph.define` and `graph.execute` return a `warnings` array for soft validation issues that an LLM can fix before execution:

    | Warning Type                 | Description                                                                |
    | ---------------------------- | -------------------------------------------------------------------------- |
    | `typed_node_agentid_ignored` | `agentId` set on a typed node is ignored -- agents come from `type_config` |
    | `typed_node_expensive_retry` | `retries > 0` on a typed node re-runs the entire driver from scratch       |
    | `typed_node_approval_retry`  | Retries on an `approval-gate` node will re-prompt the user                 |

    ### `graph.execute`

    | Property       | Value                                                   |
    | -------------- | ------------------------------------------------------- |
    | **Scope**      | `rpc`                                                   |
    | **Parameters** | `{ graphId: string, inputs?: Record<string, unknown> }` |
    | **Returns**    | Execution result with per-node outputs                  |

    If the graph contains `approval-gate` nodes, the request must include `announceChannelType` and `announceChannelId` for user interaction routing.

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "graph.execute",
        "params": { "graphId": "research-pipeline" },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "executionId": "exec_abc123",
          "status": "completed",
          "outputs": {}
        },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `graph.status`

    Returns graph execution status. This method has two response forms depending on whether a `graphId` parameter is provided.

    | Property       | Value                                                                                     |
    | -------------- | ----------------------------------------------------------------------------------------- |
    | **Scope**      | `rpc`                                                                                     |
    | **Parameters** | `{ graphId?: string, recentMinutes?: number }`                                            |
    | **Returns**    | Per-node status (with `graphId`) or graph list with concurrency stats (without `graphId`) |

    **With `graphId`** -- Returns detailed per-node execution state for a single graph, including node outputs (truncated to 500 characters), timing, and aggregate stats.

    <CodeGroup>
      ```json Request (single graph) theme={}
      {
        "jsonrpc": "2.0",
        "method": "graph.status",
        "params": { "graphId": "abc-123" },
        "id": 1
      }
      ```

      ```json Response (single graph) theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "graphId": "abc-123",
          "label": "Research Pipeline",
          "status": "running",
          "isTerminal": false,
          "executionOrder": ["research", "analyze", "write"],
          "nodes": {
            "research": { "status": "completed", "output": "Found 5 sources...", "durationMs": 4200 },
            "analyze": { "status": "running", "output": null },
            "write": { "status": "pending", "output": null }
          },
          "stats": { "total": 3, "completed": 1, "failed": 0, "skipped": 0, "running": 1, "pending": 1 }
        },
        "id": 1
      }
      ```
    </CodeGroup>

    **Without `graphId`** -- Returns a list of all recent graphs with aggregate status, plus live concurrency statistics. Use the optional `recentMinutes` parameter to filter to graphs active within the specified time window.

    <CodeGroup>
      ```json Request (list all) theme={}
      {
        "jsonrpc": "2.0",
        "method": "graph.status",
        "id": 1
      }
      ```

      ```json Response (list all) theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "graphs": [
            {
              "graphId": "abc-123",
              "label": "Research Pipeline",
              "status": "running",
              "stats": { "total": 3, "completed": 1, "failed": 0, "skipped": 0, "running": 1, "pending": 1 }
            }
          ],
          "concurrency": {
            "globalActiveSubAgents": 3,
            "maxGlobalSubAgents": 20,
            "queueDepth": 0
          }
        },
        "id": 1
      }
      ```
    </CodeGroup>

    The `concurrency` object reports the current state of the [three-tier concurrency model](/developer-guide/pipelines#three-tier-concurrency-model): `globalActiveSubAgents` is the count of currently running sub-agents across all graphs, `maxGlobalSubAgents` is the configured cap, and `queueDepth` is the number of spawns waiting in the FIFO queue.

    ### `graph.outputs`

    Retrieve node output values from a completed or running graph. Uses memory-first retrieval with disk fallback for expired graphs.

    | Property       | Value                                                                                      |
    | -------------- | ------------------------------------------------------------------------------------------ |
    | **Scope**      | `rpc`                                                                                      |
    | **Parameters** | `{ graphId: string }`                                                                      |
    | **Returns**    | `{ graphId: string, outputs: Record<string, string \| null>, source: "memory" \| "disk" }` |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "graph.outputs",
        "params": { "graphId": "abc-123-def" },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "graphId": "abc-123-def",
          "outputs": {
            "search": "Found 5 sources on quantum computing...",
            "analyze": "Key trends: 1) ..., 2) ..., 3) ...",
            "write": null
          },
          "source": "memory"
        },
        "id": 1
      }
      ```
    </CodeGroup>

    `outputs` is a `Record<string, string \| null>` -- `null` means the node has not completed yet. `source` is `"memory"` (from the in-memory coordinator) or `"disk"` (from `*-output.md` files in `graph-runs/<graphId>/`). Output values are truncated at 12,000 characters per node (compared to 500 for `graph.status`).

    ### `graph.cancel`

    Cancel a running graph execution. Nodes that are already in-flight are allowed to finish, but no new nodes will be dispatched after the cancellation is processed.

    | Property       | Value                                  |
    | -------------- | -------------------------------------- |
    | **Scope**      | `rpc`                                  |
    | **Parameters** | `{ graphId: string }`                  |
    | **Returns**    | `{ graphId: string, cancelled: true }` |

    ### `graph.save`

    Save a graph definition under a human-readable name so you can reload it later with `graph.load`. The `graphId` refers to a graph you have already defined with `graph.define`.

    | Property       | Value                               |
    | -------------- | ----------------------------------- |
    | **Scope**      | `rpc`                               |
    | **Parameters** | `{ name: string, graphId: string }` |
    | **Returns**    | `{ name: string, saved: true }`     |

    ### `graph.load`

    Load a previously saved named graph definition and return a fresh `graphId` you can execute. This is equivalent to calling `graph.define` with the saved node/edge structure.

    | Property       | Value                               |
    | -------------- | ----------------------------------- |
    | **Scope**      | `rpc`                               |
    | **Parameters** | `{ name: string }`                  |
    | **Returns**    | `{ graphId: string, name: string }` |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "graph.load",
        "params": { "name": "research-pipeline" },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "graphId": "abc-def-123",
          "name": "research-pipeline"
        },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `graph.list`

    List all saved named graph definitions. Returns the name, creation timestamp, and node count for each entry.

    | Property       | Value                                                                       |
    | -------------- | --------------------------------------------------------------------------- |
    | **Scope**      | `rpc`                                                                       |
    | **Parameters** | None                                                                        |
    | **Returns**    | `{ graphs: Array<{ name: string, createdAt: number, nodeCount: number }> }` |

    ### `graph.delete`

    Delete a saved named graph definition. This only removes the saved definition — it does not affect any in-progress executions or stored run artifacts.

    | Property       | Value                             |
    | -------------- | --------------------------------- |
    | **Scope**      | `rpc`                             |
    | **Parameters** | `{ name: string }`                |
    | **Returns**    | `{ name: string, deleted: true }` |

    ### `graph.deleteRun`

    | Property       | Value                 |
    | -------------- | --------------------- |
    | **Scope**      | `rpc`                 |
    | **Parameters** | `{ runId: string }`   |
    | **Returns**    | Deletion confirmation |

    ### `graph.runDetail`

    | Property       | Value                                                      |
    | -------------- | ---------------------------------------------------------- |
    | **Scope**      | `rpc`                                                      |
    | **Parameters** | `{ runId: string }`                                        |
    | **Returns**    | Detailed run info with per-node state, outputs, and timing |

    ### `graph.runs`

    | Property       | Value                                                  |
    | -------------- | ------------------------------------------------------ |
    | **Scope**      | `rpc`                                                  |
    | **Parameters** | `{ limit?: number }`                                   |
    | **Returns**    | `{ runs: object[] }` -- recent graph execution history |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "graph.runs",
        "params": { "limit": 5 },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "runs": [
            { "runId": "run_abc", "graphId": "research-pipeline", "status": "completed", "startedAt": 1773313498000, "durationMs": 45000 }
          ]
        },
        "id": 1
      }
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="heartbeat (4 methods)">
    Per-agent heartbeat management for automated check-in and health monitoring.

    | Method              | Scope   | Description                                                         |
    | ------------------- | ------- | ------------------------------------------------------------------- |
    | `heartbeat.states`  | `admin` | Get all agent heartbeat states (enabled, interval, errors, backoff) |
    | `heartbeat.get`     | `admin` | Get heartbeat config for a specific agent                           |
    | `heartbeat.update`  | `admin` | Update heartbeat configuration with persistence                     |
    | `heartbeat.trigger` | `admin` | Trigger an immediate heartbeat run for an agent                     |

    ### `heartbeat.states`

    | Property       | Value                                                                                                                         |
    | -------------- | ----------------------------------------------------------------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                                                                                       |
    | **Parameters** | None                                                                                                                          |
    | **Returns**    | `{ agents: Array<{ agentId, enabled, intervalMs, lastRunMs, nextDueMs, consecutiveErrors, backoffUntilMs, lastErrorKind }> }` |

    ### `heartbeat.get`

    | Property       | Value                                                      |
    | -------------- | ---------------------------------------------------------- |
    | **Scope**      | `admin`                                                    |
    | **Parameters** | `{ agentId: string }` (required)                           |
    | **Returns**    | `{ agentId: string, perAgent: object, effective: object }` |

    ### `heartbeat.update`

    | Property       | Value                                                                                                                                       |
    | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                                                                                                     |
    | **Parameters** | `{ agentId: string, enabled?: boolean, intervalMs?: number, prompt?: string, model?: string, showOk?: boolean, showAlerts?: boolean, ... }` |
    | **Returns**    | `{ agentId: string, config: object, updated: true }`                                                                                        |

    Updates are deep-merged with existing configuration and persisted to the YAML config file.

    ### `heartbeat.trigger`

    | Property       | Value                                  |
    | -------------- | -------------------------------------- |
    | **Scope**      | `admin`                                |
    | **Parameters** | `{ agentId: string }` (required)       |
    | **Returns**    | `{ agentId: string, triggered: true }` |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "heartbeat.states",
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "agents": [
            {
              "agentId": "default",
              "enabled": true,
              "intervalMs": 300000,
              "lastRunMs": 1773313200000,
              "nextDueMs": 1773313500000,
              "consecutiveErrors": 0,
              "backoffUntilMs": 0,
              "lastErrorKind": null
            }
          ]
        },
        "id": 1
      }
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="context (2 methods)">
    LCD lossless-store RPCs. Both methods are `rpc`-scoped and back the dashboard's Context DAG browser.

    | Method                  | Scope | Description                                                           |
    | ----------------------- | ----- | --------------------------------------------------------------------- |
    | `context.conversations` | `rpc` | List the calling agent's distinct LCD conversations (paginated)       |
    | `context.tree`          | `rpc` | Get a conversation's resolved DAG (summary nodes + raw-message count) |

    <Note>
      The `rpc`-scoped browse methods are AGENT+TENANT scoped: the calling agent comes from the request context and the tenant from the daemon — neither is a caller-supplied parameter, so a browse can never read another agent's (or tenant's) conversations.

      The in-session context **tools** an agent uses while running — `ctx_search`, `ctx_inspect`, `ctx_expand` (deep recall via `ctx_recall`) — are agent tools invoked through the executor, **not** gateway RPC methods, and are documented under the agent tool reference rather than here.

      Two further operator-browse RPCs the Context DAG browser will use — `context.inspect` (per-node metadata + taint-wrapped content recovery) and `context.searchByConversation` (full-text search within one conversation) — are **not yet registered**; calling them returns JSON-RPC `-32601` (method not found). The browser degrades to loading and rendering the DAG structure without per-node deep-inspect or in-conversation search until they ship.

      The explicit conversation reset command is `session.reset_conversation` (admin-scoped), documented in the `sessions` accordion below.
    </Note>

    ### `context.conversations`

    | Property       | Value                                                                                                                                                                                                                                                                                                                       |
    | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | **Scope**      | `rpc`                                                                                                                                                                                                                                                                                                                       |
    | **Parameters** | `{ limit?: number, offset?: number }` (defaults: `limit` 100, `offset` 0; the agent + tenant are resolved from the request context)                                                                                                                                                                                         |
    | **Returns**    | `{ conversations: Array<{ conversation_id, tenant_id, agent_id, session_key, title, created_at, updated_at, message_count }>, total }` — `title` is always `null` (the LCD store has no per-conversation title); `created_at` / `updated_at` are ISO-8601 strings derived from the conversation's earliest / latest message |

    ### `context.tree`

    | Property       | Value                                                                                                                                                                                                                                                                                                                                                                      |
    | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | **Scope**      | `rpc`                                                                                                                                                                                                                                                                                                                                                                      |
    | **Parameters** | `{ conversation_id: string }` (the agent + tenant are resolved from the request context)                                                                                                                                                                                                                                                                                   |
    | **Returns**    | `{ conversationId, nodes: Array<{ summaryId, kind, depth, tokenCount, contentPreview, childIds, parentIds, taint, createdAt }>, messageCount }` — `messageCount` counts raw turns not yet collapsed into a summary; `contentPreview` is a bounded, untrusted-origin slice surfaced for the human operator view (never re-fed to a model) and `taint` flags untrusted nodes |
  </Accordion>

  <Accordion title="workspace (12 methods)">
    Agent workspace file and git management. Read, write, and delete workspace files, browse directories, and manage git history.

    | Method                  | Scope   | Description                                  |
    | ----------------------- | ------- | -------------------------------------------- |
    | `workspace.status`      | `rpc`   | Get workspace status and file list           |
    | `workspace.readFile`    | `rpc`   | Read a workspace file's content              |
    | `workspace.writeFile`   | `admin` | Create or overwrite a workspace file         |
    | `workspace.deleteFile`  | `admin` | Delete a workspace file                      |
    | `workspace.listDir`     | `rpc`   | List directory contents                      |
    | `workspace.resetFile`   | `admin` | Reset a template file to its default content |
    | `workspace.init`        | `admin` | Initialize or re-bootstrap a workspace       |
    | `workspace.git.status`  | `rpc`   | Get git working tree status                  |
    | `workspace.git.log`     | `rpc`   | Get recent commit history                    |
    | `workspace.git.diff`    | `rpc`   | Get unified diff of changes                  |
    | `workspace.git.commit`  | `admin` | Commit staged or all changes                 |
    | `workspace.git.restore` | `admin` | Discard changes to a file                    |

    ### `workspace.status`

    | Property       | Value                                                                                             |
    | -------------- | ------------------------------------------------------------------------------------------------- |
    | **Scope**      | `rpc`                                                                                             |
    | **Parameters** | `{ agentId: string }`                                                                             |
    | **Returns**    | `{ dir: string, exists: boolean, files: string[], hasGitRepo: boolean, isBootstrapped: boolean }` |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "workspace.status",
        "params": { "agentId": "my-agent" },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "dir": "/home/user/.comis/agents/my-agent/workspace",
          "exists": true,
          "files": ["SOUL.md", "IDENTITY.md", "USER.md", "AGENTS.md", "ROLE.md", "TOOLS.md", "HEARTBEAT.md", "BOOTSTRAP.md", "BOOT.md"],
          "hasGitRepo": true,
          "isBootstrapped": true
        },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `workspace.readFile`

    | Property       | Value                                    |
    | -------------- | ---------------------------------------- |
    | **Scope**      | `rpc`                                    |
    | **Parameters** | `{ agentId: string, filePath: string }`  |
    | **Returns**    | `{ content: string, sizeBytes: number }` |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "workspace.readFile",
        "params": { "agentId": "my-agent", "filePath": "SOUL.md" },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "content": "# Soul\n\nYou are a helpful assistant...",
          "sizeBytes": 245
        },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `workspace.writeFile`

    | Property       | Value                                                    |
    | -------------- | -------------------------------------------------------- |
    | **Scope**      | `admin`                                                  |
    | **Parameters** | `{ agentId: string, filePath: string, content: string }` |
    | **Returns**    | `{ written: true, sizeBytes: number }`                   |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "workspace.writeFile",
        "params": {
          "agentId": "my-agent",
          "filePath": "IDENTITY.md",
          "content": "# Identity\n\nYou are Aria, a research assistant."
        },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "written": true,
          "sizeBytes": 48
        },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `workspace.deleteFile`

    | Property       | Value                                   |
    | -------------- | --------------------------------------- |
    | **Scope**      | `admin`                                 |
    | **Parameters** | `{ agentId: string, filePath: string }` |
    | **Returns**    | `{ deleted: true }`                     |

    ### `workspace.listDir`

    | Property       | Value                                                                                                       |
    | -------------- | ----------------------------------------------------------------------------------------------------------- |
    | **Scope**      | `rpc`                                                                                                       |
    | **Parameters** | `{ agentId: string, subdir?: string }`                                                                      |
    | **Returns**    | `{ entries: Array<{ name: string, type: "file" \| "directory", sizeBytes?: number, modifiedAt: string }> }` |

    ### `workspace.resetFile`

    | Property       | Value                                   |
    | -------------- | --------------------------------------- |
    | **Scope**      | `admin`                                 |
    | **Parameters** | `{ agentId: string, fileName: string }` |
    | **Returns**    | `{ reset: true, fileName: string }`     |

    ### `workspace.init`

    | Property       | Value                                |
    | -------------- | ------------------------------------ |
    | **Scope**      | `admin`                              |
    | **Parameters** | `{ agentId: string }`                |
    | **Returns**    | `{ initialized: true, dir: string }` |

    ### `workspace.git.status`

    | Property       | Value                                                                                  |
    | -------------- | -------------------------------------------------------------------------------------- |
    | **Scope**      | `rpc`                                                                                  |
    | **Parameters** | `{ agentId: string }`                                                                  |
    | **Returns**    | `{ branch: string, clean: boolean, entries: Array<{ path: string, status: string }> }` |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "workspace.git.status",
        "params": { "agentId": "my-agent" },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "branch": "main",
          "clean": false,
          "entries": [
            { "path": "SOUL.md", "status": "M" },
            { "path": "notes.md", "status": "??" }
          ]
        },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `workspace.git.log`

    | Property       | Value                                                                                |
    | -------------- | ------------------------------------------------------------------------------------ |
    | **Scope**      | `rpc`                                                                                |
    | **Parameters** | `{ agentId: string, limit?: number }`                                                |
    | **Returns**    | `{ commits: Array<{ sha: string, author: string, date: string, message: string }> }` |

    ### `workspace.git.diff`

    | Property       | Value                                    |
    | -------------- | ---------------------------------------- |
    | **Scope**      | `rpc`                                    |
    | **Parameters** | `{ agentId: string, filePath?: string }` |
    | **Returns**    | `{ diff: string }`                       |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "workspace.git.diff",
        "params": { "agentId": "my-agent", "filePath": "SOUL.md" },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "diff": "diff --git a/SOUL.md b/SOUL.md\nindex abc1234..def5678 100644\n--- a/SOUL.md\n+++ b/SOUL.md\n@@ -1,3 +1,3 @@\n # Soul\n-You are a helpful assistant.\n+You are a creative writing assistant."
        },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `workspace.git.commit`

    | Property       | Value                                                            |
    | -------------- | ---------------------------------------------------------------- |
    | **Scope**      | `admin`                                                          |
    | **Parameters** | `{ agentId: string, message?: string, paths?: string[] }`        |
    | **Returns**    | `{ sha: string, author: string, date: string, message: string }` |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "workspace.git.commit",
        "params": {
          "agentId": "my-agent",
          "message": "Update agent personality"
        },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "sha": "a1b2c3d",
          "author": "comis",
          "date": "2026-03-20T12:00:00Z",
          "message": "Update agent personality"
        },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `workspace.git.restore`

    | Property       | Value                                   |
    | -------------- | --------------------------------------- |
    | **Scope**      | `admin`                                 |
    | **Parameters** | `{ agentId: string, filePath: string }` |
    | **Returns**    | `{ restored: true }`                    |

    <Warning>
      Write operations (`writeFile`, `deleteFile`, `resetFile`, `init`, `git.commit`, `git.restore`) require `admin` scope. Read operations (`status`, `readFile`, `listDir`, `git.status`, `git.log`, `git.diff`) require only `rpc` scope.
    </Warning>
  </Accordion>

  <Accordion title="daemon (1 method)">
    Runtime daemon management.

    | Method               | Scope   | Description                              |
    | -------------------- | ------- | ---------------------------------------- |
    | `daemon.setLogLevel` | `admin` | Change the daemon's log level at runtime |

    ### `daemon.setLogLevel`

    | Property       | Value                                                                                               |
    | -------------- | --------------------------------------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                                                             |
    | **Parameters** | `{ level: string }` -- valid values: `"trace"`, `"debug"`, `"info"`, `"warn"`, `"error"`, `"fatal"` |
    | **Returns**    | `{ updated: true, level: string }`                                                                  |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "daemon.setLogLevel",
        "params": { "level": "debug" },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "updated": true,
          "level": "debug"
        },
        "id": 1
      }
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="discord (1 method)">
    Discord platform action dispatch. Sends a platform-specific action to the Discord channel adapter.

    | Method           | Scope   | Description                                                         |
    | ---------------- | ------- | ------------------------------------------------------------------- |
    | `discord.action` | `admin` | Dispatch a Discord platform action (e.g., set status, manage roles) |

    ### `discord.action`

    | Property       | Value                                                                                                                                                                                                                                                                                             |
    | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                                                                                                                                                                                                                                                           |
    | **Parameters** | `{ action: string, channel_id: string, endpoint?: ChannelEndpoint, ...platformParams }` -- the `action` string selects the operation; additional parameters vary per action. Gateway clients must supply the exact endpoint. See [Discord channel docs](/channels/discord) for available actions. |
    | **Returns**    | Platform-specific result object                                                                                                                                                                                                                                                                   |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "discord.action",
        "params": {
          "action": "set_status",
          "channel_id": "channel789",
          "endpoint": {
            "channelType": "discord",
            "channelInstanceId": "discord-main",
            "conversationId": "channel789",
            "conversationKind": "shared"
          },
          "status": "online",
          "activity": "Monitoring channels"
        },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "ok": true,
          "action": "set_status"
        },
        "id": 1
      }
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="media (7 methods)">
    Media processing test methods and provider inspection. All methods require `admin` scope. Use these to verify STT, TTS, vision, document extraction, video analysis, and link processing configurations.

    | Method                | Scope   | Description                                 |
    | --------------------- | ------- | ------------------------------------------- |
    | `media.test.stt`      | `admin` | Test speech-to-text transcription           |
    | `media.test.tts`      | `admin` | Test text-to-speech synthesis               |
    | `media.test.vision`   | `admin` | Test image analysis                         |
    | `media.test.document` | `admin` | Test document text extraction               |
    | `media.test.video`    | `admin` | Test video analysis                         |
    | `media.test.link`     | `admin` | Test link content processing                |
    | `media.providers`     | `admin` | List configured media provider capabilities |

    ### `media.test.stt`

    | Property       | Value                                                                           |
    | -------------- | ------------------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                                         |
    | **Parameters** | `{ audio?: string, language?: string }` -- `audio` is base64-encoded audio data |
    | **Returns**    | Transcription result with text and metadata                                     |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "media.test.stt",
        "params": { "audio": "T2dnUwACAAAAAAA...", "language": "en" },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "text": "Hello world",
          "language": "en",
          "durationMs": 1500
        },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `media.test.tts`

    | Property       | Value                                                 |
    | -------------- | ----------------------------------------------------- |
    | **Scope**      | `admin`                                               |
    | **Parameters** | `{ text: string, voice?: string, provider?: string }` |
    | **Returns**    | Audio result with base64-encoded audio data           |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "media.test.tts",
        "params": { "text": "Hello world", "voice": "alloy" },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "audio": "UklGRi4A...",
          "mimeType": "audio/mp3",
          "durationMs": 2100
        },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `media.test.vision`

    | Property       | Value                                                                           |
    | -------------- | ------------------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                                         |
    | **Parameters** | `{ image?: string, url?: string }` -- provide either base64 image data or a URL |
    | **Returns**    | Analysis result with description                                                |

    ### `media.test.document`

    | Property       | Value                                    |
    | -------------- | ---------------------------------------- |
    | **Scope**      | `admin`                                  |
    | **Parameters** | `{ file_path: string }`                  |
    | **Returns**    | Extracted text content from the document |

    ### `media.test.video`

    | Property       | Value                 |
    | -------------- | --------------------- |
    | **Scope**      | `admin`               |
    | **Parameters** | `{ url?: string }`    |
    | **Returns**    | Video analysis result |

    ### `media.test.link`

    | Property       | Value                                                 |
    | -------------- | ----------------------------------------------------- |
    | **Scope**      | `admin`                                               |
    | **Parameters** | `{ url: string }`                                     |
    | **Returns**    | Processed link content (article extraction, metadata) |

    ### `media.providers`

    | Property       | Value                                                                 |
    | -------------- | --------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                               |
    | **Parameters** | None                                                                  |
    | **Returns**    | `{ providers: object }` -- configured media capabilities per provider |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "media.providers",
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "providers": {
            "stt": { "configured": true, "provider": "openai" },
            "tts": { "configured": true, "provider": "elevenlabs" },
            "vision": { "configured": true, "provider": "openai" },
            "imageGen": { "configured": false }
          }
        },
        "id": 1
      }
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="message (7 methods)">
    Cross-channel message operations: send, reply, edit, delete, fetch, react, and
    attach files. `message.send`, `message.reply`, and `message.react` are
    `rpc`-scoped outward operations guarded by the `orch:message` capability.
    Editing, deleting, fetching, and attaching remain `admin`-scoped. Every method
    requires the complete [`ChannelEndpoint`](#session-history) for its target and
    verifies the channel type, adapter instance, and conversation ID against the
    other request coordinates. The request schema keeps `endpoint` optional only
    because an authenticated in-process turn can supply the same endpoint through
    request context; gateway clients must send it explicitly. Missing or
    conflicting authority fails closed before any adapter action.

    <Note>
      **Durable duplicate suppression for autonomy-originated sends.** When
      [`autonomy.durability.enabled`](/reference/config-yaml#autonomy-agentsautonomy)
      is on, an outward `message.send` / `message.reply` / `message.react` issued by an
      autonomy run (a run with a `rootRunId`) uses one stable logical operation
      identity. While its ledger row is retained, a committed repeat returns the prior
      platform receipt without another send, and every non-committed existing state
      blocks another execution. A call whose platform outcome may be ambiguous is
      parked `unresolved` and escalated; startup applies that rule to every interrupted
      row without a channel-history query or replay. This is not a universal
      exactly-once guarantee. See [Durability & Resume](/agents/autonomy#durability-resume)
      for the uncertainty model. Operator-issued sends (no `rootRunId`) remain
      best-effort pass-through operations.
    </Note>

    | Method           | Scope   | Description                          |
    | ---------------- | ------- | ------------------------------------ |
    | `message.send`   | `rpc`   | Send a message to a channel          |
    | `message.reply`  | `rpc`   | Reply to a specific message          |
    | `message.edit`   | `admin` | Edit an existing message             |
    | `message.delete` | `admin` | Delete a message                     |
    | `message.fetch`  | `admin` | Fetch recent messages from a channel |
    | `message.react`  | `rpc`   | Add a reaction to a message          |
    | `message.attach` | `admin` | Send a file attachment to a channel  |

    ### `message.send`

    | Property       | Value                                                                                                                                                                        |
    | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | **Scope**      | `rpc`                                                                                                                                                                        |
    | **Parameters** | `{ channel_type: string, channel_id: string, endpoint?: ChannelEndpoint, text: string, buttons?: object[][], cards?: object[], effects?: object[], thread_reply?: boolean }` |
    | **Returns**    | `{ messageId: string, channelId: string }`                                                                                                                                   |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "message.send",
        "params": {
          "channel_type": "telegram",
          "channel_id": "chat123",
          "endpoint": {
            "channelType": "telegram",
            "channelInstanceId": "telegram-main",
            "conversationId": "chat123",
            "conversationKind": "direct"
          },
          "text": "Hello from the API!"
        },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "messageId": "msg_456",
          "channelId": "chat123"
        },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `message.reply`

    | Property       | Value                                                                                                                                                                                            |
    | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
    | **Scope**      | `rpc`                                                                                                                                                                                            |
    | **Parameters** | `{ channel_type: string, channel_id: string, endpoint?: ChannelEndpoint, text: string, message_id: string, buttons?: object[][], cards?: object[], effects?: object[], thread_reply?: boolean }` |
    | **Returns**    | `{ messageId: string, channelId: string }`                                                                                                                                                       |

    ### `message.edit`

    | Property       | Value                                                                                                        |
    | -------------- | ------------------------------------------------------------------------------------------------------------ |
    | **Scope**      | `admin`                                                                                                      |
    | **Parameters** | `{ channel_type: string, channel_id: string, endpoint?: ChannelEndpoint, message_id: string, text: string }` |
    | **Returns**    | `{ edited: true, channelId: string, messageId: string }`                                                     |

    ### `message.delete`

    | Property       | Value                                                                                          |
    | -------------- | ---------------------------------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                                                        |
    | **Parameters** | `{ channel_type: string, channel_id: string, endpoint?: ChannelEndpoint, message_id: string }` |
    | **Returns**    | `{ deleted: true, channelId: string, messageId: string }`                                      |

    ### `message.fetch`

    | Property       | Value                                                                                                       |
    | -------------- | ----------------------------------------------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                                                                     |
    | **Parameters** | `{ channel_type: string, channel_id: string, endpoint?: ChannelEndpoint, limit?: number, before?: string }` |
    | **Returns**    | `{ messages: object[], channelId: string }`                                                                 |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "message.fetch",
        "params": {
          "channel_type": "discord",
          "channel_id": "channel789",
          "endpoint": {
            "channelType": "discord",
            "channelInstanceId": "discord-main",
            "conversationId": "channel789",
            "conversationKind": "shared"
          },
          "limit": 10
        },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "messages": [
            { "id": "msg_001", "text": "Hello", "author": "user123", "timestamp": 1773313498000 }
          ]
        },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `message.react`

    | Property       | Value                                                                                                         |
    | -------------- | ------------------------------------------------------------------------------------------------------------- |
    | **Scope**      | `rpc`                                                                                                         |
    | **Parameters** | `{ channel_type: string, channel_id: string, endpoint?: ChannelEndpoint, message_id: string, emoji: string }` |
    | **Returns**    | `{ reacted: true, channelId: string, messageId: string, emoji: string }`                                      |

    ### `message.attach`

    | Property       | Value                                                                                                                                                                                                                   |
    | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                                                                                                                                                                                 |
    | **Parameters** | `{ channel_type: string, channel_id: string, endpoint?: ChannelEndpoint, attachment_url: string, attachment_type?: "image" \| "file" \| "audio" \| "video", mime_type?: string, file_name?: string, caption?: string }` |
    | **Returns**    | `{ receipt: { kind: "tracked", messageId: string } \| { kind: "delivered_untracked" }, channelId: string }`                                                                                                             |
  </Accordion>

  <Accordion title="notification (1 method)">
    Send notifications through configured channels.

    | Method              | Scope | Description                 |
    | ------------------- | ----- | --------------------------- |
    | `notification.send` | `rpc` | Send a notification message |

    ### `notification.send`

    | Property       | Value                                                                                                                                         |
    | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
    | **Scope**      | `rpc`                                                                                                                                         |
    | **Parameters** | `{ message: string, priority?: string, channel_type?: string, channel_id?: string, destination_endpoint?: ChannelEndpoint, origin?: string }` |
    | **Returns**    | `{ success: boolean, entryId?: string, error?: string }`                                                                                      |

    The daemon resolves every notification to a complete endpoint before enqueueing
    it. Explicit `channel_type` plus `channel_id`, a configured primary channel,
    and automatic recent-session selection are accepted only when they resolve to
    tracked endpoint authority. `destination_endpoint` is an exact endpoint claim,
    not caller-minted authority: all five fields must match the tracked endpoint for
    that agent, including channel instance, optional thread, and direct/shared kind.
    Resolution fails closed when the tracked endpoint or its active adapter instance
    is unavailable.

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "notification.send",
        "params": {
          "message": "Build completed successfully",
          "priority": "normal"
        },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "success": true,
          "entryId": "notif_abc123"
        },
        "id": 1
      }
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="slack (1 method)">
    Slack platform action dispatch. Sends a platform-specific action to the Slack channel adapter.

    | Method         | Scope   | Description                      |
    | -------------- | ------- | -------------------------------- |
    | `slack.action` | `admin` | Dispatch a Slack platform action |

    ### `slack.action`

    | Property       | Value                                                                                                                                                                                                                                                                                         |
    | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                                                                                                                                                                                                                                                       |
    | **Parameters** | `{ action: string, channel_id: string, endpoint?: ChannelEndpoint, ...platformParams }` -- the `action` string selects the operation; additional parameters vary per action. Gateway clients must supply the exact endpoint. See [Slack channel docs](/channels/slack) for available actions. |
    | **Returns**    | Platform-specific result object                                                                                                                                                                                                                                                               |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "slack.action",
        "params": {
          "action": "set_topic",
          "channel_id": "C0123456",
          "endpoint": {
            "channelType": "slack",
            "channelInstanceId": "slack-main",
            "conversationId": "C0123456",
            "conversationKind": "shared"
          },
          "topic": "Sprint 42 updates"
        },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "ok": true,
          "action": "set_topic"
        },
        "id": 1
      }
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="subagent (7 methods)">
    Sub-agent lifecycle management and process-lifetime spawn admission control. `subagent.list`, `subagent.wait`, `subagent.kill`, and `subagent.steer` have both `rpc` and `admin` routes. Agent callers on the `rpc` route are owner-scoped to runs created by the same caller agent and conversation; they cannot select global scope. Admin callers can inspect or control any selected run. `subagent.pause`, `subagent.resume`, and `subagent.status` are admin-only.

    | Method            | Scope          | Description                                                                 |
    | ----------------- | -------------- | --------------------------------------------------------------------------- |
    | `subagent.list`   | `rpc`, `admin` | List recent owner-scoped or admin-selected runs                             |
    | `subagent.wait`   | `rpc`, `admin` | Wait for bounded completion outcomes without polling                        |
    | `subagent.kill`   | `rpc`, `admin` | Kill an owned or admin-selected running sub-agent                           |
    | `subagent.steer`  | `rpc`, `admin` | Inject into a live child or kill and respawn it, depending on configuration |
    | `subagent.pause`  | `admin`        | Pause admission of new sub-agent spawns for this daemon process             |
    | `subagent.resume` | `admin`        | Resume admission of new sub-agent spawns                                    |
    | `subagent.status` | `admin`        | Inspect the process-lifetime spawn-admission gate                           |

    ### `subagent.list`

    | Property       | Value                                                               |
    | -------------- | ------------------------------------------------------------------- |
    | **Scope**      | `rpc`, `admin`                                                      |
    | **Parameters** | `{ recentMinutes?: integer, agentId?: string, rootRunId?: string }` |
    | **Returns**    | `{ runs: object[], total: number }`                                 |

    `recentMinutes` defaults to 30 and accepts integers from 1 through 10,080. On the `rpc` route, `agentId` and `rootRunId` are forbidden and the response contains only runs owned by the caller's agent-and-conversation pair. Those records are a content-free projection: `runId`, `status`, `agentId`, `startedAt`, and, when present, `queuedAt`, `completion`, `depth`, `rootRunId`, `graphId`, and `nodeId`. Terminal `completion` exposes `endReason`, `completedAtMs`, and `errorKind` for unsuccessful runs. On the `admin` route, `agentId` and `rootRunId` are optional exact-match filters and `runs` contains the full diagnostic run records. `total` is the length of the returned `runs` array.

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "subagent.list",
        "params": { "recentMinutes": 30 },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "runs": [
            { "runId": "run_abc", "status": "running", "agentId": "default", "startedAt": 1774368000000 }
          ],
          "total": 1
        },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `subagent.wait`

    | Property       | Value                                                                |
    | -------------- | -------------------------------------------------------------------- |
    | **Scope**      | `rpc`, `admin`                                                       |
    | **Parameters** | `{ runIds?: string[], timeoutMs?: integer }`                         |
    | **Returns**    | `{ results: WaitResult[] }` (at most 32 entries, in requested order) |

    `runIds`, when supplied, must contain 1–32 IDs; duplicates are removed. Without `runIds`, the handler selects up to 32 currently `running` or `queued` runs visible to the caller. Agent callers can wait only for owned runs; admins can wait for any selected runs. An unknown or unauthorized ID returns `denied_unknown`, deliberately avoiding run enumeration.

    `timeoutMs` accepts 0–300,000. When omitted, it uses `security.agentToAgent.waitTimeoutMs` (default 60,000) capped at 300,000.

    Each `results` entry includes its `runId`; its `status` is `completed`, `denied_unknown`, `timeout`, or `cancelled`:

    * `{ runId, status: "completed", completion }`
    * `{ runId, status: "denied_unknown" }`
    * `{ runId, status: "timeout" }`
    * `{ runId, status: "cancelled" }`

    `completion` always contains `endReason` and `completedAtMs`. A successful completion has `endReason: "completed"`. Failure end reasons are `failed`, `killed`, `watchdog_timeout`, or `ghost_sweep` and also carry `errorKind`. Both forms may include a bounded `summary` and `resultRef`. A `resultRef` contains `ref`, `kind`, `bytes`, `preview`, and `expiresAt`, with optional `rows` and `schema`; `kind` is one of `jsonl`, `json`, `csv`, `html`, `text`, or `binary`.

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "subagent.wait",
        "params": { "runIds": ["run_abc"], "timeoutMs": 60000 },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "results": [
            {
              "runId": "run_abc",
              "status": "completed",
              "completion": { "endReason": "completed", "completedAtMs": 1774368060000, "summary": "Research completed" }
            }
          ]
        },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `subagent.kill`

    | Property       | Value                                                     |
    | -------------- | --------------------------------------------------------- |
    | **Scope**      | `rpc`, `admin`                                            |
    | **Parameters** | `{ target: string }` -- `target` is the sub-agent `runId` |
    | **Returns**    | `{ killed: true, runId: string }`                         |

    Agent callers may kill only an owned direct child. Admin callers may target any run. On success, `killed` is `true` and `runId` repeats the target. A missing, unknown, or non-running target fails the RPC; it does not return `{ killed: false }`.

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "subagent.kill",
        "params": { "target": "run_abc" },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": { "killed": true, "runId": "run_abc" },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `subagent.steer`

    | Property       | Value                                                                              |
    | -------------- | ---------------------------------------------------------------------------------- |
    | **Scope**      | `rpc`, `admin`                                                                     |
    | **Parameters** | `{ target: string, message: string }` -- `target` is the sub-agent `runId`         |
    | **Returns**    | `{ status: "steered", oldRunId, newRunId } \| { status: "steered_inject", runId }` |

    Agent callers may steer only an owned direct child; admins may target any run. Behavior is gated on [`security.agentToAgent.steerInject`](/reference/config-yaml#security), which defaults to `false`:

    The `status` discriminator is `steered` with `oldRunId` and `newRunId`, or `steered_inject` with `runId`.

    * **Flag off (default)** -- kills the current run and respawns a new run with `message` as its task. The continuation preserves the original spawn tree, agent, capabilities, and parent lease, but receives a new run ID and discards the prior live transcript and progress. Returns `{ status: "steered", oldRunId, newRunId }`.
    * **Flag on** -- requires `target` to be `running` and injects `message` into the live child at its next step boundary. It preserves the transcript and progress, performs no kill or respawn, and continues with the same run ID. Returns `{ status: "steered_inject", runId }`. Injection does not interrupt a tool call mid-execution.

    Both modes share a per-controller, per-`target` rate limit of one steer every 2 seconds. The `message` is framed as untrusted external content and cannot grant capabilities or bypass the child's tool restrictions.

    ### `subagent.pause`

    | Property       | Value                                                                                    |
    | -------------- | ---------------------------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                                                  |
    | **Parameters** | `{}` (strict; no public fields)                                                          |
    | **Returns**    | `{ paused: boolean, acceptingSpawns: boolean, resetsOnRestart: true, changed: boolean }` |

    Sets the process-lifetime admission gate to paused. This blocks new sub-agent spawns only; existing running and queued work continues. The returned `paused`, `acceptingSpawns`, and literal `resetsOnRestart` fields describe the resulting gate. The call is idempotent: `changed` is `true` only when the gate transitions from unpaused to paused.

    ### `subagent.resume`

    | Property       | Value                                                                                    |
    | -------------- | ---------------------------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                                                  |
    | **Parameters** | `{}` (strict; no public fields)                                                          |
    | **Returns**    | `{ paused: boolean, acceptingSpawns: boolean, resetsOnRestart: true, changed: boolean }` |

    Clears the process-lifetime pause. The returned `paused`, `acceptingSpawns`, and literal `resetsOnRestart` fields describe the resulting gate. The call is idempotent: `changed` is `true` only when the gate was paused. Resume does not reopen admission after daemon shutdown has set `acceptingSpawns: false`.

    ### `subagent.status`

    | Property       | Value                                                                  |
    | -------------- | ---------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                                |
    | **Parameters** | `{}` (strict; no public fields)                                        |
    | **Returns**    | `{ paused: boolean, acceptingSpawns: boolean, resetsOnRestart: true }` |

    Returns the current in-memory admission state. `paused` is the operator pause; `acceptingSpawns` is the runner's broader admission state and becomes `false` during shutdown. `resetsOnRestart` is always `true`: pause state is not persisted, so every daemon restart starts unpaused.
  </Accordion>

  <Accordion title="capabilities (1 method)">
    The read-only, **agent-reachable** introspection of the [bounded-autonomy](/agents/autonomy) posture — "what can I do + how much budget/quota is left". The companion of the operator-facing live-control methods below (the read side; `lease.revoke` / `run.kill` / `autonomy.evict` are the write side).

    | Method                    | Scope | Description                                                                                                |
    | ------------------------- | ----- | ---------------------------------------------------------------------------------------------------------- |
    | `capabilities.introspect` | `rpc` | The caller's resolved capabilities + remaining per-root budget/quota (self-scoped, no capability required) |

    ### `capabilities.introspect`

    | Property       | Value                                                                                                                                                                                                                                                                                                                                                                                                                                |
    | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
    | **Scope**      | `rpc` (read-only — **no capability required**; the [`comis whoami`](/reference/cli#comis-whoami) surface)                                                                                                                                                                                                                                                                                                                            |
    | **Parameters** | `{}` — **self-scoped**: the caller is resolved from the connection (the dispatcher-injected `_agentId` for an in-process agent, the default agent for an operator token). There is NO `agentId` param — a caller cannot introspect another agent.                                                                                                                                                                                    |
    | **Returns**    | `{ agentId, caps: string[], budget?, outwardQuota? }` — `caps` is the resolved `orch:*` capability list; `budget` (`{ tokensRemaining, wallClockMsRemaining, usdRemaining }`, `usdRemaining` nullable for an unpriceable model) and `outwardQuota` (`{ perHourRemaining }`) are present ONLY when the run has a live root (an in-flight turn / spawn tree), and OMITTED otherwise (in-process pre-spawn) — never fabricated as zero. |

    Read-only and **not capability-gated** (an agent reading its OWN posture needs no capability) and **not** admin-scoped, so the in-process agent loop can reach it directly. It is **self-scoped** — the read returns capabilities/budget for the caller's own `_agentId` only, never an arbitrary agent (an information-disclosure boundary). The remaining budget/quota is **live daemon state** (never persisted), so this is a LIVE-only read with no offline equivalent — the post-mortem authorization topology of a finished run is on [`obs.explain`](#obs-explain)'s `spawnTree` instead.

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "capabilities.introspect",
        "params": {},
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "agentId": "default",
          "caps": ["orch:read", "orch:web"],
          "budget": { "tokensRemaining": 384000, "wallClockMsRemaining": 1740000, "usdRemaining": 4.82 },
          "outwardQuota": { "perHourRemaining": 18 }
        },
        "id": 1
      }
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="autonomy / live control (3 methods)">
    Operator-facing live control of the [bounded-autonomy](/agents/autonomy) control plane: stop, hard-stop, or demote a runaway or misbehaving spawn tree. All three methods are **admin-scoped and deny-by-origin** — they reject a **non-admin** agent-origin call, so a jailed/runaway (non-admin) agent can never invoke them (the bound is external to and non-bypassable by such an agent; there is no "un-revoke"/"un-kill"/"un-evict" method, and a revoked lease's `validate`/`renew` are denied regardless). An admin-trust agent (an explicit operator grant) may invoke them as the admin operating the control plane. The bound mechanisms (spawn ceiling, rate limit, outward quota, per-root budget) are otherwise automatic from the resolved autonomy profile; these methods are the manual override.

    | Method           | Scope   | Description                                                                                                                        |
    | ---------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------- |
    | `lease.revoke`   | `admin` | Cooperative stop — revoke a capability lease so the bearer's next RPC is denied                                                    |
    | `run.kill`       | `admin` | Hard stop — kill a whole spawn tree by `rootRunId` (abort each SDK session) and revoke its leases                                  |
    | `autonomy.evict` | `admin` | Demote-but-continue — drop an in-flight run to the `default` profile by `rootRunId` (it keeps running, but under the safe posture) |

    ### `lease.revoke`

    | Property       | Value                                                                                                                                                                     |
    | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | **Scope**      | `admin` (deny-by-origin)                                                                                                                                                  |
    | **Parameters** | `{ leaseId?: string, rootRunId?: string }` — exactly one. `leaseId` revokes a single lease; `rootRunId` revokes every lease of that spawn tree. Neither/both is rejected. |
    | **Returns**    | `{ revoked: number }` — the count of leases revoked (0 when the selector matched nothing)                                                                                 |

    Cooperative: the revoked lease's next `validate`/`renew` is denied, so the bearer cannot keep acting or re-lease — but an in-flight call already past validation runs to completion. For a hard stop use `run.kill`.

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "lease.revoke",
        "params": { "rootRunId": "root-agent-1-abc" },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": { "revoked": 3 },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `run.kill`

    | Property       | Value                                                                   |
    | -------------- | ----------------------------------------------------------------------- |
    | **Scope**      | `admin` (deny-by-origin)                                                |
    | **Parameters** | `{ rootRunId: string }` — the root run identifying the whole spawn tree |
    | **Returns**    | `{ killed: number }` — the count of runs killed across the tree         |

    Hard stop: aborts every running/queued SDK session of the tree (by `rootRunId`) AND revokes its leases (the cascade reaches grandchildren via `parentLeaseId`). Use this for a runaway `for(;;) spawn()` loop the cooperative `lease.revoke` cannot interrupt mid-flight.

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "run.kill",
        "params": { "rootRunId": "root-agent-1-abc" },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": { "killed": 4 },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `autonomy.evict`

    | Property       | Value                                                                                                                              |
    | -------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
    | **Scope**      | `admin` (deny-by-origin)                                                                                                           |
    | **Parameters** | `{ rootRunId: string }` — the root run to demote                                                                                   |
    | **Returns**    | `{ evicted: boolean }` — `true` once the run is demoted (whether this call newly demoted it or it was already evicted; idempotent) |

    Demote-but-continue: unlike `lease.revoke` (cooperative stop) and `run.kill` (hard stop), evict does **not** abort the run. It marks the `rootRunId` in a daemon-wide evicted-set; the bounded-autonomy chokepoint consults that set at the run's **next gate decision** and resolves its effective profile to `default` — so an over-eager [`unattended`](/agents/autonomy#the-unattended-profile) run reverts to the conservative posture **mid-flight** (not at the next mint or spawn). The run keeps going under `default` (which still escalates outward, never auto-sends). `default` is also the fail-closed target [`evictOnPolicyUnreachable`](/reference/config-yaml#autonomy-agents-autonomy) lands on when a mode cannot be resolved.

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "autonomy.evict",
        "params": { "rootRunId": "root-agent-1-abc" },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": { "evicted": true },
        "id": 1
      }
      ```
    </CodeGroup>

    <Note>
      **Durability / resume signals.** When
      [`autonomy.durability.enabled`](/reference/config-yaml#autonomy-agentsautonomy)
      is on, a `lease.revoke` / `run.kill` also **poisons the run's persisted record**
      (flips it to `revoked`), so a subsequent daemon restart can never resurrect the
      killed run's pre-revoke capabilities. Restart-time **resume** and **orphan**
      outcomes are observable out-of-band, not over these RPCs: a run that the daemon
      could not safely resume is orphaned and the operator is **notified out-of-band**
      (it does not silently vanish), and the durable run/ledger state is inspectable in
      [`memory.db`](/operations/data-directory#memory-db). See
      [Durability & Resume](/agents/autonomy#durability-resume).
    </Note>
  </Accordion>

  <Accordion title="telegram (1 method)">
    Telegram platform action dispatch. Sends a platform-specific action to the Telegram channel adapter.

    | Method            | Scope   | Description                         |
    | ----------------- | ------- | ----------------------------------- |
    | `telegram.action` | `admin` | Dispatch a Telegram platform action |

    ### `telegram.action`

    | Property       | Value                                                                                                                                                                                                                                                                                            |
    | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
    | **Scope**      | `admin`                                                                                                                                                                                                                                                                                          |
    | **Parameters** | `{ action: string, chat_id: string, endpoint?: ChannelEndpoint, ...platformParams }` -- the `action` string selects the operation; additional parameters vary per action. Gateway clients must supply the exact endpoint. See [Telegram channel docs](/channels/telegram) for available actions. |
    | **Returns**    | Platform-specific result object                                                                                                                                                                                                                                                                  |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "telegram.action",
        "params": {
          "action": "send_message",
          "chat_id": "123456",
          "endpoint": {
            "channelType": "telegram",
            "channelInstanceId": "telegram-main",
            "conversationId": "123456",
            "conversationKind": "shared"
          },
          "text": "Hello from Telegram action"
        },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "ok": true,
          "action": "send_message"
        },
        "id": 1
      }
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="whatsapp (1 method)">
    WhatsApp platform action dispatch. Sends a platform-specific action to the WhatsApp channel adapter.

    | Method            | Scope   | Description                         |
    | ----------------- | ------- | ----------------------------------- |
    | `whatsapp.action` | `admin` | Dispatch a WhatsApp platform action |

    ### `whatsapp.action`

    | Property       | Value                                                                                                                                                                                                                                                                                              |
    | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                                                                                                                                                                                                                                                            |
    | **Parameters** | `{ action: string, group_jid: string, endpoint?: ChannelEndpoint, ...platformParams }` -- the `action` string selects the operation; additional parameters vary per action. Gateway clients must supply the exact endpoint. See [WhatsApp channel docs](/channels/whatsapp) for available actions. |
    | **Returns**    | Platform-specific result object                                                                                                                                                                                                                                                                    |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "whatsapp.action",
        "params": {
          "action": "send_template",
          "group_jid": "group123",
          "endpoint": {
            "channelType": "whatsapp",
            "channelInstanceId": "whatsapp-main",
            "conversationId": "group123",
            "conversationKind": "shared"
          },
          "to": "1234567890",
          "template": "welcome_message"
        },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "ok": true,
          "action": "send_template"
        },
        "id": 1
      }
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="delivery (1 method)">
    Delivery queue status inspection. Provides a live count of messages at each stage of the outbound delivery pipeline.

    | Method                  | Scope | Description                                            |
    | ----------------------- | ----- | ------------------------------------------------------ |
    | `delivery.queue.status` | `rpc` | Get per-status counts from the outbound delivery queue |

    ### `delivery.queue.status`

    Check how many messages are currently sitting at each stage of the delivery queue. Useful for diagnosing backlogs or verifying that a channel is draining correctly. Optionally filter to a specific channel type.

    | Property       | Value                                                                                       |
    | -------------- | ------------------------------------------------------------------------------------------- |
    | **Scope**      | `rpc`                                                                                       |
    | **Parameters** | `{ channel_type?: string }`                                                                 |
    | **Returns**    | `{ pending: number, inFlight: number, failed: number, delivered: number, expired: number }` |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "delivery.queue.status",
        "params": { "channel_type": "telegram" },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "pending": 2,
          "inFlight": 1,
          "failed": 0,
          "delivered": 847,
          "expired": 3
        },
        "id": 1
      }
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="env (2 methods)">
    Runtime secret management. Write secrets through the configured writable secret
    store and list configured secret names. Values are never returned by either
    method.

    | Method     | Scope   | Description                                                   |
    | ---------- | ------- | ------------------------------------------------------------- |
    | `env.set`  | `admin` | Persist and live-apply a secret without restarting the daemon |
    | `env.list` | `admin` | List configured secret names (values never returned)          |

    ### `env.set`

    Write a secret through the active `SecretStorePort`. The default `encrypted`
    mode stores it in `~/.comis/secrets.db`; `file` mode stores it in the
    owner-readable `secrets.json` file. The read-only `env` storage mode rejects
    writes—there is no `.env` fallback. A successful write updates the daemon's
    in-memory secret view without restarting it.

    Rate-limited to 5 writes per minute. The value is never logged at any level.

    | Property       | Value                                                                           |
    | -------------- | ------------------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                                         |
    | **Parameters** | `{ key: string, value: string }`                                                |
    | **Returns**    | `{ set: true, key: string, storage: "encrypted" \| "file", restarting: false }` |

    `key` must start with an uppercase letter and contain only uppercase letters, digits, and underscores (e.g. `OPENAI_API_KEY`). Max length: 256 characters. `value` max length: 8192 characters. Passing the literal string `[REDACTED]` is rejected to prevent session-redaction poisoning.

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "env.set",
        "params": { "key": "OPENAI_API_KEY", "value": "test-key" },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "set": true,
          "key": "OPENAI_API_KEY",
          "storage": "encrypted",
          "restarting": false
        },
        "id": 1
      }
      ```
    </CodeGroup>

    The response is returned after persistence and the in-memory update succeed.
    Existing WebSocket connections remain open.

    ### `env.list`

    List the names of all configured secrets. Secret values are never included in the response. Use this before calling `env.set` to check whether a key is already configured.

    Rate-limited to 30 calls per minute.

    | Property       | Value                                                                                                                                                              |
    | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
    | **Scope**      | `admin`                                                                                                                                                            |
    | **Parameters** | `{ filter?: string, limit?: number }`                                                                                                                              |
    | **Returns**    | `{ secrets: Array<{ name, source: "secretstore" \| "envfile", provider?, description?, createdAt?, updatedAt?, expiresAt? }>, total: number, truncated: boolean }` |

    `filter` supports glob-style patterns (e.g. `OPENAI_*`). `limit` defaults to 100, max 500.

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "env.list",
        "params": { "filter": "OPENAI_*" },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "secrets": [
            { "name": "OPENAI_API_KEY", "source": "secretstore", "updatedAt": 1773313498000 }
          ],
          "total": 1,
          "truncated": false
        },
        "id": 1
      }
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="image (2 methods)">
    Image generation and image analysis. `image.generate` produces images from a text prompt; `image.analyze` runs vision analysis on an existing image.

    | Method           | Scope | Description                                          |
    | ---------------- | ----- | ---------------------------------------------------- |
    | `image.generate` | `rpc` | Generate an image from a text prompt                 |
    | `image.analyze`  | `rpc` | Analyze an image with the configured vision provider |

    ### `image.generate`

    Generate an image from a text prompt using the configured image generation provider (OpenAI DALL-E, fal.ai, etc.). If a `_callerChannelType` and `_callerChannelId` are available (set automatically when called from an agent tool), the image is delivered directly to the channel. Otherwise it is returned as base64.

    Rate-limited per-agent via the `imageGen.maxPerHour` config value.

    | Property       | Value                                                                                                                |
    | -------------- | -------------------------------------------------------------------------------------------------------------------- |
    | **Scope**      | `rpc`                                                                                                                |
    | **Parameters** | `{ prompt: string, size?: string }`                                                                                  |
    | **Returns**    | `{ success: true, delivered?: true, imageBase64?: string, mimeType: string }` or `{ success: false, error: string }` |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "image.generate",
        "params": {
          "prompt": "A serene mountain lake at dawn, photorealistic",
          "size": "1024x1024"
        },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "success": true,
          "imageBase64": "iVBORw0KGgo...",
          "mimeType": "image/png"
        },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `image.analyze`

    Analyze an image using the configured vision provider. Accepts an image from a file path in the agent's workspace, a URL, a base64 data URI, or a channel attachment URL.

    | Property       | Value                                                                                                               |
    | -------------- | ------------------------------------------------------------------------------------------------------------------- |
    | **Scope**      | `rpc`                                                                                                               |
    | **Parameters** | `{ source: string, source_type: "file" \| "url" \| "base64" \| "attachment", prompt?: string, mime_type?: string }` |
    | **Returns**    | `{ description: string, provider: string, model: string }`                                                          |

    `prompt` defaults to `"Describe this image in detail"` when omitted. Vision scope rules configured under `media.vision.scopeRules` may deny analysis for certain channel contexts.

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "image.analyze",
        "params": {
          "source": "https://example.com/chart.png",
          "source_type": "url",
          "prompt": "What does this chart show?"
        },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "description": "The chart shows monthly revenue growth from January to December...",
          "provider": "openai",
          "model": "gpt-4o"
        },
        "id": 1
      }
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="tts (2 methods)">
    Text-to-speech synthesis and auto-trigger checking. Requires a TTS provider configured under `media.tts`.

    | Method           | Scope | Description                                                   |
    | ---------------- | ----- | ------------------------------------------------------------- |
    | `tts.synthesize` | `rpc` | Synthesize speech from text and save to the agent's workspace |
    | `tts.auto_check` | `rpc` | Check whether TTS should auto-trigger for a given response    |

    ### `tts.synthesize`

    Convert text to speech using the configured TTS provider. The audio file is written to the agent's workspace under `media/tts/` and the file path is returned. Old files are cleaned up automatically after one hour.

    Supports inline TTS directives in the text (e.g. `[[tts:voice=nova]]`) which override the default voice. Output format is resolved automatically based on the calling channel (Opus for Telegram, MP3 otherwise).

    | Property       | Value                                                       |
    | -------------- | ----------------------------------------------------------- |
    | **Scope**      | `rpc`                                                       |
    | **Parameters** | `{ text: string, voice?: string, format?: string }`         |
    | **Returns**    | `{ filePath: string, mimeType: string, sizeBytes: number }` |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "tts.synthesize",
        "params": {
          "text": "Good morning! Here is your daily briefing.",
          "voice": "nova"
        },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "filePath": "/home/user/.comis/workspace-default/media/tts/tts-abc123.mp3",
          "mimeType": "audio/mp3",
          "sizeBytes": 48320
        },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `tts.auto_check`

    Check whether TTS synthesis should be triggered automatically for a given response, based on the configured `tts.autoMode` setting and the presence of inbound audio or media. Used internally by the agent pipeline; exposed here for testing and custom integrations.

    | Property       | Value                                                                             |
    | -------------- | --------------------------------------------------------------------------------- |
    | **Scope**      | `rpc`                                                                             |
    | **Parameters** | `{ response_text: string, has_inbound_audio?: boolean, has_media_url?: boolean }` |
    | **Returns**    | `{ shouldSynthesize: boolean, strippedText: string, mode: string }`               |

    `strippedText` has any `[[tts:...]]` directives removed. `mode` reflects the configured `autoMode` (`"always"`, `"never"`, `"voice_reply"`, etc.).
  </Accordion>

  <Accordion title="link (1 method)">
    Link content processing for enriching messages that contain URLs.

    | Method         | Scope | Description                                                  |
    | -------------- | ----- | ------------------------------------------------------------ |
    | `link.process` | `rpc` | Process message text through the link understanding pipeline |

    ### `link.process`

    Run message text through the link processing pipeline. URLs in the text are fetched, article content is extracted, and the enriched text is returned with inline summaries or expansions. Used by the agent pipeline automatically; exposed here for custom preprocessing workflows.

    | Property       | Value                                                                |
    | -------------- | -------------------------------------------------------------------- |
    | **Scope**      | `rpc`                                                                |
    | **Parameters** | `{ text: string }`                                                   |
    | **Returns**    | `{ enrichedText: string, linksProcessed: number, errors: string[] }` |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "link.process",
        "params": { "text": "Check this out: https://example.com/article" },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "enrichedText": "Check this out: https://example.com/article\n\n[Article: Example Title — A brief summary of the content...]",
          "linksProcessed": 1,
          "errors": []
        },
        "id": 1
      }
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="media (3 agent-facing methods)">
    On-demand media processing for agent tools: transcribe audio attachments, describe video attachments, and extract text from document attachments. These methods operate on channel attachment URLs resolved at call time, unlike the `media.test.*` admin methods which accept raw base64 input.

    | Method                   | Scope | Description                                    |
    | ------------------------ | ----- | ---------------------------------------------- |
    | `media.transcribe`       | `rpc` | Transcribe an audio attachment by URL          |
    | `media.describe_video`   | `rpc` | Describe a video attachment by URL             |
    | `media.extract_document` | `rpc` | Extract text from a document attachment by URL |

    ### `media.transcribe`

    Transcribe an audio attachment using the configured STT provider. The attachment URL is resolved by the daemon's attachment resolver (fetches from channel storage), then passed to the transcriber. MIME type is auto-detected from magic bytes.

    | Property       | Value                                                      |
    | -------------- | ---------------------------------------------------------- |
    | **Scope**      | `rpc`                                                      |
    | **Parameters** | `{ attachment_url: string, language?: string }`            |
    | **Returns**    | `{ text: string, language?: string, durationMs?: number }` |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "media.transcribe",
        "params": { "attachment_url": "https://cdn.example.com/voice/msg_001.ogg" },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "text": "Please schedule the meeting for Thursday at three pm.",
          "language": "en",
          "durationMs": 3800
        },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `media.describe_video`

    Analyze a video attachment using a vision provider that supports video (e.g. Gemini). Returns a natural-language description.

    | Property       | Value                                                      |
    | -------------- | ---------------------------------------------------------- |
    | **Scope**      | `rpc`                                                      |
    | **Parameters** | `{ attachment_url: string, prompt?: string }`              |
    | **Returns**    | `{ description: string, provider: string, model: string }` |

    `prompt` defaults to `"Describe this video concisely."` when omitted. Returns an error if no video-capable vision provider is configured.

    ### `media.extract_document`

    Extract the text content from a document attachment (PDF, DOCX, etc.) using the configured document extraction service.

    | Property       | Value                                                                                                                   |
    | -------------- | ----------------------------------------------------------------------------------------------------------------------- |
    | **Scope**      | `rpc`                                                                                                                   |
    | **Parameters** | `{ attachment_url: string }`                                                                                            |
    | **Returns**    | `{ text: string, fileName?: string, mimeType: string, extractedChars: number, truncated: boolean, durationMs: number }` |

    `truncated` is `true` when the document exceeded the extractor's character limit and the text was cut off.

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "media.extract_document",
        "params": { "attachment_url": "https://cdn.example.com/docs/report.pdf" },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "text": "Q1 Financial Report\n\nExecutive Summary...",
          "fileName": "report.pdf",
          "mimeType": "application/pdf",
          "extractedChars": 42800,
          "truncated": false,
          "durationMs": 1240
        },
        "id": 1
      }
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="scheduler (1 method)">
    Typed admission into the heartbeat coordinator.

    | Method           | Scope | Description                        |
    | ---------------- | ----- | ---------------------------------- |
    | `scheduler.wake` | `rpc` | Submit an agent or monitoring wake |

    ### `scheduler.wake`

    Submits a routine wake to one closed target. `agent` targets the calling agent's lane; `monitoring` targets the monitoring lane. This method admits or coalesces a heartbeat occurrence and does not inspect or run cron jobs.

    The closed vocabulary is `target`, `agent`, `monitoring`, `accepted`, `coalesced`, `disposition`, `new_occurrence`, `occurrence_upgraded`, `correlationId`, `lane`, and `retainedReason`.

    <Note>
      **Not the per-job wake-gate.** `scheduler.wake` nudges the whole heartbeat coalescer to run now — it is a scheduler-wide trigger. A [wake-gate](/agent-tools/scheduling#wake-gate-run-the-model-only-when-it-matters) is the opposite: a per-job pre-run script that decides whether a *single* cron fire invokes the model. They share the word "wake" and nothing else.
    </Note>

    | Property       | Value                                                                                                                                                                                                                           |
    | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | **Scope**      | `rpc`                                                                                                                                                                                                                           |
    | **Parameters** | `{ target: "agent" \| "monitoring" }`                                                                                                                                                                                           |
    | **Returns**    | `{ status: "accepted", disposition: "new_occurrence" \| "occurrence_upgraded", correlationId, lane: "normal" \| "task", retainedReason }` or `{ status: "coalesced", correlationId, lane: "normal" \| "task", retainedReason }` |

    `retainedReason` is `interval`, `manual`, `hook`, `wake`, `exec-event`, `cron`, or `task`. `accepted` distinguishes a newly admitted occurrence from an upgrade of one already admitted; `coalesced` reports the retained occurrence without creating another.

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "scheduler.wake",
        "params": { "target": "monitoring" },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "status": "accepted",
          "disposition": "new_occurrence",
          "correlationId": "wake-monitoring-a",
          "lane": "normal",
          "retainedReason": "wake"
        },
        "id": 1
      }
      ```
    </CodeGroup>
  </Accordion>
</AccordionGroup>

## Error Handling

When a method call fails, the server returns a JSON-RPC error response.

```json theme={}
{
  "jsonrpc": "2.0",
  "error": {
    "code": -32603,
    "message": "Insufficient scope: requires 'admin'"
  },
  "id": 1
}
```

Common error conditions:

| Error              | Code     | Cause                                                 |
| ------------------ | -------- | ----------------------------------------------------- |
| Insufficient scope | `-32603` | Token does not have the required scope for the method |
| Method not found   | `-32601` | Unknown method name                                   |
| Invalid params     | `-32602` | Missing required parameters                           |
| Internal error     | `-32603` | Handler threw an exception                            |

See the [WebSocket Protocol](/reference/websocket) error codes table for transport-level errors (parse error, rate limiting, message size).

## The agent cap-socket surface: `tool.invoke`

Everything above is reached over the operator-facing WebSocket/HTTP gateway. There is a **second, internal** RPC surface an autonomous agent reaches from inside a jail: `tool.invoke`, dispatched over the lease-authenticated loopback **capability socket** (`COMIS_ORCH_SOCKET`, a `0600` owner-only Unix socket) — never the public gateway. It is the one-route generalization that lets a jailed [`orchestrate`](/agent-tools/orchestrate) script call a capability-scoped tool.

### `tool.invoke`

| Property       | Value                                                                                                                                                                                                         |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Transport**  | The cap socket only (`COMIS_ORCH_SOCKET`); the wire is one `{ bearer, method: "tool.invoke", params: { tool, args } }` newline-JSON line per connection. Not reachable over the public WS/HTTP gateway.       |
| **Auth**       | A capability **lease** bearer (`COMIS_CAP_LEASE`), audience-bound to the inner `tool` — a captured lease cannot dispatch a tool whose capability it lacks.                                                    |
| **Origin**     | **Agent-origin.** Deny-by-origin keeps the entire control plane unreachable through this route.                                                                                                               |
| **Parameters** | `{ tool: string, args: object }` — `tool` is a name on the curated [tool→capability map](/security/capability-model#the-curated-tool-capability-surface-tool-invoke); `args` are the inner tool's parameters. |
| **Returns**    | The inner tool's result, or a `ResultRef` handle when the return is over the per-tool size threshold (see [orchestrate](/agent-tools/orchestrate#resultref-high-volume-returns)).                             |

The dispatch gates in a fixed order — **cap-map allow-list** (an unmapped tool is undispatchable: a `CapabilityDeniedError`, default-deny) → **denylist** (no cap-mapped tool is denylisted; `mcp_manage`/`mcp_login`/`gateway`/`*_manage` stay unreachable) → **deny-by-origin** (automatic on the RPC route, since the lease's `_agentId` is injected) → **`requireCapability`** → **route** (an RPC-backed read forwards to its registered handler with the lease's identity strip-then-injected; an in-process builtin — `read`/`grep`/`find`/`ls`/`jq` and the daemon-side, DNS-pinned `web_search`/`web_fetch` — runs on the daemon under the agent's jailed workspace).

```json Request (over COMIS_ORCH_SOCKET, not the gateway) theme={}
{ "bearer": "<COMIS_CAP_LEASE>", "method": "tool.invoke", "params": { "tool": "web_fetch", "args": { "url": "https://example.com" } } }
```

## Related

<CardGroup cols={2}>
  <Card title="WebSocket Protocol" icon="plug" href="/reference/websocket">
    Transport protocol, error codes, and connection management
  </Card>

  <Card title="HTTP Gateway" icon="globe" href="/reference/http-gateway">
    HTTP endpoint reference and authentication
  </Card>

  <Card title="CLI Reference" icon="terminal" href="/reference/cli">
    CLI commands that use these JSON-RPC methods
  </Card>

  <Card title="Config YAML" icon="file-code" href="/reference/config-yaml">
    Gateway and scope configuration
  </Card>
</CardGroup>
