What reloads, and how
| Change | How it applies | Drops connections? |
|---|---|---|
config.patch / config.apply (any section) | Validate → write → 200 ms delayed SIGUSR1 → process restart | Yes — supervisor (pm2/systemd) restarts the daemon. WS clients reconnect with restart-continuation. |
agents.create / agents.update / agents.delete | Same path, but with a 2-second debounced SIGUSR1 so batches coalesce | Yes |
tokens.create / tokens.revoke / tokens.rotate | Same debounced restart path | Yes |
channels.enable / channels.disable | Same debounced restart path | Yes |
gateway.restart RPC | Direct SIGUSR1 (200 ms delay to flush response) | Yes |
tooling.* (entire subtree) | config.patch / config.apply validate → write → 200 ms delayed SIGUSR1 → process restart. Cannot hot-flip within a running session — tooling.capabilityIndex.enabled selects between two cached system-prompt shapes (one-line residual vs flat tool dump), and tooling.installDetours.mode is read at adapter-construction time. Operator-only: agents cannot self-edit (the entire tooling subtree is in IMMUTABLE_CONFIG_PREFIXES). | Yes |
| New SKILL.md added to a discovery path | chokidar add event → debounced re-discovery, no restart | No |
| Existing SKILL.md edited or deleted | chokidar change / unlink event → debounced re-discovery | No |
Editing ~/.comis/config.yaml directly | Not detected. Run comis config validate then restart manually. | — |
Config Hot Reload
Config changes are applied through RPC methods that validate the change, write to the local YAML file, and trigger a SIGUSR1 signal for process restart.Source:
packages/daemon/src/rpc/config-handlers.ts — 10 RPC methods including config.patch, config.apply, config.rollback, gateway.restart.config.patch
Deep-merges a partial config change into the existing configuration. Flow:- Rate limit check — token bucket, 5 patches per 60 seconds (shared with
config.apply) - Immutable path check — certain config paths cannot be modified at runtime
- Build patch object — supports dot-notation keys (e.g.,
budget.maxTokens) - Type coercion — automatically converts string booleans (
"true"/"false") and numeric strings to native types (handles LLM tool call quirks) - Deep merge — merge patch into current in-memory config
- Zod validation — validate merged result against
AppConfigSchema - MCP server env restore — restore
envfields from existing YAML when the UI patch omits them (becauseconfig.readredacts secret values) - Duplicate MCP server name check — reject patches with duplicate server names
- Suspicious env value check — reject bare
$VAR,[REDACTED], raw keys - Atomic write — write to temp file, then
rename()toconfig.local.yaml - Best-effort git commit — record the change in config git history
- Audit event — emit
audit:eventwithactionType: "config.patch" - 200ms delayed SIGUSR1 — schedule process restart to pick up new config
config.apply
Full section replacement (not deep merge). Same flow asconfig.patch except step 5 replaces the entire section instead of merging. Shares the rate limit bucket with config.patch.
config.rollback
Restores config from git history to a specific commit SHA. CallsconfigGitManager.rollback(sha), then sends SIGUSR1 for restart.
persistToConfig (Internal Utility)
Used by management RPC handlers (agents, tokens, channels) to persist config changes. Follows the same validate-write-restart pattern but with a 2-second debounced SIGUSR1 instead of the 200ms delay used byconfig.patch. This allows batch operations (e.g., creating 8 agents in sequence) to coalesce into a single restart.
Source:
packages/daemon/src/rpc/persist-to-config.ts — debounced SIGUSR1 for batch config operations.Immutable Config Keys
Certain config paths cannot be modified at runtime through the RPC API. Attempting to patch these paths returns an error instructing the operator to modify config files manually.Git Versioning
Config changes are recorded in a git repository for history and rollback. The git operations are best-effort: if git is unavailable or the commit fails, the config change still proceeds. History and diff operations are available through theconfig.history and config.diff RPC methods.
Config Patch Rate Limiting
A token bucket rate limiter enforces a maximum of 5 patches per 60 seconds. This limit is shared betweenconfig.patch and config.apply to prevent runaway config changes from agent tool calls or automated scripts.
When the rate limit is exceeded, the RPC returns an error with a retry-after duration:
Skill Hot Reload
Skill hot reload uses chokidar file watchers to monitor discovery directories for SKILL.md file changes. This is a completely separate mechanism from config hot reload.Source:
packages/skills/src/registry/skill-watcher.ts — chokidar-based file watcher with batch debounce.createSkillWatcher
| Field | Type | Description |
|---|---|---|
discoveryPaths | string[] | Directories to watch for skill file changes |
debounceMs | number | Batch debounce interval in milliseconds |
onReload | () => void | Callback fired after debounce window closes |
logger | SkillsLogger? | Optional logger for diagnostic output |
Watched Events
The watcher monitors for three chokidar events on discovery directories:| Event | Trigger |
|---|---|
add | New SKILL.md file created |
change | Existing SKILL.md file modified |
unlink | SKILL.md file deleted |
Batch Debounce
Rapid file changes within thedebounceMs window are coalesced into a single onReload() invocation. This prevents redundant re-discovery when multiple skill files are modified simultaneously (e.g., during a git checkout or bulk file operation).
Late Directory Creation
When discovery paths do not exist yet (e.g., the workspaceskills/ directory has not been created by an agent), the watcher monitors their parent directories for creation:
- For each missing discovery path, walk up to the nearest existing ancestor directory
- Watch those ancestor directories with chokidar at depth 3
- When a missing discovery path appears (detected via
addDirevent):- Close the parent watcher (its job is done)
- Start the real skill file watcher on all now-existing discovery paths
- Trigger re-discovery for newly available paths
Lifecycle
TheSkillWatcherHandle provides a close() method for shutdown cleanup. During graceful shutdown, all skill watchers are closed in the teardown sequence.
Gateway Restart
Thegateway.restart RPC method triggers the same SIGUSR1 mechanism as config hot reload:
SIGUSR1 Lifecycle
When SIGUSR1 is received, the daemon initiates an ordered teardown sequence followed by process exit. The process manager (pm2 or systemd) detects the exit and restarts the daemon with the updated config.Source:
packages/daemon/src/wiring/setup-shutdown.ts — ordered teardown with hard timeout.Teardown Sequence
The shutdown handler stops components in dependency order:- Graph coordinator (cancel running graph pipelines)
- Sub-agent runner (drain active sub-agent runs)
- Lock cleanup timer
- Approval gate (dispose pending timers)
- Skill file watchers (close chokidar instances)
- Cron schedulers (per-agent)
- Session reset schedulers (per-agent)
- Browser services (close Chrome processes)
- Restart continuation tracker (capture active sessions)
- Lifecycle reactors
- Channel manager (stop all channel adapters)
- Heartbeat runner
- Per-agent heartbeat runner
- Wake coalescer
- Media temp manager
- Gateway HTTP/WebSocket server
- Observability modules (diagnostic collector, activity tracker, delivery tracer)
- Background embedding indexing (with 5-second timeout)
- Audit aggregator
- Injection rate limiter
- Secret store database
- Memory database
Hard Timeout
The shutdown handler has a hard timeout of 30 seconds (defaulttimeoutMs). If the teardown sequence does not complete within this window, the process is forcibly terminated. This timeout must be less than the process manager’s stop timeout (e.g., systemd TimeoutStopSec).
Signal Registration
Security Model
Defense-in-depth security architecture
Rate Limiting
Multi-layer rate limiting
Config YAML
Configuration file reference
