Skip to main content
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 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.
1

Connect with a bearer token

Open the WebSocket and authenticate. If the token is invalid, the server closes with code 4001.
2

Verify liveness

system.ping is the cheapest method and a good way to validate auth + transport before sending real work.
Response: {"jsonrpc":"2.0","result":{"pong":true,"ts":1773313498000},"id":1}
3

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.
Response carries response, tokensUsed, and finishReason.
4

Inspect or list the session

session.list enumerates active sessions; session.history reads the full message log for one session.
5

Clean up when done

Use session.delete (admin) to remove a session. The session entry plus its memory entries are dropped.
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.

Scopes

Every JSON-RPC method requires a specific scope. Tokens are configured in gateway.tokens[] with a scopes array. 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 for transport details.
The params object is optional and defaults to {} when omitted. The id field correlates requests with responses.

Methods by Namespace

Health check and connectivity testing.

system.ping

Agent execution for processing user messages through the LLM pipeline, cache statistics inspection, and per-operation model resolution introspection.

agent.cacheStats

agent.execute

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.

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.
Configuration reading, writing, schema inspection, history, and rollback. Read-only methods and mutations for runtime config management.

config.read

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

config.schema

config.patch

config.apply

config.diff

config.gc

config.history

config.rollback

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.

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.
Gateway server status, process information, and restart.

gateway.restart

gateway.status

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

obs.diagnostics

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.

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.

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.

obs.billing.byAgent

obs.billing.bySession

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.

obs.channels.stale

obs.channels.get

obs.delivery.recent

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.

obs.context.dag

obs.context.pipeline

obs.getCacheStats

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.

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’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 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.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 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) 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 (activestalearchived, 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 and the admission funnel by comis memory skills.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 discriminatorsynthesized > 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). 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 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 (0success); 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’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.healthobs.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 skip opens no session, so a cron job id is not a valid input — read a job’s skipped fires from 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’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 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).
The comis explain CLI command (see the CLI reference) and the obs_query agent explain / session_report actions surface this same report.

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’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.The same config_posture row also carries importedSkillCount (total imported skills) 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.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 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 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 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 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 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.
The comis fleet CLI command (see the CLI reference) 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).

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.
The comis security audit-log CLI command (see the CLI reference) 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.

obs.reset

obs.reset.table

These methods permanently delete observability data. The web console requires typing RESET as confirmation before calling obs.reset.
Session management for agent conversations. Client-facing methods use rpc scope; administrative operations use admin scope.

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.

session.history

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

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.

session.send

session.spawn

session.delete

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.

session.export

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

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.

session.compact

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.
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.
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.
Scheduled job management for cron expressions, intervals, and one-time tasks.

cron.add

cron.list

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

cron.update

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

cron.status

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

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: 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.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 filtered on that root. The cross-job skip-rate rollup is on obs.fleet.health’s cronWakeGate block.

cron.remove

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.
Browser automation via Chrome DevTools Protocol (CDP). All methods use rpc scope and bridge to the browser automation subsystem.

browser.status

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

browser.start

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

browser.stop

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

browser.navigate

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.

browser.screenshot

browser.pdf

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

browser.act

browser.tabs

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

browser.open

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

browser.focus

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

browser.close

Close a specific tab by its ID.

browser.console

Retrieve console log entries captured since the browser session started. Useful for debugging page-level JavaScript errors during automation.
Speech-to-text transcription using the configured STT provider.

audio.transcribe

Returns { error: "..." } if STT is not configured or transcription fails.
Approval gate management for action confirmation workflows.

admin.approval.clearDenialCache

admin.approval.pending

admin.approval.resolve

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.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.
Agent lifecycle management: create, read, update, delete, suspend, and resume agents.

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.

agents.create

agents.get

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.

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.

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.

agents.resume

Resume a previously suspended agent, allowing it to accept messages again.
Memory system operations: search, inspect, browse, manage, store, and export. Core search and inspect use rpc scope; management methods use admin scope.

memory.browse

memory.delete

memory.embeddingCache

memory.inspect

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

memory.stats

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

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.
memory.flush is irreversible. Export your memories with memory.export before flushing if you want a backup.

memory.export

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

memory.store

Model catalog inspection and connectivity testing. The catalog is sourced live from the @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.

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

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.

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.
Gateway token management for API authentication.

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.

tokens.create

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

tokens.revoke

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

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.
Channel adapter management: list, inspect, enable, disable, restart, capabilities, and health for chat platform connections.

channels.list

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

channels.capabilities

channels.get

channels.health

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.

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.

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.
Model Context Protocol (MCP) server management: list, connect, disconnect, and test MCP tool servers.

mcp.list

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

mcp.status

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

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.

