/ws or the HTTP endpoint at POST /rpc.
The authoritative method set is API_CONTRACTS_ORDERED in packages/core/src/api-contracts/. Gateway registration is derived from that registry, and the bidirectional architecture check prevents handler/contract drift; this reference therefore avoids a hand-maintained global method count.
Session-creation walkthrough
The most common end-to-end flow: open a connection, ping for liveness, kick off an agent execution, and check the resulting session. Every step is one JSON-RPC call.Connect with a bearer token
4001.Verify liveness
system.ping is the cheapest method and a good way to validate auth + transport before sending real work.{"jsonrpc":"2.0","result":{"pong":true,"ts":1773313498000},"id":1}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, tokensUsed, and finishReason.Inspect or list the session
session.list enumerates active sessions; session.history reads the full message log for one session.Clean up when done
session.delete (admin) to archive and remove the exact conversation partition. RAG memories are not deleted by this method; use session.reset_conversation with memory: true for that separate destructive operation.POST /rpc. For streaming token deltas use the SSE endpoint POST /api/chat/stream documented in HTTP Gateway.
Scopes
Every JSON-RPC method requires a specific scope. Tokens are configured ingateway.tokens[] with a scopes array.
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.params object is optional and defaults to {} when omitted. The id field correlates requests with responses.
Methods by Namespace
system (1 method)
system (1 method)
agent (4 methods)
agent (4 methods)
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 POST /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.config (10 methods)
config (10 methods)
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 (2 methods)
gateway (2 methods)
obs (16 methods)
obs (16 methods)
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 message-lifecycle statistics across daemon restarts. total includes every completed lifecycle, while attempted is the delivery-health denominator (success + error + timeout). Intentional filtering and user cancellation are reported separately and do not depress the success rate. Average latency uses attempted deliveries only. Pass sinceMs as a duration-ago window; omit it for the complete retained history.obs.context.dag
obs.context.pipeline
obs.getCacheStats
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.obs.explain
Assemble a self-contained, redaction-safe post-mortem (an IncidentReport) for a single agent session: outcome, cost, timing, per-tool stats, the normalized failures (newest-first), the circuit-breaker timeline, large-result offloads, a one-line summary, and a deterministic likelyRootCause. The report is derived from log evidence only — no LLM is invoked — so it reproduces the same verdict from the same session forever.outcome.endReason names the terminal cause. Three degradation causes are named individually rather than collapsing into a generic error (and drive likelyRootCause): context_exhausted (the context-window pre-flight guard aborted the run before the model could continue), output_starved (the final response was truncated at the model’s max output tokens; the fix is to raise the agent’s maxTokens or enable contextEngine.outputEscalation), and timeout (the prompt-level stall budget or makespan ceiling killed the turn; likelyRootCause code prompt_timeout — when the trajectory carries the enriched execution.prompt_timeout record the verdict is numbers-backed: the stall variant names the binding knob, e.g. agents.<id>.promptTimeout.promptTimeoutMs, with the configured budget and elapsed time, and the makespan variant names agents.<id>.promptTimeout.stallCeilingMultiplier for a streaming runaway; sessions whose trajectory lacks that record fall back to a generic knob suggestion). A tool-failure root cause still out-ranks them — they explain the terminal state, not a tool crash. The same causes are aggregated deployment-wide by obs.system.health’s degradedByCause.An outcome.endReason of background_pending means the foreground turn
deliberately ended after promoting work; the terminal user outcome belongs to
the durable background completion and exact-origin delivery lifecycle.
likelyRootCause.code is background_pending and its steps point to the
background_task:promoted → background_task:completed → either
background_task:notified (including live_turn_consumed and fallback routes)
or background_task:reentered. If protected restart recovery could not
durably reset a task transition, the higher-priority
background_recovery_retry_required verdict points to the retained
reconciliation authority and the
health_signal:background_task_recovery_failed system-health signal.For context_exhausted sessions the report carries an optional contextBudget section — the terminal per-LLM-call budget equation extracted from the trajectory’s context.budget records (last record wins): { windowTokens, rawContextWindowTokens, windowCapSource, systemTokens, freshTailTokens, budgetedHistoryTokens, keptCount, assembledInputTokens, outputHeadroom, verdict }. windowCapSource names what clamped the effective window below the model’s configured contextWindow: the exact contextEngine.budget.* knob (effectiveContextCapSmall / effectiveContextCapNano), served (the Ollama-served num_ctx bound the window — fix with OLLAMA_CONTEXT_LENGTH=<configured> ollama serve or a Modelfile PARAMETER num_ctx), capabilityClass (the operator’s providers.entries.<id>.capabilities.capabilityClass pin bound the window — pin a higher class or remove the pin; the contextEngine.budget.* caps do not move this bind), or none. verdict is the fit-check outcome (fits / downshifted / exhausted). When present, likelyRootCause is numbers-backed: it states the assembled-vs-window totals, the binding cap, the system+tools share of the window, and the kept-history count, and its suggested steps name the binding lever (the budget knob to raise, the Ollama serve knobs, or the capabilityClass pin) or recommend reducing the active tool surface when tool schemas dominate. The section is absent when the trajectory carries no context.budget records; the verdict then falls back to the generic wording.When the session ran any memory recalls the report carries an optional recall section — the memory-recall outcome aggregated over the trajectory’s memory.recalled records: { recalls, zeroHits, lastLanes, lastFinalCount, rerankerAvailable, crossUserRecalls?, lastCrossUserCount? } (counts and booleans only — never the query text or any recalled memory body). recalls is how many recalls ran, zeroHits how many returned no injected memories (a recall miss), and the last* fields describe the terminal recall (its lane count and final injected-memory count). crossUserRecalls (present only when > 0) is how many recalls injected at least one memory scoped to a different user than the conversation — the cross-sender privacy signal for agent-scoped recall (one shared agent surfacing sender A’s memory into sender B’s turn), so “was cross-sender data injected into this turn’s context?” is answerable from the always-on report without pre-enabling the opt-in diagnostics.recallTrace artifact; lastCrossUserCount is the terminal recall’s cross-user injected count. Counts only — never the user-ids or bodies. This drives a dedicated recall_miss likelyRootCause verdict: a degraded session whose recalls all missed (zeroHits === recalls) and that matched no tool/context/breaker cause is root-caused as a recall miss, pointing the operator at the recall scope (agent- vs user-scoped) and, for non-Latin queries, the trigram-twin lanes (see comis system-health 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 (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 and the admission funnel by comis memory skills.The reflection cron itself emits two counts-only trajectory events, surfaced through comis explain / comis system-health and bridged so a reflection run is reconstructable per session — never a doc body:reflect:admitted—{ agentId, count, timestamp }: the number of candidate docs admitted to the store this run.reflect:funnel—{ agentId, synthesized, validated, admitted, maxClusterCardinality, distinctTopicKeys, untrustedDrops, nameLengthRejections, skipped, sourceTrajectoryCount, totalSourceChars, admissionOutcome, timestamp }: the whole admission funnel as counts plus one content-free closed enum.maxClusterCardinalityis the largest distinct(session, sender)corroboration size (a value of1means a single uncorroborated instance, so admission correctly refused — the same conservatism that defeats poisoning).distinctTopicKeysis the under-merge discriminator —synthesized > 1withdistinctTopicKeys > 1andmaxClusterCardinality < 2means the successes landed on separate topicKeys (under-merge → the LLM-tag-fallback trigger), vsdistinctTopicKeys: 1, maxClusterCardinality ≥ 2= genuinely corroborated.untrustedDropsis the magnitude behind anuntrusted_originverdict;sourceTrajectoryCount/totalSourceCharsdistinguish an empty-source wiring gap (0 chars in) from an LLM-yield (real chars in, nothing admitted).admissionOutcomeis thereflectOutcomeverdict — 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), orrejected_validation— socomis explainanswers “why was 0 admitted” from one field.
comis explain / comis system-health lenses and counts only — never a memory body: learning:memory_demoted / learning:memory_evicted (the per-sweep soft-eviction counts), plus a once-per-run learning:lifecycle_swept ({ agentId, scanned, promoted, demoted, evicted, timestamp }) summary. The summary is folded onto the cron run history (cron.runs jobName "Memory lifecycle" shows scanned/evicted/demoted per sweep) and rolled into a daemon-wide memory_lifecycle comis system-health finding (run count + summed evicted/demoted; evicted=0 is usually healthy — no corroborated-wrong / dormant candidates — not a fault). The corroborated-failure accrual that precedes eviction emits learning:memory_failure_attributed ({ agentId, count, timestamp }) so “why did/didn’t this memory evict” has an event trail. They are bridged onto the trajectory so an eviction sweep is reconstructable per session; they are part of the one learning layer (agents.<id>.learning.enabled, force-disabled by memory.enabled: false) and read learning.forget.*. Recall ranking is the fixed rag.scoring fusion — there is no learned recall weight. The per-memory recall score breakdown (the recall records) still carries a usefulnessOutcomeShare annotation — the outcome-attributed component of a memory’s usefulness factor (the bounded recordUsage/recordFailure feed), distinct from its lexical relevance base (a derived, normalized share — never a raw learned weight).One reflection cron. There are no standalone user-representation or consolidation/reasoning crons and no dedicated revision/generalization telemetry events: that work runs inside the one reflection cron asFor an unattended coding-CLI drive (a webhook/cron turn that opens akind:"profile"/kind:"topic"docs (see memory config). A profile revision surfaces through thereflect:*funnel above, not a dedicated event.
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 (0 → success); failureClass is a closed enum (timeout / stdout_cap / nonzero_exit / spawn_fail / lease_absent) present only on a failed run; each toolCalls entry ({ tool, capability, decision, count }) is attributed to the run by its per-run child leaseId, so an in-jail decision: "deny" groups under the run that attempted it, not the shared assembly lease that spans the session; savings ({ estSavedTokens, savedRatio }) is the measured token-savings estimate, present only when the run materialized ResultRefs. This drives a deterministic orchestrate_failed likelyRootCause verdict — it fires when any run’s outcome is failure or any toolCalls entry is a deny, names the failed-of-total run count with the de-duplicated failureClass / exitCode / denied-capability sets, and (keyed on a signal absent from a non-autonomy session) never reorders the other verdicts. Note that capability:audited records the authorization decision only — an allowed tool call whose result then failed is not in that stream; the run-level failureClass + the failing tool’s own bounded stderr tail cover that half. The daemon-wide savings roll-up is obs.system.health’s orchestrate_efficiency finding. The section is absent for sessions that ran no orchestrate scripts.Accepts ONE of sessionKey / traceId / rootRunId (at least one is required). A traceId or a rootRunId is canonicalized to its sessionKey first, so by-trace, by-session, and by-run produce the identical report. rootRunId is an autonomy run’s id — the synthetic in-process root-session-<sessionKey> (resolved by a pure prefix-strip) or a real spawned/socket root (resolved by scanning the day-keyed session-index for the run’s capability.audited record). It is the obs.system.health → obs.explain drill-down ref: pass the system report’s autonomy.worstRootRunId to render that run’s spawnTree. An unresolvable rootRunId (or traceId) yields the honest session_not_found marker naming the ref that missed — never a clean-looking empty report. There is no by-job-id form: a cron wake-gate 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.system.health’s cronWakeGate block.depth: "summary" (the default) bounds the serialized report to roughly 6 KB (about 1,500 tokens) and caps the failure list; depth: "full" relaxes the array caps. At BOTH depths the report is digest-only: no raw tool-output body is ever inlined (oversized previews and untrusted external content collapse to a resultDigest fingerprint), and an honest truncations[] ledger records anything the bounding pass dropped.An optional coverage block reports READ-coverage — whether the trajectory, rollup, and each offload pointer were actually located and resolved ({ trajectory: { found, records }, rollup: { present }, offloads: { pointersResolved, pointersTotal } }) — distinct from truncations[], which records what the size-bounding pass dropped. A degraded report is self-evident from it (e.g. records: 0 or pointersResolved < pointersTotal) rather than masquerading as a clean session. When the session resolved to real on-disk artifacts, coverage.sources carries the source paths (pointers, never bodies): { session, trajectory }. The distinction is load-bearing for numeric/value reconciliation — the .trajectory.jsonl holds tool-call provenance only (toolName / success / durationMs; the result body is deliberately kept out of the event stream for secret-egress safety), while the co-located raw session .jsonl (sources.session) holds the tool-result values the model actually saw. To reconcile a reported figure to the tool result that produced it, read session (values), not trajectory (provenance); large results additionally offload to disk (offloads[].diskPathRel).coverage.toolStats reconciles this report’s per-tool toolStats (the whole-session trajectory union) against the persisted per-session rollup that obs.system.health reads (the latest-execution rollup — it is built per execution and the session metadata is overwritten each turn). The two lenses read structurally-different sources, so they CAN differ for the same session — but only in one direction: the rollup is a subset of the trajectory (rollup.{ok,failed} <= trajectory.{ok,failed} per tool). The block ({ reconciled, rollupSource: "last-execution", divergentTools: [{ tool, rollup: { ok, failed }, trajectory: { ok, failed } }] }) makes that gap transparent so comis explain and comis system-health can never silently contradict: divergentTools[] names each tool whose persisted rollup differs from the trajectory with both count pairs, and reconciled is the directional invariant (a rollup that OVER-counts the trajectory — the forbidden direction, including a tool present in the rollup but absent from the trajectory — flips it to false and surfaces the offending tool instead of hiding it).comis explain CLI command (see the CLI reference) and the obs_query agent explain / session_report actions surface this same report.obs.system.health
Assemble a bounded, redaction-safe cross-session system-health triage (a SystemHealthReport) over a recent time window: how many sessions ran and how many degraded, the degraded sessions bucketed by named cause (degradedByCause), the merged top error kinds, the total circuit-breaker trips, a per-tool ok/failed rollup, the window cost, the recent activity (agents, channels, exit reasons), the recurring findings[] (counts + short codes + hints only — no raw WARN bodies), and a deterministic likelyRootCause. Like obs.explain, the report is derived from log + diagnostics evidence only — no LLM is invoked — so the same window reproduces the same verdict.degradedByCause is the system-level degradation detector: a bounded { <endReason>: count } map (counts only) over the degraded sessions in the window, keyed by the named terminal cause — e.g. { "context_exhausted": 5, "output_starved": 2 } answers “how many sessions degraded, and why” at a glance. Only degraded sessions contribute; a session whose cause is missing folds into "unknown". Two named degradation causes are worth calling out — context_exhausted (the context-window pre-flight guard aborted the run; run obs.explain on the session for the numbers-backed contextBudget verdict) and output_starved (the final response was truncated at the model’s max output tokens) — both named here and in obs.explain’s outcome.endReason + likelyRootCause, so neither collapses into a generic error.likelyRootCause ranks acute over chronic: any degraded session with a named cause (system_acute_degradation, naming the dominant degradedByCause entry) out-ranks the standing config-posture (system_config_posture) and recurring-health-signal (system_recurring_health_signal) verdicts; only a deployment-wide degradation spike (system_high_degraded_rate) ranks above it. A degraded autonomy run (an unattended run orphaned on restart, revoked/killed, or aborted by the capability-denial breaker) ranks first of all — system_autonomy_degradation names the worst run’s rootRunId so you can paste it straight into comis explain (the unattended-mode signal out-prioritizes the session-level rules). Routine session_rebase continuations (a restart resuming at the store’s max seq) ingest at info severity, so they never inflate the warning findings.The boot-time config_posture diagnostic row carries servedBelowConfiguredCount in its details JSON — the number of providers whose Ollama-served window (num_ctx) was below the configured contextWindow at that boot (a count only, never provider names; the posture record severity is warning when it is > 0). When the latest posture row’s count is non-zero, findings[] gains the dedicated config_posture:served_below_configured code with that count — latest-row semantics because posture is standing state, not cumulative (a healthy restart clears the finding). The hint names the OLLAMA_CONTEXT_LENGTH / Modelfile PARAMETER num_ctx knobs; a malformed posture row (or one missing the count field) folds to 0 defensively (no finding, never an assembly error). For triaging this finding on a local deployment — model choice, window sizing, and the full knob map — see the Local models playbook.When transcriptions/syntheses degraded across the window, findings[] gains a voice_health code: the count of degraded STT/TTS turns and the dominant voice errorKind in the detail (e.g. model_load_failed, auth_required), with a hint pointing at comis explain and — for the keyless-engine load/download failures — the whisper model cache + disk + the local engine probe. The finding rolls up the voice_degraded health_signal diagnostic rows the daemon voice path emits on a failure; like every system finding it carries counts + a short code + a closed errorKind label + a static hint only — no raw provider body or secret, safe to paste. A malformed row folds to a no-count defensively (never an assembly error), and there is no finding when no voice turn degraded. See Voice → Observability and cost.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 system finding it carries counts + a short code + a static hint only — no pipeline body, no node type_config, no secret, safe to paste. The hint names the small-model-DAG-authoring build/defer gate. The finding fires only when at least one small/nano-tier authoring ran in the window; mid- and unknown-tier authorings are in neither cohort. The denominator counts every contract-parse-reachable authoring invocation — a present-but-malformed call that fails the request-contract parse or the graph parse/validate step is counted as invalid; only the bespoke pre-checks (a define with no nodes, an execute rejected by the agent-to-agent policy gate) are excluded, since neither is an authoring attempt.This finding is the metric behind a pre-committed, measure-first decision rule. The report carries a deterministic pipelineAuthoringGate verdict { buildAuthor, reason }, computed by a pure reducer over the windowed authoring rows — the same window always reproduces the same verdict (no LLM, no clock). Native small-model-authorable DAG support is built only when this rule fires buildAuthor: true, which happens only when both gates pass: small-tier invocations are non-trivial (at least 20 over the window) AND small-tier author-validity is materially below frontier (at least 15 percentage points below). Otherwise it defers — and with no telemetry, the default state for a fresh deployment, it returns buildAuthor: false with a reason naming insufficient telemetry. The thresholds are pinned in code so a silent retune is caught; the verdict is auditable from comis system-health once the daemon has accumulated authoring telemetry.When the daemon ran orchestrate scripts over the window, findings[] gains an orchestrate_efficiency code — the measured token savings from those runs materializing high-volume tool results as ResultRefs instead of re-entering them into context. It rolls up the content-free orchestrate_efficiency health_signal rows the daemon emits from each orchestrate:run_summary event: the detail names the run count and the summed estimated tokens saved (plus a degraded-run count when any run recorded a failureClass), and the count is the run count. Like every system finding it carries counts + a short code + token estimates + a static hint only — no script body, stdout, or secret, safe to paste; the hint points at comis explain for the per-run savings breakdown. A completed run — success or a classified failure — is standing efficiency signal, not a system degrade, so it rides info severity and never inflates the degraded count; the finding fires only when at least one orchestrate run ran in the window.When the daemon runs unattended/durable runs, the report carries an optional autonomy block — the cross-run autonomy-health slice: { runs: { total, degraded, degradedRate }, orphaned, resumed, revoked, killed, breakerTrips, denialBreakerTrips, budgetBreaches, costUsd, worstRootRunId? } (counts + the worst run’s id only — never a lease bearer, an orphan reason body, or any secret; safe to paste). The run counts + degraded rate come from the crash-surviving durable_run_checkpoints table (countByStatus — autonomy runs are durable runs by construction, so this survives a hard crash that would lose an in-process event), where degraded = orphaned + revoked. resumed (healthy crash-recovery) and killed are recovered from the event-sourced health_signal rows — killed is separable from revoked only because a hard run.kill and a cooperative lease.revoke (which both flip the durable status to revoked indistinguishably in the table) emit distinct events. breakerTrips is the tool-failure breaker count (the synthetic-excluded breakerTripTotal read-back — summed per-session breakerTripCount). denialBreakerTrips is the distinct capability-denial breaker count — N consecutive capability/quota floor-blocks aborted + killed a run tree. It is event-sourced from the content-free autonomy_denial_breaker health_signal rows, NOT a session rollup: a denial-breaker abort is never a session endReason and never a breakerTripCount, so breakerTrips can never see it, and the aborted run lands in durable status completed (so it is also 0 in orphaned/revoked/killed) — this separable count is its only system surface. budgetBreaches is the per-node token-budget breach count; costUsd is the window’s autonomy-inclusive cost. worstRootRunId is the highest-severity degraded run (orphaned > denial-breaker > killed > revoked) — paste it into comis explain for its spawn-tree. The same labels also surface as dedicated findings[] codes (durable_orphaned / autonomy_revoked / autonomy_killed / autonomy_denial_breaker). The block is absent when the durable store is unwired — e.g. the daemon-less comis system-health --offline CLI, or a non-durability boot — an honest coverage degradation, not a silent zero.When cron jobs ran a wake-gate 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.system.health rolls up a WINDOW of sessions.sinceHours is optional; the 24h default is applied in the handler. The report is digest-only: findings[] is capped top-N and topErrorKinds is merged + capped (counts only), with an honest truncations[] ledger recording anything the bounding pass dropped. An optional coverage block reports READ-coverage of the system sources — the session-summary store ({ found, rows }), the multi-day session index ({ daysRead, daysMissing }), and the billing source ({ present }) — so a silently-empty window (e.g. rows: 0 or daysMissing > 0) is self-evident rather than masquerading as a clean zero-activity system.comis system-health CLI command (see the CLI reference) and the obs_query agent system_health action surface this same report. It is the cross-session sibling of comis explain — NOT to be confused with comis health, the LOCAL daemon doctor (which runs no RPC).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.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
session (12 methods)
session (12 methods)
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.ChannelEndpoint is the complete exact routing identity used by session,
message, notification, and platform-action RPCs:
{ channelType, channelInstanceId, conversationId, threadId?, conversationKind: "direct" | "shared" }.
It is projected from the conversation’s authoritative scope and is omitted only
when that scope is not endpoint-bound.session.list
List sessions in an exact tenant and agent partition, including sessions stored in SQLite, JSONL transcript files, and workspace session files. Optionally filter by recency or session kind.session.send
Send text to an exact durable conversation under the agent-to-agent policy and principal-isolation gates. Agent-origin calls are confined to the caller’s tenant and principal and may target only the same agent or an exact delegated child.session.spawn
Start a background sub-agent and return its run ID immediately. The optional async field is accepted but does not select a synchronous path. Agent-origin calls bind result delivery to the immutable requester origin; only an authenticated control-plane caller without an agent principal may provide both announcement-route fields explicitly.session.delete
session.reset
Clear the working and runtime transcripts while preserving the conversation identity. It also clears cached approval decisions and pending delivery-mirror rows. LCD durable history is not cleared; use session.reset_conversation when every prompt-bearing layer must be forgotten.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 owner-authorized, content-bounded status of a sub-agent run. Raw task text, provider output, display session keys, and free-form errors never cross this surface.Agent callers receive only endReason, completedAtMs, and failure errorKind inside completion. An authorized administrator may additionally receive the bounded summary and resultRef retained on the run.session.search
session.compact
session.reset_conversation
Complete cross-mode conversation reset. Clears all four prompt-bearing layers: the delivery mirror, LCD durable history, daemon sessionStore working transcript, and pi runtime session. Destructive and irreversible. Admin-scoped — requires an admin token.This is the explicit forget command when the session identity should remain addressable. Unlike session.reset, it also clears the LCD store used by dag mode; memory: true additionally removes source memories. The runtime layer matters: without it, the surviving runtime JSONL is re-ingested wholesale on the next turn (LCD epoch rebase) and the “forgotten” conversation resurrects.The operation is serialized against live LCD ingest (safe to call while the daemon is running). Returns count-only — no message content is returned or logged. Best-effort on each layer: absent sessionStore entry or zero LCD rows is not an error.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.cron (8 methods)
cron (8 methods)
cron.add
Creates one authored job. The request is { name, agentId?, schedule, payload, sessionPolicy?, continuationMode?, deliveryTarget?, wakeGate?, cacheRetention?, toolPolicy?, maxConsecutiveDependencyErrors? }.Request fields are name, agentId, schedule, payload, sessionPolicy, continuationMode, deliveryTarget, wakeGate, cacheRetention, toolPolicy, and maxConsecutiveDependencyErrors. Schedule kinds are cron, every, at, and in; authorable payload kinds are heartbeat_event, delivery, and agent_turn.scheduleis exactly one of{ kind: "cron", expr, tz? },{ kind: "every", everyMs, anchorMs? },{ kind: "at", at, tz?, fold?: "earlier" | "later" }, or{ kind: "in", seconds }.everyMsandsecondsare positive safe integers. The persisted response projects aninoratrequest as{ kind: "at", atMs }.payloadis exactly one of{ kind: "heartbeat_event", text, wakeMode: "now" | "next-heartbeat" },{ kind: "delivery", text }, or{ kind: "agent_turn", message, model?, timeoutSeconds? }.sessionPolicyis{ strategy: "fresh" }or{ strategy: "rolling", maxHistoryTurns };continuationModeisnone,heartbeat_excerpt, ororigin_history.deliveryTargetis{ conversation, destinationEndpoint }.conversationcarries a canonicalconversationScopeand matchingconversationRef;destinationEndpointcarrieschannelType,channelInstanceId,conversationId, optionalthreadId, andconversationKind: "direct" | "shared".wakeGateis{ script, language: "js" | "ts", timeoutSeconds };cacheRetentionisnone,short, orlong;toolPolicyis{ profile, allow, deny }, whereprofileisminimal,coding,messaging,supervisor, orfull.
jobId, name, and schedule, with the schedule in persisted cron, every, or at form.cron.list
The request is { agentId? }. The response is { jobs }; every job contains id, name, agentId, source, schedule, lifecycle, and payload, plus only the optional policy fields that apply: maxConsecutiveDependencyErrors, sessionPolicy, continuationMode, deliveryTarget, wakeGate, cacheRetention, and toolPolicy.source is authored or built_in. Persisted schedule is cron, every, or at. lifecycle is one of:{ status: "scheduled", nextRunAtMs, consecutiveDependencyErrors }{ status: "paused", nextRunAtMs, consecutiveDependencyErrors, reason: "operator" | "dependency_errors" }{ status: "one_shot_claimed", executionId, scheduledForMs, claimedAtMs }{ status: "one_shot_terminal", terminalExecutionId, terminalAtMs }
payload.kind is heartbeat_event, delivery, agent_turn, or internal_action; built-in internal actions expose action: "memory_review" | "memory_lifecycle" | "reflection".cron.update
Updates an authored job selected by jobId or jobName; at least one selector is required. The strict request is { jobId?, jobName?, name?, schedule?, payload?, sessionPolicy?, continuationMode?, deliveryTarget?, wakeGate?, cacheRetention?, toolPolicy?, maxConsecutiveDependencyErrors?, paused? }. schedule and payload use the same closed unions as cron.add. Nullable deliveryTarget, wakeGate, cacheRetention, toolPolicy, and maxConsecutiveDependencyErrors clear those settings. The response is { jobName, updated }.Mutation vocabulary is jobId, jobName, name, schedule, payload, and paused; this response reports updated, while cron.remove reports the separate removed flag.cron.status
Returns scheduler-wide authority state for the resolved agent, not one job. The request is { agentId? }. The response is { state, configuredEnabled, running, strictAuthoritiesValid, ownershipReconciled, jobCount, activeClaimCount, resolvedAgentId, store, ledger, intent, lastError? }.Response fields are state, configuredEnabled, running, strictAuthoritiesValid, ownershipReconciled, jobCount, activeClaimCount, resolvedAgentId, store, ledger, intent, and optional lastError; the request optionally selects agentId.stateisinitializing,disabled,ready,active,maintenance, orfailed.storeandledgerare{ exists, bytes, digest };digestis a lowercase SHA-256 value ornull, and the three fields must agree about whether the authority exists.intentis{ status: "none" },{ status: "pending", operationId, target, phase, digest }, or{ status: "invalid", digest }. Pendingtargetisstore,ledger, orall;phaseisprepared,archives_recorded,replacements_recorded, orcompletion_recorded.lastError, when present, is{ code, errorKind }, using the closed maintenance error and platform error-kind vocabularies.
cron.runs
Reads immutable start/terminal groups for one job. The request is { jobName, limit?, agentId? }; limit is a positive safe integer capped at 10,000. The response is { runs }. Each run contains executionId, jobId, agentId, scheduledForMs, trigger, workKind, rootRunId, startedAtMs, optional terminalAtMs and durationMs, status, deliveryStatus, optional errorKind, and optional counters.Request fields are jobName, limit, and optional agentId.triggerisscheduled,catchup, ormanual;workKindisagent_turn,heartbeat_event,internal_action, ordelivery_only.statusisstarted,dispatched,completed,failed,aborted,skipped, orunknown.deliveryStatusisnot_requested,suppressed,pre_send_failed,accepted,partial,rejected, orunknown.countersappears only on terminalinternal_actionoutcomes. It contains at most 32 content-free{ name, value }records. A name matches^[a-z][a-z0-9_]*$, is at most 64 characters, and each value is a nonnegative safe integer.
cron.remove
Removes an authored job selected by jobId or jobName; at least one selector is required. The request is { jobId?, jobName? }. The response is { jobName, removed }.cron.run
Submits work through the normal scheduler execution path. The request is { jobName?, mode?: "force" | "due", agentId? }; omitted mode means force. force requires a resolvable jobName and returns { triggered, mode: "force", jobName, resolvedAgentId, executionId }. due evaluates all overdue occurrences for the resolved agent and returns { triggered, mode: "due", resolvedAgentId, executionIds }.The closed request/response vocabulary is jobName, mode, force, due, agentId, triggered, resolvedAgentId, executionId, and executionIds.A manual force submission does not change the job’s scheduled lifecycle or dependency-breaker state. It creates a new manual occurrence; it does not replay or reinterpret an earlier uncertain occurrence.cron.reset
admin-only compare-and-set maintenance for raw scheduler authorities. The request is one of { target: "store", expectedDigests: { store }, confirmed: true, agentId? }, { target: "ledger", expectedDigests: { ledger }, confirmed: true, agentId? }, or { target: "all", expectedDigests: { store, ledger }, confirmed: true, agentId? }. Each expected digest is a lowercase SHA-256 value or null when that authority is absent. The exact digest prevents resetting state that changed after inspection.The response is { operationId, target, resolvedAgentId, beforeDigests, afterDigests, state, reactivated }. beforeDigests and afterDigests each contain nullable store and ledger digests; state is disabled, ready, or active.Reset fields are target, expectedDigests, confirmed, optional agentId, operationId, beforeDigests, afterDigests, state, and reactivated.browser (13 methods)
browser (13 methods)
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.audio (1 method)
audio (1 method)
audio.transcribe
{ error: "..." } if STT is not configured or transcription fails.admin.approval (4 methods)
admin.approval (4 methods)
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.agents (7 methods)
agents (7 methods)
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 (9 methods)
memory (9 methods)
rpc scope; management methods use admin scope.memory.search
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.export
Export the entire memory database as a portable JSON snapshot. Useful for backups, migration between instances, or offline analysis.memory.store
models (3 methods)
models (3 methods)
@earendil-works/pi-ai SDK — getProviders() for the provider list and getModels(provider) for per-provider model details. These methods do not query provider APIs.models.list_providers
List all providers in the pi-ai SDK catalog. Returns provider ids and a count. The CLI setup flow uses this list for provider selection.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
Inspect whether a provider is used by configured agents and whether its models exist in the local catalog (or a configured custom-provider entry). No network request, credential verification, or inference is performed.tokens (4 methods)
tokens (4 methods)
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.channels (7 methods)
channels (7 methods)
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.mcp (6 methods)
mcp (6 methods)
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.skills (6 methods)
skills (6 methods)
skills.list
Install vetting
skills.upload, skills.import, skills.create, and skills.update all run the same pre-write vetting gate: every text file in the bundle is scanned, the bundle’s structure and manifest are checked, and nothing is written unless the result is acceptable for the skill’s trust tier. See Security Scanning for the tier × verdict matrix.Two failure shapes are distinct and both surface as JSON-RPC errors:[skill_vet_confirm:<verdict>]— the install needs an explicit acknowledgement. The message names every finding and its location. Re-run the identical call withforce: trueto proceed.[skill_vet_rejected:<verdict>]— the install is refused.forcedoes not override this.
force therefore means “I read the findings and accept them”, never “skip the scan”. On skills.upload and skills.import it additionally archives a conflicting bundled-MCP entry under _bundleArchive.skills.upload
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
https://github.com/{owner}/{repo}/tree/{branch}/{path}The optional scope parameter works the same as in skills.upload — “local” (default) imports to the agent’s workspace, “shared” imports to the global directory (default agent only).skills.create
Create a new skill by providing its full SKILL.md content directly — a convenient shortcut over skills.upload when your skill is a single file. The content is vetted before being written to disk (see Install vetting below). 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
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 (12 methods)
graph (12 methods)
graph.define
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.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
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.graphId — Returns detailed per-node execution state for a single graph, including node outputs (truncated to 500 characters), timing, and aggregate stats.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.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
heartbeat (4 methods)
heartbeat (4 methods)
context (2 methods)
context (2 methods)
rpc-scoped and back the dashboard’s Context DAG browser.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
workspace (12 methods)
workspace (12 methods)
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
daemon (1 method)
daemon (1 method)
discord (1 method)
discord (1 method)
discord.action
media (7 methods)
media (7 methods)
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.test.link
media.providers
message (7 methods)
message (7 methods)
message.send, message.reply, and message.react are
rpc-scoped outward operations guarded by the orch:message capability.
Editing, deleting, fetching, and attaching remain admin-scoped. Every method
requires the complete ChannelEndpoint for its target and
verifies the channel type, adapter instance, and conversation ID against the
other request coordinates. The request schema keeps endpoint optional only
because an authenticated in-process turn can supply the same endpoint through
request context; gateway clients must send it explicitly. Missing or
conflicting authority fails closed before any adapter action.autonomy.durability.enabled
is on, an outward message.send / message.reply / message.react issued by an
autonomy run (a run with a rootRunId) uses one stable logical operation
identity. While its ledger row is retained, a committed repeat returns the prior
platform receipt without another send, and every non-committed existing state
blocks another execution. A call whose platform outcome may be ambiguous is
parked unresolved and escalated; startup applies that rule to every interrupted
row without a channel-history query or replay. This is not a universal
exactly-once guarantee. See Durability & Resume
for the uncertainty model. Operator-issued sends (no rootRunId) remain
best-effort pass-through operations.message.send
message.reply
message.edit
message.delete
message.fetch
message.react
message.attach
notification (1 method)
notification (1 method)
notification.send
channel_type plus channel_id, a configured primary channel,
and automatic recent-session selection are accepted only when they resolve to
tracked endpoint authority. destination_endpoint is an exact endpoint claim,
not caller-minted authority: all five fields must match the tracked endpoint for
that agent, including channel instance, optional thread, and direct/shared kind.
Resolution fails closed when the tracked endpoint or its active adapter instance
is unavailable.slack (1 method)
slack (1 method)
slack.action
subagent (7 methods)
subagent (7 methods)
subagent.list, subagent.wait, subagent.kill, and subagent.steer have both rpc and admin routes. Agent callers on the rpc route are owner-scoped to runs created by the same caller agent and conversation; they cannot select global scope. Admin callers can inspect or control any selected run. subagent.pause, subagent.resume, and subagent.status are admin-only.subagent.list
recentMinutes defaults to 30 and accepts integers from 1 through 10,080. On the rpc route, agentId and rootRunId are forbidden and the response contains only runs owned by the caller’s agent-and-conversation pair. Those records are a content-free projection: runId, status, agentId, startedAt, and, when present, queuedAt, completion, depth, rootRunId, graphId, and nodeId. Terminal completion exposes endReason, completedAtMs, and errorKind for unsuccessful runs. On the admin route, agentId and rootRunId are optional exact-match filters and runs contains the full diagnostic run records. total is the length of the returned runs array.subagent.wait
runIds, when supplied, must contain 1–32 IDs; duplicates are removed. Without runIds, the handler selects up to 32 currently running or queued runs visible to the caller. Agent callers can wait only for owned runs; admins can wait for any selected runs. An unknown or unauthorized ID returns denied_unknown, deliberately avoiding run enumeration.timeoutMs accepts 0–300,000. When omitted, it uses security.agentToAgent.waitTimeoutMs (default 60,000) capped at 300,000.Each results entry includes its runId; its status is completed, denied_unknown, timeout, or cancelled:{ runId, status: "completed", completion }{ runId, status: "denied_unknown" }{ runId, status: "timeout" }{ runId, status: "cancelled" }
completion always contains endReason and completedAtMs. A successful completion has endReason: "completed". Failure end reasons are failed, killed, watchdog_timeout, or ghost_sweep and also carry errorKind. Both forms may include a bounded summary and resultRef. A resultRef contains ref, kind, bytes, preview, and expiresAt, with optional rows and schema; kind is one of jsonl, json, csv, html, text, or binary.subagent.kill
killed is true and runId repeats the target. A missing, unknown, or non-running target fails the RPC; it does not return { killed: false }.subagent.steer
security.agentToAgent.steerInject, which defaults to false:The status discriminator is steered with oldRunId and newRunId, or steered_inject with runId.- Flag off (default) — kills the current run and respawns a new run with
messageas its task. The continuation preserves the original spawn tree, agent, capabilities, and parent lease, but receives a new run ID and discards the prior live transcript and progress. Returns{ status: "steered", oldRunId, newRunId }. - Flag on — requires
targetto berunningand injectsmessageinto the live child at its next step boundary. It preserves the transcript and progress, performs no kill or respawn, and continues with the same run ID. Returns{ status: "steered_inject", runId }. Injection does not interrupt a tool call mid-execution.
target rate limit of one steer every 2 seconds. The message is framed as untrusted external content and cannot grant capabilities or bypass the child’s tool restrictions.subagent.pause
paused, acceptingSpawns, and literal resetsOnRestart fields describe the resulting gate. The call is idempotent: changed is true only when the gate transitions from unpaused to paused.subagent.resume
paused, acceptingSpawns, and literal resetsOnRestart fields describe the resulting gate. The call is idempotent: changed is true only when the gate was paused. Resume does not reopen admission after daemon shutdown has set acceptingSpawns: false.subagent.status
paused is the operator pause; acceptingSpawns is the runner’s broader admission state and becomes false during shutdown. resetsOnRestart is always true: pause state is not persisted, so every daemon restart starts unpaused.capabilities (1 method)
capabilities (1 method)
lease.revoke / run.kill / autonomy.evict are the write side).capabilities.introspect
_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.autonomy / live control (3 methods)
autonomy / live control (3 methods)
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
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
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
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.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 (1 method)
telegram (1 method)
telegram.action
whatsapp (1 method)
whatsapp (1 method)
whatsapp.action
delivery (1 method)
delivery (1 method)
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.env (2 methods)
env (2 methods)
env.set
Write a secret through the active SecretStorePort. The default encrypted
mode stores it in ~/.comis/secrets.db; file mode stores it in the
owner-readable secrets.json file. The read-only env storage mode rejects
writes—there is no .env fallback. A successful write updates the daemon’s
in-memory secret view without restarting it.Rate-limited to 5 writes per minute. The value is never logged at any level.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.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 (2 methods)
image (2 methods)
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.tts (2 methods)
tts (2 methods)
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.).link (1 method)
link (1 method)
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.media (3 agent-facing methods)
media (3 agent-facing methods)
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.scheduler (1 method)
scheduler (1 method)
scheduler.wake
Submits a routine wake to one closed target. agent targets the calling agent’s lane; monitoring targets the monitoring lane. This method admits or coalesces a heartbeat occurrence and does not inspect or run cron jobs.The closed vocabulary is target, agent, monitoring, accepted, coalesced, disposition, new_occurrence, occurrence_upgraded, correlationId, lane, and retainedReason.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.retainedReason is interval, manual, hook, wake, exec-event, cron, or task. accepted distinguishes a newly admitted occurrence from an upgrade of one already admitted; coalesced reports the retained occurrence without creating another.Error Handling
When a method call fails, the server returns a JSON-RPC error response.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
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).
