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

# JSON-RPC Methods Reference

> Complete reference for all 170 JSON-RPC methods across 41 namespaces

**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 all JSON-RPC methods available over the Comis gateway WebSocket and HTTP transports. The gateway exposes 170 methods across 41 namespaces covering agent execution, memory, configuration, sessions, scheduling, browser automation, context engine, workspace management, media processing, image generation, text-to-speech, link enrichment, cross-channel messaging, sub-agent lifecycle, autonomy live-control (lease-revoke / run-kill / autonomy-evict), platform actions, notifications, environment 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`.

Method count is sourced from `packages/core/src/api-contracts/` contract files (167 unique method strings across 39 namespaces). The 29 handler-factory modules in `packages/daemon/src/api/rpc-dispatch.ts` implement these contracts. Some older handler files may use additional direct-keyed methods not yet covered by the contracts layer; 167 represents the formally declared public API surface.

## 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","id":3}
    ```
  </Step>

  <Step title="Clean up when done">
    Use `session.delete` (admin) to remove a session. The session entry plus its memory entries are dropped.

    ```json theme={}
    {
      "jsonrpc": "2.0",
      "method": "session.delete",
      "params": { "key": "default:alice:cli" },
      "id": 4
    }
    ```
  </Step>
</Steps>

That same pattern works whether you call over WebSocket or `POST /rpc`. For streaming token deltas use the SSE endpoint `GET /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 `GET /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.12.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** | `{ channelId: string }` (required)     |
    | **Returns**    | `{ channel: ChannelActivity \| null }` |

    ### `obs.delivery.recent`

    | Property       | Value                                                      |
    | -------------- | ---------------------------------------------------------- |
    | **Scope**      | `admin`                                                    |
    | **Parameters** | `{ sinceMs?: number, limit?: number, channelId?: string }` |
    | **Returns**    | `{ deliveries: DeliveryTrace[] }`                          |

    ### `obs.delivery.stats`

    Get aggregate delivery statistics: total messages attempted, successes, failures, and average delivery latency. Combines in-memory counters with the SQLite observability store for a complete picture across daemon restarts.

    | Property       | Value                                                                          |
    | -------------- | ------------------------------------------------------------------------------ |
    | **Scope**      | `admin`                                                                        |
    | **Parameters** | None                                                                           |
    | **Returns**    | `{ total: number, successes: number, failures: number, avgLatencyMs: number }` |

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

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "total": 1204,
          "successes": 1198,
          "failures": 6,
          "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 fleet-wide by [`obs.fleet.health`](#obs-fleet-health)'s `degradedByCause`.

    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 fleet`](#obs-fleet-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 fleet` 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 fleet` 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 fleet` 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.fleet.health`](#obs-fleet-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.fleet.health`](#obs-fleet-health) → `obs.explain` drill-down ref: pass the fleet 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.fleet.health`](#obs-fleet-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.fleet.health`](#obs-fleet-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 fleet` 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:678314278:678314278:peer:678314278", "depth": "summary" },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "schemaVersion": 1,
          "sessionKey": "default:678314278:678314278:peer:678314278",
          "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.fleet.health`

    Assemble a bounded, redaction-safe **cross-session** fleet-health triage (a `FleetHealthReport`) 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 fleet-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 (`fleet_acute_degradation`, naming the dominant `degradedByCause` entry) out-ranks the standing config-posture (`fleet_config_posture`) and recurring-health-signal (`fleet_recurring_health_signal`) verdicts; only a fleet-wide degradation spike (`fleet_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 -- `fleet_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).

    The same `config_posture` row also carries `importedSkillCount` (total [imported skills](/skills/importing)) and `importedNonAllowlistedRegistryCount` (how many carry a recorded registry no longer in its applicable `skills.import.registries` allowlist, resolved **per record** -- a shared-scope skill against the default agent, a local one against its owner) in its details JSON -- **counts only**, never a registry origin or agent id. When the **latest** posture row's drift count is > 0, `findings[]` gains the dedicated `config_posture:imported_skills` code with that count (the same latest-row standing-state semantics; a malformed or missing field folds to 0 defensively). The hint names the `skills.import.registries` knob and `comis explain`.

    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 fleet 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 fleet 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 fleet` 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 fleet 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 fleet 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_runs` 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** fleet 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 fleet --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.fleet.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 fleet 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 fleet.

    | Property       | Value                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
    | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
    | **Parameters** | `{ sinceHours?: number }` -- the aggregation window in hours (positive). Optional; defaults to `24` in the handler.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
    | **Returns**    | A `FleetHealthReport`: `{ 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.fleet.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": "fleet_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": "fleet_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 fleet` CLI command (see the [CLI reference](/reference/cli)) and the `obs_query` agent `fleet_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 (11 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`               | `admin` | List all active sessions                                                                               |
    | `session.delete`             | `admin` | Delete a session                                                                                       |
    | `session.reset`              | `admin` | Reset all sessions                                                                                     |
    | `session.export`             | `admin` | Export session data                                                                                    |
    | `session.compact`            | `admin` | Compact session history                                                                                |
    | `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.

    | Property       | Value                                                                        |
    | -------------- | ---------------------------------------------------------------------------- |
    | **Scope**      | `rpc`                                                                        |
    | **Parameters** | `{ channelId?: string }`                                                     |
    | **Returns**    | `{ messages: Array<{ role: string, content: string, timestamp?: number }> }` |

    ### `session.list`

    List all sessions the daemon knows about, including sessions stored in SQLite, JSONL transcript files, and workspace session files. Optionally filter by recency or session kind.

    | Property       | Value                                                                             |
    | -------------- | --------------------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                                           |
    | **Parameters** | `{ kind?: "all" \| "sub-agent" \| "group" \| "dm", since_minutes?: number }`      |
    | **Returns**    | `{ sessions: Array<{ sessionKey, agentId, messageCount, updatedAt, metadata }> }` |

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

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "sessions": [
            { "sessionKey": "telegram:user123:chat456", "agentId": "default", "messageCount": 42, "updatedAt": 1773313498000 }
          ]
        },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `session.send`

    | Property       | Value                                                |
    | -------------- | ---------------------------------------------------- |
    | **Scope**      | `rpc`                                                |
    | **Parameters** | `{ key: string, message: string, agentId?: string }` |
    | **Returns**    | Agent response                                       |

    ### `session.spawn`

    | Property       | Value                                                   |
    | -------------- | ------------------------------------------------------- |
    | **Scope**      | `rpc`                                                   |
    | **Parameters** | `{ parentKey: string, task: string, agentId?: string }` |
    | **Returns**    | Spawned session info                                    |

    ### `session.delete`

    | Property       | Value                                                       |
    | -------------- | ----------------------------------------------------------- |
    | **Scope**      | `admin`                                                     |
    | **Parameters** | `{ session_key: string }`                                   |
    | **Returns**    | `{ sessionKey: string, deleted: true, transcript: object }` |

    The parameter name is `session_key` (snake\_case) — the handler rejects requests that pass `key` instead. The deleted session's transcript is returned in the response so you have a final copy.

    ### `session.reset`

    Clear all messages in a session while preserving the session's metadata (identity, channel association). Useful for giving an agent a clean slate without fully deleting the session record. Also clears any cached approval decisions for the session.

    | Property       | Value                                                               |
    | -------------- | ------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                             |
    | **Parameters** | `{ session_key: string }`                                           |
    | **Returns**    | `{ sessionKey: 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** | `{ session_key: string }`                                                |
    | **Returns**    | `{ sessionKey, messages, metadata, messageCount, createdAt, updatedAt }` |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "session.export",
        "params": { "session_key": "telegram:user123:chat456" },
        "id": 1
      }
      ```

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

    ### `session.run_status`

    Get the live status of a specific sub-agent run by its run ID. Returns timing, the task description, the current status, and — once complete — the response text and token usage.

    | Property       | Value                                                                                                                     |
    | -------------- | ------------------------------------------------------------------------------------------------------------------------- |
    | **Scope**      | `rpc`                                                                                                                     |
    | **Parameters** | `{ run_id: string }`                                                                                                      |
    | **Returns**    | `{ runId, status, agentId, task, sessionKey, startedAt, completedAt?, runtimeMs, response?, tokensUsed?, cost?, error? }` |

    <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",
          "task": "Summarize recent papers on quantum error correction",
          "startedAt": 1773313450000,
          "completedAt": 1773313498000,
          "runtimeMs": 48000,
          "response": "Here is a summary of recent papers..."
        },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `session.search`

    | Property       | Value                                                                                                                    |
    | -------------- | ------------------------------------------------------------------------------------------------------------------------ |
    | **Scope**      | `rpc`                                                                                                                    |
    | **Parameters** | `{ query: string, limit?: number }`                                                                                      |
    | **Returns**    | `Array<{ sessionKey: string, agentId: string, channelType: string, snippet: string, score: number, timestamp: number }>` |

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

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": [
          {
            "sessionKey": "telegram:user123:chat456",
            "agentId": "default",
            "channelType": "telegram",
            "snippet": "...deploy using pm2 for production...",
            "score": 0.87,
            "timestamp": 1773313498000
          }
        ],
        "id": 1
      }
      ```
    </CodeGroup>

    ### `session.compact`

    | Property       | Value             |
    | -------------- | ----------------- |
    | **Scope**      | `admin`           |
    | **Parameters** | `{ key: string }` |
    | **Returns**    | Compaction result |

    ### `session.reset_conversation`

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

    This is the correct explicit forget command. Unlike `session.delete` (which only removes the session record), this operation ensures the model starts with a clean slate in **both engine modes**: the LCD store is empty (dag mode), the sessionStore messages are empty, and the runtime session transcript is destroyed. 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** | `{ session_key: string, memory?: boolean, purge_derived?: boolean }` — `memory` defaults to `false`; when `true`, the conversation's RAG memories are also deleted by `source_session_key` (both paired-conversation **and** LCD-distilled episodic memories) for the 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**    | `{ sessionKey: string, lcdRowsDeleted: number, sessionMessagesCleared: number, memoriesDeleted?: number, 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": {
          "session_key": "default:user123:telegram",
          "memory": true
        },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "sessionKey": "default:user123:telegram",
          "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 (7 methods)">
    Scheduled job management for cron expressions, intervals, and one-time tasks.

    | Method        | Scope | Description                 |
    | ------------- | ----- | --------------------------- |
    | `cron.list`   | `rpc` | List all cron jobs          |
    | `cron.add`    | `rpc` | Add a new cron job          |
    | `cron.update` | `rpc` | Update an existing cron job |
    | `cron.remove` | `rpc` | Remove a cron job           |
    | `cron.status` | `rpc` | Get cron job status         |
    | `cron.runs`   | `rpc` | Get cron job run history    |
    | `cron.run`    | `rpc` | Trigger a manual cron run   |

    ### `cron.add`

    | Property       | Value                                                                                                                                                                |
    | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | **Scope**      | `rpc`                                                                                                                                                                |
    | **Parameters** | `{ name: string, message: string, agentId?: string, schedule: { kind: "cron" \| "interval" \| "once", expr?: string, tz?: string, everyMs?: number, at?: string } }` |
    | **Returns**    | Created job info                                                                                                                                                     |

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "cron.add",
        "params": {
          "name": "daily-summary",
          "message": "Summarize today's activity",
          "schedule": {
            "kind": "cron",
            "expr": "0 18 * * *",
            "tz": "America/New_York"
          }
        },
        "id": 1
      }
      ```

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

    ### `cron.list`

    List all scheduled jobs. Returns each job's name, schedule expression, next run time, and whether it is currently enabled.

    | Property       | Value                                                                                                                                                                                                                                  |
    | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | **Scope**      | `rpc`                                                                                                                                                                                                                                  |
    | **Parameters** | None                                                                                                                                                                                                                                   |
    | **Returns**    | `{ jobs: Array<{ id, name, scheduleKind, expr?, tz?, everyMs?, nextRunAtMs, enabled, lastRunAtMs?, lastError?, wakeGate? }> }` (a gated job carries its `wakeGate` so the web scheduler editor can display and edit the existing gate) |

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

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "jobs": [
            {
              "id": "daily-summary",
              "name": "daily-summary",
              "scheduleKind": "cron",
              "expr": "0 18 * * *",
              "tz": "America/New_York",
              "nextRunAtMs": 1773334800000,
              "enabled": true
            }
          ]
        },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `cron.update`

    Update an existing cron job's schedule or message. Only the fields you provide are changed.

    | Property       | Value                                                                                                                                                                  |
    | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | **Scope**      | `rpc`                                                                                                                                                                  |
    | **Parameters** | `{ name: string, message?: string, agentId?: string, schedule?: { kind: "cron" \| "interval" \| "once", expr?: string, tz?: string, everyMs?: number, at?: string } }` |
    | **Returns**    | Updated job info                                                                                                                                                       |

    ### `cron.status`

    Get the current status of a single cron job by name.

    | Property       | Value                                                                        |
    | -------------- | ---------------------------------------------------------------------------- |
    | **Scope**      | `rpc`                                                                        |
    | **Parameters** | `{ name: string }`                                                           |
    | **Returns**    | `{ id, name, scheduleKind, nextRunAtMs, enabled, lastRunAtMs?, lastError? }` |

    ### `cron.runs`

    Get the run history for a cron job — the timestamp, duration, and outcome of each fire. This is the per-fire lens for a [wake-gate](/agent-tools/scheduling#wake-gate-run-the-model-only-when-it-matters): a fire the gate **skipped** is recorded here as a `skipped` outcome (no model turn ran), so a gated job's skips are visible without a session to `obs.explain`.

    | Property       | Value                                                                                                                                                                                                        |
    | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
    | **Scope**      | `rpc`                                                                                                                                                                                                        |
    | **Parameters** | `{ jobName: string, limit?: number, agentId?: string }`                                                                                                                                                      |
    | **Returns**    | `{ runs: Array<{ ts: number, jobId: string, status: "ok" \| "error" \| "skipped", durationMs: number, summary?: string, error?: string, toolCalls?: number, estTurnsSaved?: number, rootRunId?: string }> }` |

    Each entry's `status` is `ok` (the payload ran), `error` (it failed — `error` carries the message), or `skipped` (a wake-gate decided not to invoke the model — `summary` reads `wake-gate: skipped`). On a skip, `toolCalls` is the gate's own cap-call count, `estTurnsSaved` is the model turn it avoided (`1` per skip), and `rootRunId` is the gate fire's `root-wakegate-…` root: when the gate made cap-calls before skipping (a skip-after-fetch, `toolCalls > 0`), reconstruct them from the durable audit trail with [`security audit-log`](/reference/cli#comis-security-audit-log) filtered on that root. The cross-job skip-rate rollup is on [`obs.fleet.health`](#obs-fleet-health)'s `cronWakeGate` block.

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

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "runs": [
            { "ts": 1773248400000, "jobId": "job_a1b2c3", "status": "ok", "durationMs": 3200 },
            { "ts": 1773162000000, "jobId": "job_a1b2c3", "status": "skipped", "durationMs": 210, "summary": "wake-gate: skipped", "toolCalls": 1, "estTurnsSaved": 1 }
          ]
        },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `cron.remove`

    | Property       | Value                |
    | -------------- | -------------------- |
    | **Scope**      | `rpc`                |
    | **Parameters** | `{ name: string }`   |
    | **Returns**    | Removal confirmation |

    ### `cron.run`

    Trigger an immediate manual run of a scheduled job. By default this forces the job to execute regardless of its next scheduled time (`mode: "force"`). Pass `mode: "due"` to run all currently overdue jobs instead.

    | Property       | Value                                                                                   |
    | -------------- | --------------------------------------------------------------------------------------- |
    | **Scope**      | `rpc`                                                                                   |
    | **Parameters** | `{ name?: string, mode?: "force" \| "due" }` (`name` required when `mode` is `"force"`) |
    | **Returns**    | `{ triggered: true, mode: string, jobName?: string }`                                   |
  </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 inspection and connectivity testing. The catalog is sourced live from the [`@earendil-works/pi-ai`](https://www.npmjs.com/package/@earendil-works/pi-ai) SDK — `getProviders()` for the provider list, `getModels(provider)` for per-provider model details. No hardcoded provider/model tables on the comis side.

    | 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` | Test connectivity to a specific model                                        |

    ### `models.list_providers`

    List all providers in the pi-ai SDK catalog. Returns provider ids and a count. Use this to populate provider pickers (the web wizard's Provider step and CLI wizard's `03-provider` step both consume this).

    | 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", "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", "openai", "openai-codex", "opencode", "opencode-go",
            "openrouter", "vercel-ai-gateway", "xai", "zai"
          ],
          "count": 27
        },
        "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`

    Test connectivity to a specific model by sending a minimal prompt and measuring the round-trip latency. Useful for verifying that your API keys and network path are working before assigning a model to an agent.

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

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "models.test",
        "params": { "model": "claude-sonnet-4-5-20250929", "provider": "anthropic" },
        "id": 1
      }
      ```

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "ok": true,
          "latencyMs": 312,
          "provider": "anthropic"
        },
        "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 a GitHub directory, an archive, or an allowlisted registry), update, and delete skills.

    | Method          | Scope   | Description                                                                                                   |
    | --------------- | ------- | ------------------------------------------------------------------------------------------------------------- |
    | `skills.list`   | `rpc`   | List prompt skills for an agent (each carries its `source` and, for imports, a provenance summary)            |
    | `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 directory URL, a packaged archive, or an allowlisted registry (well-known index) |
    | `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[] }` |

    Each `SkillDescription` carries an optional `source` -- the trust tier the registry assigns: `bundled`, `workspace`, `local`, `learned`, or `imported`. An imported skill additionally carries a content-free `provenanceSummary`:

    ```json theme={}
    {
      "name": "my-skill",
      "description": "...",
      "location": "/home/user/.comis/workspace/skills/my-skill",
      "source": "imported",
      "provenanceSummary": {
        "source": "archive",
        "hashPrefix": "3f9a1c…",
        "importedAt": "2026-07-06T12:00:00Z"
      }
    }
    ```

    The `provenanceSummary.source` is the acquisition **channel** (`github` / `archive` / `wellknown` / `clawhub` / `upload`) -- distinct from the trust-tier `source` (`imported`). A `wellknown` or `clawhub` import additionally carries a `registry` field (the origin, or the literal `clawhub` token, the skill was resolved from), and a `clawhub` import carries an `officialPublisher` boolean (whether ClawHub reported the publisher as official). These are populated only for imported skills; bundled, workspace, local, and learned skills omit them. The summary is content-free: an acquisition channel, an optional registry origin, an optional official-publisher flag, a hash prefix, and a timestamp -- never a skill body.

    ### `skills.upload`

    | Property       | Value                                                                                                              |
    | -------------- | ------------------------------------------------------------------------------------------------------------------ |
    | **Scope**      | `admin`                                                                                                            |
    | **Parameters** | `{ name: string, agentId?: string, scope?: "local" \| "shared", 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, source?: "github" \| "archive" \| "wellknown" \| "clawhub", registry?: string, archiveUrl?: string, archiveBytes?: string, name?: string, scope?: "local" \| "shared", agentId?: string, confirm?: boolean }` |
    | **Returns**    | `{ ok: true, path: string, name: string, fileCount: number, source: "imported", resolvedAgentId: string, warnings?: string[] }`                                                                                                |

    Every source funnels through one staged pipeline: the content scan and the MCP Phase-A check run **before** anything is written, so a rejected import leaves zero live files. A successful import is stamped the `imported` trust tier and pinned in the provenance store; the response reports `source: "imported"` and the `resolvedAgentId` the import acted on.

    **Source modes** -- `source` selects the acquisition channel (it defaults to `github` when a `url` is present):

    * `github` -- `url` is a GitHub directory URL in the format `https://github.com/{owner}/{repo}/tree/{branch}/{path}`.
    * `archive` -- supply either `archiveUrl` (a `.skill` / `.zip` / `.tar` / `.tar.gz` URL, fetched through the SSRF guard) or `archiveBytes` (the archive as base64, e.g. an operator upload). The archive is unpacked in-house against the configured size caps.
    * `wellknown` -- resolve a skill **by name** from an allowlisted registry's well-known index. `registry` is the registry origin (`https://<host>[:port]`) and `name` is the index-lookup key (which advertised skill to fetch -- it does **not** override the installed manifest name). The registry must be listed in [`skills.import.registries`](/reference/config-yaml#skills-agents-skills); a non-allowlisted registry refuses flatly and is **not** `confirm`-overridable. The `/.well-known/skills/index.json` and each advertised file are fetched SSRF-pinned and byte-capped, and one unsafe advertised path rejects the whole bundle. The pinned provenance additionally records the registry origin.
    * `clawhub` -- resolve a skill from **ClawHub** by its `@owner/slug` identifier. `name` is the `@owner/slug`; the registry is **inferred** as the literal `clawhub` token (you do **not** pass `registry`), which must be listed in [`skills.import.registries`](/reference/config-yaml#skills-agents-skills) or the import refuses flatly (not `confirm`-overridable). Resolution is an install-resolver flow: the install decision and the scan **verdict** are fetched and evaluated **before** the release downloads, so a `malicious` / `blockedFromDownload` / `quarantined` / `revoked` verdict refuses before any artifact byte is fetched -- a hard stop `confirm` cannot override. A server-sent release hash (`X-ClawHub-Artifact-Sha256`) is verified when present; the pinned self-computed content hash is the always-present floor. The pinned provenance records the `clawhub` registry and the resolved `officialPublisher` flag. See [ClawHub import](/skills/importing#clawhub-import).

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

    `confirm` overrides **only** a content divergence on a re-import of the same provenance-matched source (it swaps in the new content and re-pins the hash). It does **not** override a name collision on an unprovenanced or foreign-source skill -- that refuses regardless, and you must delete the existing skill first. There is intentionally **no** `force` parameter.

    For a ClawHub import, `confirm` additionally acknowledges a **non-official publisher** (`officialPublisher: false` while `requireOfficialPublisher` is `true`). These are the two **warnable** classes -- a non-official publisher and a re-import pin divergence -- and a single `confirm` covers both: when one import trips both at once, one `confirm` acknowledges each and the response's `warnings[]` enumerates the acknowledged classes. A blocking scan **verdict** is never a warnable class -- `confirm` cannot override it, and the release is refused before it downloads.

    <Info>
      An imported skill is prompt-only and installs at a cautious trust tier: bundled MCP servers persist disabled (per-server opt-in), dynamic context is forced off, and only UTF-8 text files are kept. See [Importing Skills](/skills/importing) for the trust model and the honest limits of the import scan.
    </Info>

    ### `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 security-scanned before being written to disk. 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" }` |
    | **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" }` |
    | **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, ...platformParams }` -- the `action` string selects the operation; additional parameters vary per action. 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",
          "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. All methods require `admin` scope and a `channel_type` + `channel_id` to identify the target channel.

    <Note>
      **Exactly-once across a daemon restart (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`) is durably ledgered and delivered
      **exactly once across a daemon restart**: a replayed step whose send already
      committed is a **no-op** (it returns the prior result, it does not re-send), and
      a step that crashed *between* the send attempt and the platform acknowledgement
      is **reconciled** on recovery (the channel is asked "did this send?") rather than
      blind-replayed. See [Durability & Resume](/agents/autonomy#durability-resume) for
      the guarantee and its honest per-channel coverage. Operator-issued sends (no
      `rootRunId`) are unchanged at-least-once.
    </Note>

    | Method           | Scope   | Description                          |
    | ---------------- | ------- | ------------------------------------ |
    | `message.send`   | `admin` | Send a message to a channel          |
    | `message.reply`  | `admin` | 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`  | `admin` | Add a reaction to a message          |
    | `message.attach` | `admin` | Send a file attachment to a channel  |

    ### `message.send`

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

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "message.send",
        "params": {
          "channel_type": "telegram",
          "channel_id": "chat123",
          "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**      | `admin`                                                                          |
    | **Parameters** | `{ channel_type: string, channel_id: string, text: string, message_id: string }` |
    | **Returns**    | `{ messageId: string, channelId: string }`                                       |

    ### `message.edit`

    | Property       | Value                                                                            |
    | -------------- | -------------------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                                          |
    | **Parameters** | `{ channel_type: string, channel_id: string, message_id: string, text: string }` |
    | **Returns**    | Edit confirmation                                                                |

    ### `message.delete`

    | Property       | Value                                                              |
    | -------------- | ------------------------------------------------------------------ |
    | **Scope**      | `admin`                                                            |
    | **Parameters** | `{ channel_type: string, channel_id: string, message_id: string }` |
    | **Returns**    | Delete confirmation                                                |

    ### `message.fetch`

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

    <CodeGroup>
      ```json Request theme={}
      {
        "jsonrpc": "2.0",
        "method": "message.fetch",
        "params": {
          "channel_type": "discord",
          "channel_id": "channel789",
          "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**      | `admin`                                                                           |
    | **Parameters** | `{ channel_type: string, channel_id: string, message_id: string, emoji: string }` |
    | **Returns**    | React confirmation                                                                |

    ### `message.attach`

    | Property       | Value                                                                               |
    | -------------- | ----------------------------------------------------------------------------------- |
    | **Scope**      | `admin`                                                                             |
    | **Parameters** | `{ channel_type: string, channel_id: string, file_path: string, caption?: string }` |
    | **Returns**    | `{ messageId: string, 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 }` |
    | **Returns**    | `{ success: boolean, entryId?: string }`                                             |

    <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, ...platformParams }` -- the `action` string selects the operation; additional parameters vary per action. 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": "C0123456",
          "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 (3 methods)">
    Sub-agent lifecycle management: list running sub-agents, kill stuck executions, and steer active sub-agents with new instructions.

    | Method           | Scope   | Description                                                                                                     |
    | ---------------- | ------- | --------------------------------------------------------------------------------------------------------------- |
    | `subagent.list`  | `admin` | List recent sub-agent runs                                                                                      |
    | `subagent.kill`  | `admin` | Kill a running sub-agent                                                                                        |
    | `subagent.steer` | `admin` | Steer a running sub-agent -- inject a message into the live child (flag on) or kill+respawn (flag off, default) |

    ### `subagent.list`

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

    <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", "task": "Research quantum computing", "status": "running", "durationMs": 45000 }
          ],
          "total": 1
        },
        "id": 1
      }
      ```
    </CodeGroup>

    ### `subagent.kill`

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

    <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**      | `admin`                                                                                        |
    | **Parameters** | `{ target: string, message: string }` -- `target` is the runId; `message` is the steering text |
    | **Returns**    | A discriminated union on `status` (see below)                                                  |

    Behavior is gated on [`security.agentToAgent.steerInject`](/reference/config-yaml#security) (default `false`):

    * **Flag off (default)** -- kills the running child and respawns a fresh run with `message` as the new task. Returns `{ status: "steered", oldRunId, newRunId }`. The child's transcript and progress are discarded.
    * **Flag on** -- injects `message` into the *running* child's live session at its next step boundary, preserving the transcript and prior work (no kill, no respawn -- the same runId continues). Returns `{ status: "steered_inject", runId }`. The message lands as a user turn after the current step commits (it does not interrupt a tool call mid-execution).

    Rate-limited to one steer per `target` every 2 seconds (shared across both modes). A steered message is a message, not a capability grant: the child's tool set is fixed at spawn, so a steered request for a tool on the sub-agent tool denylist is still refused (the [defense-in-depth](/security/defense-in-depth) governance applies regardless of message source).
  </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, ...platformParams }` -- the `action` string selects the operation; additional parameters vary per action. 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",
          "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, ...platformParams }` -- the `action` string selects the operation; additional parameters vary per action. 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",
          "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 to the encrypted store (or `.env` file fallback) and list configured secret names. Values are never returned by any method — only names and metadata.

    | Method     | Scope   | Description                                                     |
    | ---------- | ------- | --------------------------------------------------------------- |
    | `env.set`  | `admin` | Write a secret to the encrypted store (triggers daemon restart) |
    | `env.list` | `admin` | List configured secret names (values never returned)            |

    ### `env.set`

    Write a secret value to the encrypted secrets store (`~/.comis/secrets.db` via AES-256-GCM) or the `.env` file fallback when no master key is configured. The daemon restarts automatically after a successful write so the new value is available to all subsystems.

    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" \| "envfile", restarting: true }` |

    `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": "sk-..." },
        "id": 1
      }
      ```

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

    <Warning>
      `env.set` triggers a daemon restart (`SIGUSR2`) approximately 200ms after returning. Your WebSocket connection will drop and reconnect. Active sessions are preserved via restart continuations.
    </Warning>

    ### `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: "encrypted" \| "envfile", provider?, createdAt?, updatedAt?, usageCount? }>, 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": "encrypted", "updatedAt": 1773313498000, "usageCount": 142 }
          ],
          "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)">
    Internal scheduler wake signal. Used by webhooks and other triggers to nudge the heartbeat coalescer into running immediately.

    | Method           | Scope | Description                                  |
    | ---------------- | ----- | -------------------------------------------- |
    | `scheduler.wake` | `rpc` | Request an immediate heartbeat coalescer run |

    ### `scheduler.wake`

    Signal the wake coalescer to fire immediately rather than waiting for the next scheduled interval. The coalescer debounces rapid wake calls, so multiple calls within a short window are collapsed into a single run. Returns immediately — the actual heartbeat execution is fire-and-forget.

    <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** | `{ source?: string }`            |
    | **Returns**    | `{ woke: true, source: string }` |

    `source` is a free-form label for tracing (e.g. `"webhook"`, `"agent"`). Defaults to `"agent"` when omitted.

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

      ```json Response theme={}
      {
        "jsonrpc": "2.0",
        "result": {
          "woke": true,
          "source": "webhook"
        },
        "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>