mcp.disconnect

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

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.

mcp.test

Test connectivity to an MCP server without changing its state. Returns the round-trip latency and confirms the server is reachable.
Prompt skill management: list, create, upload, import (from a GitHub directory, an archive, or an allowlisted registry), update, and delete skills.

skills.list

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

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".

skills.import

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 modessource selects the acquisition channel (it defaults to github when a url is present):
  • githuburl 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; 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 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.
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.
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 for the trust model and the honest limits of the import scan.

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

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.

skills.delete

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.
Graph execution engine for multi-step agent pipelines (DAGs).

graph.define

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

graph.execute

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

graph.status

Returns graph execution status. This method has two response forms depending on whether a graphId parameter is provided.With graphId — Returns detailed per-node execution state for a single graph, including node outputs (truncated to 500 characters), timing, and aggregate stats.
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.
The concurrency object reports the current state of the 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.
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.

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.

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.

graph.list

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

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.

graph.deleteRun

graph.runDetail

graph.runs

Per-agent heartbeat management for automated check-in and health monitoring.

heartbeat.states

heartbeat.get

heartbeat.update

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

heartbeat.trigger

LCD lossless-store RPCs. Both methods are rpc-scoped and back the dashboard’s Context DAG browser.
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.

context.conversations

context.tree

Agent workspace file and git management. Read, write, and delete workspace files, browse directories, and manage git history.

workspace.status

workspace.readFile

workspace.writeFile

workspace.deleteFile

workspace.listDir

workspace.resetFile

workspace.init

workspace.git.status

workspace.git.log

workspace.git.diff

workspace.git.commit

workspace.git.restore

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.
Runtime daemon management.

daemon.setLogLevel

Discord platform action dispatch. Sends a platform-specific action to the Discord channel adapter.

discord.action

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.

media.test.stt

media.test.tts

media.test.vision

media.test.document

media.test.video

media.providers

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.
Exactly-once across a daemon restart (autonomy-originated sends). When autonomy.durability.enabled 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 for the guarantee and its honest per-channel coverage. Operator-issued sends (no rootRunId) are unchanged at-least-once.

message.send

message.reply

message.edit

message.delete

message.fetch

message.react

message.attach

Send notifications through configured channels.

notification.send

Slack platform action dispatch. Sends a platform-specific action to the Slack channel adapter.

slack.action

Sub-agent lifecycle management: list running sub-agents, kill stuck executions, and steer active sub-agents with new instructions.

subagent.list

subagent.kill

subagent.steer

Behavior is gated on security.agentToAgent.steerInject (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 governance applies regardless of message source).
The read-only, agent-reachable introspection of the bounded-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).

capabilities.introspect

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’s spawnTree instead.
Operator-facing live control of the bounded-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.

lease.revoke

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.

run.kill

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.

autonomy.evict

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 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 lands on when a mode cannot be resolved.
Durability / resume signals. When autonomy.durability.enabled 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. See Durability & Resume.
Telegram platform action dispatch. Sends a platform-specific action to the Telegram channel adapter.

telegram.action

WhatsApp platform action dispatch. Sends a platform-specific action to the WhatsApp channel adapter.

whatsapp.action

Delivery queue status inspection. Provides a live count of messages at each stage of the outbound delivery pipeline.

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

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

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.filter supports glob-style patterns (e.g. OPENAI_*). limit defaults to 100, max 500.
Image generation and image analysis. image.generate produces images from a text prompt; image.analyze runs vision analysis on an existing image.

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.

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.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.
Text-to-speech synthesis and auto-trigger checking. Requires a TTS provider configured under media.tts.

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

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.strippedText has any [[tts:...]] directives removed. mode reflects the configured autoMode ("always", "never", "voice_reply", etc.).
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.

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.

media.describe_video

Analyze a video attachment using a vision provider that supports video (e.g. Gemini). Returns a natural-language description.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.truncated is true when the document exceeded the extractor’s character limit and the text was cut off.
Internal scheduler wake signal. Used by webhooks and other triggers to nudge the heartbeat coalescer into running immediately.

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.
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 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.
source is a free-form label for tracing (e.g. "webhook", "agent"). Defaults to "agent" when omitted.

Error Handling

When a method call fails, the server returns a JSON-RPC error response.
Common error conditions: See the WebSocket Protocol 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 script call a capability-scoped tool.

tool.invoke

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) → requireCapabilityroute (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).
Request (over COMIS_ORCH_SOCKET, not the gateway)

WebSocket Protocol

Transport protocol, error codes, and connection management

HTTP Gateway

HTTP endpoint reference and authentication

CLI Reference

CLI commands that use these JSON-RPC methods

Config YAML

Gateway and scope configuration