Overview
~/.comis/ is the single directory where Comis stores all runtime state: your config, secrets, conversation memory, logs, and the pm2 supervisor file. It is created automatically the first time you run comis init or start the daemon.
The opt-in Prometheus & Grafana surface adds no
file to
~/.comis/ — it opens a loopback network listener (/metrics on
127.0.0.1:9464), not a data-dir path. The Grafana/Prometheus artifacts
(dashboards, rules, docker-compose) live in the Comis source repository, not the
data directory.Directory layout
Some entries only appear under specific conditions.
secrets.db— created only whenSECRETS_MASTER_KEYis set andsecurity.storage: encrypted(the default).auth-profiles.json,mcp-tokens/,secrets.json— only present whensecurity.storage: file. Inencryptedmode, OAuth profiles and secrets live insecrets.dbtables instead..daemon.lock— present only while the daemon is running. Safe to delete when the daemon is stopped (it will be recreated on the next start).daemon.console.log— present only when the daemon was launched by the CLI (thecomis initwizard’s start step orcomis daemon start). Under systemd the process output goes to the journal instead, and under pm2 to~/.pm2/logs/— so this file is absent in those setups.broker.sock— present only when the credential broker is active (executor.broker.enabled: trueinconfig.yaml). Ephemeral — deleted and recreated on each daemon start.cap.sock— present only when an autonomy-bearing agent is configured (the loopback capability endpoint). Ephemeral —0600owner-only, deleted and recreated on each daemon start. The lease bearer itself is never persisted — it is re-minted on each resume from the run’s persisted (attenuated) capabilities, so there is still nocap.db/ lease-secret table on disk. The transient bounded-autonomy control-plane counters (the per-root spawn-ceiling counters, the call-rate windows, and the outward-quota windows) remain in-memory and reset on restart. The in-flight run state need not be ephemeral, though: whenautonomy.durability.enabledis on (on by default — full capability out of the box), the daemon persists each run’s checkpoint and its outward-send ledger tomemory.db(thedurable_runs+outward_send_ledgertables), so a cron-fired pipeline survives a daemon restart — on resume the lease is re-minted from those persisted attenuated caps and the consumed budget is re-seeded from the record. With durability off (an explicit opt-out) the data-dir layout is unchanged by autonomy and those tables are never written. See Durability & Resume.ecosystem.config.js— created only bycomis pm2 setup.support-bundles/— created only after the firstcomis support-bundlerun; each run writes one timestampedcomis-support-<ts>/directory. Absent on a fresh install.workspace/trace-exports/— created only after the firstcomis trace export(or an owner/export-trajectory); each export writes onecomis-trace-<sid8>-<ts>/directory under the agent’s workspace. Absent until then.- Named workspaces like
workspace-myagent/— appear only when you define more than one agent. terminal-drive/— the per-agent durable terminal drive-state journals. A durable drive is the default (drive.durable: true), so this appears whenever an agent runs a terminal drive; absent only for an explicitdrive.durable: false(non-durable) drive or on a tmux-less host.models/whisper/— created only after the first voice transcription that uses the keyless in-process local STT engine (whenintegrations.media.transcription.providerresolves tolocalwithout alocal.baseUrlserver). Absent on a fresh install and whenever local STT never runs.models/tts/— created only after the first voice reply that uses the keyless in-process offline TTS engine (whenintegrations.media.tts.providerresolves tolocal/piper). A separate subdir frommodels/whisper/. Absent on a fresh install and whenever local TTS never runs.skill-provenance.json— created on the first successful skill import. Absent on a fresh install and whenever no skill has ever been imported.skill-index-cache/— created on the firstsource: "wellknown"registry import. Absent on a fresh install and whenever no registry import has run. Safe to delete — a cold cache just re-fetches the index.tmp/— scratch for in-flight skill imports; each import stages under askill-import-<id>/subdirectory that is moved live (or discarded) at the end of the import. Any debris left by a crash mid-import is reconciled and cleaned by a boot-time sweep, so a strayskill-import-*directory here is safe to ignore.
File-by-file reference
config.yaml
Your main configuration file. The daemon reads it at every startup. You edit this directly or through comis config set.
Written by: comis init wizard, or you manually.
Safe to delete? No. Without it the daemon will not start.
config.last-good.yaml
A copy of config.yaml snapshotted after every successful daemon startup. If the daemon crashes on boot, this file lets you roll back.
Written by: the daemon automatically, after logging “Comis daemon started”.
Safe to delete? Yes. The next successful start re-creates it. You lose the ability to roll back until then.
To restore manually:
config.local.yaml
Optional second config file loaded after config.yaml. Values here override the main file. Useful for machine-specific settings you don’t want in version control.
Written by: you, manually.
Safe to delete? Yes. The daemon falls back to config.yaml alone.
.env
Secret environment variables — LLM API keys, channel tokens, SECRETS_MASTER_KEY. The daemon loads this file before anything else, then scrubs the values from process.env so child processes cannot inherit them.
Written by: comis init or you manually.
Safe to delete? Only if you have all credentials stored in secrets.db or injected via environment instead. Deleting it means the daemon will not have access to any keys defined here.
memory.db
The main SQLite database. It stores conversation sessions, long-term memory entries, delivery queue state, observability rows, DAG context engine tables, embedding cache, and more. This is the most important file in the data directory.
The observability rows live in the obs_diagnostics table, grouped by a category label. Among them, model_health is a one-shot diagnostic recorded once per boot that captures the daemon’s load-level recall signals — whether an embedding provider is available, whether the GGUF reranker loaded, and whether the reranker model is present on disk. It carries booleans only (no model paths, no secrets) and lets the fleet health lens explain why recall was degraded on a given daemon without grepping the log.
config_posture is likewise a one-shot snapshot recorded once per boot, capturing the daemon’s security posture — whether the gateway is running without TLS, the per-family counts of stranded secrets (credentials left in the inactive store that the active security.storage mode cannot reach), and a canary-fallback aggregate (how many agents fall back to deterministic canary tokens because CANARY_SECRET is unset). It records counts and closed labels only — never a secret value — so the fleet health lens can surface a TLS-off / stranded-secret / canary posture without opening the log.
Video Job Store — Async Render Lifecycle
memory.db also contains the video_jobs table: the durable record of in-flight and completed video_generate renders (it lives in the shared memory.db, not a separate database file). A video render takes 30 s–5 min — longer than an agent turn and longer than the promptTimeout — so each job is persisted on submit (the opaque provider jobId, the routing — agent, channel, traceId, and the originating session_key — the state pending/done/failed, the estimated/actual cost, and the persisted media_path once delivered) and a background poller drives it to completion off-turn. The table is what the poller resumes against across a daemon restart: on boot it reloads the pending jobs and continues polling, downloads each finished clip before its provider URL expires, persists it to workspace/media/videos/, and delivers it to the originating channel — even though the turn that requested it is long gone. It carries no secret (the jobId is the opaque provider request id; the credential stays with the boot-bound adapter). Reads are agent-scoped, so the video_status tool can only return a job to the agent that created it. See the video_generate and video_status tools.
The session_key column ties a job that completes in the background poller — after the originating turn has ended — back to that turn, so comis explain <sessionKey> can reconstruct the full video turn (provider, model, jobId, estimated + actual cost, outcome) including the off-turn completion. It is captured from the request at submit alongside trace_id.
Durable Runs + Outward-Send Ledger — Durability/Resume
memory.db also contains the durable_runs and outward_send_ledger tables (the durability/resume engine, gated behind agents.<id>.autonomy.durability.enabled — on by default, so an autonomy-bearing agent writes them unless durability is explicitly disabled). durable_runs is the per-root-run checkpoint a long-running autonomy run stamps as its spawn tree advances: the root_run_id, the attenuated capabilities the run was minted with (never a superset), the correlated lease_ids, the spawn tree, the consumed budget, the cron origin, a status (running/orphaned/completed/revoked), a last_heartbeat_at, and a dedicated monotonic outward_step counter. On a daemon restart the resume engine scans the running rows (after channels are up), re-mints each run’s lease from the persisted caps verbatim (never re-attenuating from a live parent — that would broaden authority), re-anchors it under the bounded-autonomy meter, and orphans (never silently re-mints) any run whose capabilities fail the membership parse or whose lease was revoked. A daemon-wide watchdog sweeps runs whose heartbeat has lapsed past durability.staleHeartbeatMs. outward_send_ledger is the exactly-once record of every outward message.send/reply/react: a three-state row (send_attempt_started → unknown_after_send → committed) keyed on a UNIQUE (root_run_id, step_index) idempotency pair, carrying a content-free content_digest (sha256) only — no message body or secret in either table. On restart a crashed-mid-send row is reconciled (the owning channel is asked “was this sent?”) rather than blind-replayed, so a recovered run never double-sends. A revoke (lease.revoke/run.kill) flips the persisted record to revoked, so a subsequent boot can never resurrect pre-revoke capabilities. Both tables are additive and forward-only (CREATE TABLE IF NOT EXISTS), created on every boot.
When autonomy.durability.orchestrateResume is on (default-on, nested inside the durability block), the orchestrate runner becomes the first re-runnable durable kind and its durable_runs row carries two extra nullable pointers: script_ref (the pinned <runId>.<language> script at the run-workspace root) and checkpoint_ref (the last checkpoint() blob — a longer-TTL kind:"json" ResultRef under the run’s results/). Both are content-free path/id pointers — never the script bytes or the checkpoint body. On a timeout of a resume-enabled run the runner re-affirms the row resumable and skips the run-end results/ cleanup so the pinned script + checkpoint survive; orchestrate({resumeRunId}) then re-spawns the pinned bytes (a resume never accepts a re-supplied script). On restart the boot sweep verifies the pinned script + checkpoint are still on disk and surfaces the run as resumable — it does not re-execute the bytes on boot; a missing artifact is an honest durable:orphaned (a closed-enum reason, no free text on the event) and the orphan sweep reclaims the run’s surviving results/ + pinned script. The on-disk layout of those artifacts is under workspace/.
Outcome Events Ledger — Verified Learning
memory.db also contains the outcome_events table: the durable, append-only ledger of each finished trajectory’s net task-outcome (the verified-learning signal). Every row is one raw observation from one signal source (tool / pipeline / correction / judge / reaction / explicit) for one trajectory, carrying the closed-enum outcome (success / failure / corrected / unknown), a confidence, and the (tenant_id, agent_id, session_id, trajectory_id) scope. It is content-free — ids, closed enums, counts, and a numeric confidence only; no message body is ever stored. The (tenant_id, agent_id) columns lead every key/index and the store filters every statement on them, so a row under one (tenant, agent) is never visible to a read under another in the multi-agent database.
The ledger is age-pruned at every daemon startup to the longest agents.<id>.learningOutcome.retentionDays any agent configures (default 30 days) — the prune runs regardless of the per-agent enable flag (anti-DoS: a ledger that grew while a feature was briefly enabled must still be bounded after it is turned off). Rows are idempotent on (tenant_id, agent_id, trajectory_id, source, observed_at) (a deterministic-hash id + a UNIQUE backstop), so a replayed observation is a no-op. The table is additive and forward-only (CREATE TABLE IF NOT EXISTS), created on every boot. Capture is per-agent default-OFF (agents.<id>.learningOutcome.enabled, force-disabled by memory.enabled: false); the resolved outcome is reconstructable per session via comis explain (the learning obs signal) and aggregated via comis memory learning.
Learned Skills — Verified Learning
memory.db also contains the mental_models table: the durable store of admitted learned docs (the Verified Learning loop). Its kind='skill' rows are the learned procedures — each one reusable “how to do X” guidance (a markdown body plus advisory required-tool ids, carrying the kind/topic_key discriminator) distilled from successful trajectories; the schema also forward-provisions kind ∈ (only skill is populated today). Every doc is admitted at trust_level = 'learned' — a closed-enum DB CHECK (trust_level IN ('learned')) makes a synthesized procedure structurally incapable of being system (it can never claim the trust tier reserved for operator-authored content), and the store also coerces the value in code (belt-and-suspenders). The lifecycle state (candidate → active → stale → archived) is proof-count- and outcome-driven: an admitted candidate promotes to active only once it has reinforced past the promoteAtProofCount bar from attributed successful reuse (proof_count bumps on every reuse, but the flip is gated on proof_count + 1 >= promoteAtProofCount — a single success below the bar never mints an active procedure), and an active procedure demotes (active → stale, then → archived via the soft evict) on corroborated, sustained failing reuse (a decay-aware weakening trend gated by failure corroboration, so a single induced failure on a well-reused procedure never archives it). Eviction is soft (an evicted_at timestamp + archived state — the row and its provenance survive, it just stops surfacing: get/list filter evicted_at IS NULL), never a hard delete. As with the outcome ledger, the (tenant_id, agent_id) columns lead every key/index and the store filters every statement on them — a procedure under one (tenant, agent) is never visible under another — and an unresolved scope fails closed. Rows are idempotent on (tenant_id, agent_id, name) (a deterministic-hash id + a UNIQUE backstop), so re-admitting the same procedure is a no-op-plus-refresh that survives a prior eviction. The table is additive and forward-only (CREATE TABLE IF NOT EXISTS, with FTS5/vector/trigram search twins), created on every boot, and is default-dormant: the store exists but nothing admits to it until the per-agent agents.<id>.learning.enabled flag (force-disabled by memory.enabled: false) is turned on, so a default install is byte-identical. When enabled, an active, read-only procedure is surfaced to the agent (default-off): it is materialized to a per-agent workspace .learned-skills/<name>/SKILL.md (a non-discovered sibling of skills/, listed by its absolute path so the read tool opens it directly and skill-use attribution matches that path — the same shape platform skills use, derived wholesale from the store on each refresh, dir mode 0o700 / file mode 0o600, every dynamic path segment safePath-guarded) and listed in <available_skills> (appended after the platform skills so the cached byte-prefix stays stable — see prompt cache). Only active ∧ !mutating ∧ !evicted procedures surface; a candidate, stale, archived, or mutating one is filtered out, so a demoted/archived procedure cannot leave a stale SKILL.md behind. The whole surfacing is default-off (no store / the learning layer disabled → the listing is byte-identical to a platform-only install). A learned doc is an advisory procedure (no executable column — the agent reads it and acts through its own already-permissioned tools): a read-only candidate auto-admits after a static poison/secret scan, while a mutating one is admitted only through the approval gate. The admission funnel is inspectable offline (counts/ids only — never a procedure body) via comis memory skills, and a per-session skill outcome is reconstructable via comis explain (the learning signal). See Learning in config-yaml.
Ranking & Forgetting — Verified Learning
memory.db also carries the outcome-rewarded recall-utility and wrongness-based forgetting state (the Verified Learning ranking-reward + soft-eviction). Both are part of the one learning layer — per-agent default-OFF, gated by agents.<id>.learning.enabled and force-disabled by memory.enabled: false — so with the default config the columns exist but no learning writes them and recall is byte-identical. Recall ranking itself is the fixed rag.scoring fusion — there is no learned recall weight. See Learning in config-yaml.
memory_usefulness— the per-memory recall-utility counters (used_count/ignored_count) plus afailure_countcolumn: an outcome-attributed task failure for a memory recalled into afailure/correctedtrajectory. It is the outcome-reward write gated bylearning.enabled(recordUsageon success,recordFailureon failure/corrected) and thefailure_countsource that forgetting reads (thelearning.forget.failureEvictionFloorconsumer). It is distinct fromignored_count— a correct-but-uncited memory accruesignored_count(recalled, not used), neverfailure_count; only a memory implicated in a failed outcome accrues here, and only after the daemon’s anti-induced-eviction corroboration gate (≥2 independent failures, or one deterministic tool/pipeline source). The(tenant_id, agent_id)columns lead every key.evicted_at(on thememoriesrows) — soft, reversible eviction. When forgetting is enabled, the lifecycle sweep marks a low-strength memory (its strength lowered by the boundedfailurePenalty×failure_countcoupling) by settingevicted_at— it is never hard-deleted. An evicted memory is excluded from every recall path (an always-appliedevicted_at IS NULLfilter) yet stays resolvable viaasOf/ inspect / audit reads, and can be un-evicted (cleared back toNULL) on renewed usefulness. High-corroboration memories are exempt and never soft-evicted regardless of strength:pinned,trust_level='system', or a highproof_count. There is nohalfLifeDaysknob (forgetting reuses the existing score-decay; the learning layer adds eviction + wrongness, not a new decay knob).
Per-user profile — a Mental Model store document
The per-user profile is akind:"profile" document in the mental_models store (the same store the learned-skill surface reads), maintained by the one outcome-gated reflection cron — there is no standalone profile table. A profile correction supersedes the prior doc with a non-destructive history-append (the prior body is preserved in the doc’s history column, never hard-deleted), and external-trust sources are excluded by the engine’s two-axis anti-poison SELECT.
LCD Store — Durable Conversation History
memory.db also contains the LCD store: the durable, lossless conversation history. Three layers work together, each with a distinct durability contract:
The SDK session JSONL is disposable. Deleting it directly (for any reason — operator housekeeping,
rm, daemon restart) is always treated as “continue”: the LCD store keeps its full history and appends the fresh transcript as a continuation. The daemon cannot distinguish disk-space housekeeping from a deliberate reset, so it never infers forget from a file deletion — it always continues. To perform an explicit reset, use comis sessions reset which clears both layers atomically.sessions reset clears two layers atomically: the LCD lossless-store history (dag mode) and the daemon sessionStore working transcript (both modes). A follow-up turn has no prior context regardless of which context engine the session uses.
See CLI Reference for the full command reference.
Written by: the daemon continuously during operation.
Safe to delete? Deleting it erases all conversation history, stored memories, pending deliveries, and cached embeddings. The daemon will recreate an empty database on next start, but you lose all data permanently.
secrets.db
An encrypted SQLite database for secrets managed with comis secrets set. Each value is encrypted with AES-256-GCM using the key in SECRETS_MASTER_KEY. Only created when that variable is set.
Written by: comis secrets set and the daemon on startup.
Safe to delete? Only if you have not stored any secrets here, or you have them backed up elsewhere. Deleting it with a valid master key means those secrets are gone.
ecosystem.config.js
The pm2 process definition, generated by comis pm2 setup. It tells pm2 where the daemon binary lives, what environment to pass (COMIS_CONFIG_PATHS), and restart behavior.
Written by: comis pm2 setup, one time.
Safe to delete? Yes, if you re-run comis pm2 setup afterward. pm2 will not be able to start the daemon until you do.
logs/daemon.log
The daemon’s own structured JSON log, written via pino-roll. The live file always carries a numeric index — daemon.1.log on a fresh boot, advancing to daemon.2.log, … when the current file reaches 10 MB (configurable via daemon.logging.maxSize and daemon.logging.maxFiles); pino-roll never writes the bare daemon.log name, so a daemon.log you see in the directory is a stale artifact, not the live log. The newest daemon.N.log is the file to tail; aged siblings are gzipped to daemon.N.log.gz by the startup rotation sweep (the sweep never touches the newest uncompressed indexed file). This is the canonical application log — leveled lines with traceId, agentId, durationMs, etc. — and the file you parse to diagnose behaviour. The base path is set by daemon.logging.filePath (default ~/.comis/logs/daemon.log).
Written by: the daemon continuously.
Safe to delete? Yes. The daemon creates a new file on next start. You lose historical log data. Under pm2, logs are also captured to ~/.pm2/logs/ as a separate copy.
logs/security-audit.jsonl
The durable security-decision audit trail — one scrubbed JSON line per audit event (secret access, injection detection, command blocks, canary leaks, sandbox-downgrade refusals, and classified audit:event actions). Created with 0600 permissions and rotated under observability.logRotation (size + keep-count). Content-free by construction: ids, the closed-enum fields, and a scrubbed refs blob — never a secret value. It is the greppable companion to the durable obs_audit_events SQLite table that comis security audit-log / obs.audit.query read; both survive a daemon restart. See Audit Logging.
Written by: the daemon, on each audit event (when observability.audit.persist is on, the default).
Safe to delete? Yes, but you lose the on-disk audit history (the structured daemon.N.log .audit() lines and any log-aggregation copy remain). The SQLite rows are unaffected.
daemon.console.log
The raw stdout/stderr of the detached daemon process, captured by the CLI that launched it. Distinct from logs/daemon.log: it is not structured and not written by the daemon’s logging subsystem — it lives at the data-dir root alongside daemon.pid/.daemon.lock as a process-lifecycle artifact. Its value is capturing output that the structured logger never sees: early-boot progress (e.g. embedding-model download), native-library warnings, and FATAL: Bootstrap failed crashes that exit before the logger is configured. comis daemon logs tails this file (override with COMIS_LOG_PATH).
Written by: the comis init wizard’s “start daemon” step and comis daemon start. Absent under systemd (output → journal) and pm2 (output → ~/.pm2/logs/).
Safe to delete? Yes. Recreated on the next CLI launch.
traces/
Per-session JSONL execution traces, rotated at 5 MB per file (3 files kept by default). Controlled by daemon.logging.tracing. Each agent can override the output directory in its own config block.
Written by: the daemon on each LLM call (when tracing is enabled).
Safe to delete? Yes. The daemon creates new trace files as needed.
support-bundles/
Output of comis support-bundle. Each run writes one timestamped comis-support-<ts>/ directory containing issue-summary.md, ai-issue-draft.md, triage.json, doctor.json, and manifest.json, plus — when their source can be read — fleet.json, config-posture.json, and an audit-summary.json window digest (a thrown fleet read, an unparseable config, or an absent observability store omits the corresponding file). Focusing the bundle on one session adds explain.json (with --session) and, with --deep, a trace-exports/ subdirectory holding that session’s redacted trace bundle — so a run writes up to nine files plus the optional trace-exports/ directory. It is a content-free, paste-ready diagnostic bundle assembled offline. The directory is created with mode 0o700 and every file with 0o600; the name carries a timestamp only (no hostname). The bundle excludes secrets, message bodies, and raw config values by construction.
Written by: comis support-bundle, on each run.
Safe to delete? Yes. Bundles are point-in-time diagnostic artifacts — delete them once you have shared or triaged them (the privacy guidance is to delete after triage anyway).
workspace/ and workspace-<agentId>/
Each agent gets a dedicated workspace directory. The default agent uses workspace/; additional named agents get workspace-<agentId>/. Inside each workspace:
- Agent identity files (
AGENTS.md,SOUL.md,USER.md,IDENTITY.md) - A
.scheduler/subdirectory withcron-jobs.jsonand execution logs - A
skills/subdirectory for agent-specific prompt skills - A
projects/subdirectory — the persistent working root for terminal-driver sessions (present only if the agent uses that tool). Each named coding project gets its own auto-created folderprojects/<name>, so a driven session’s work (e.g. a Claude Code build) lands atworkspace/projects/<name>and survives the session. Thisprojects/subtree is the only part of~/.comisre-exposed read-write inside the session’s bwrap jail; the agent’s siblingsessions/,skills/,memory.db, and secrets stay masked. - Other subdirectories seeded by Comis:
scripts/,documents/,media/,data/,output/
media/ subdirectory is scratch space for agent file operations and the durable home for generated images. The image_generate tool persists each generated image to media/photos/ inside the agent’s workspace (~/.comis/workspace/media/photos/ for the default agent) via the media-persistence service — a UUID filename with a MIME-detected extension, bounded by the media-persistence size cap. The persisted file is then delivered to the channel via the adapter’s sendAttachment from that durable path. When the channel cannot receive an attachment — an adapter without sendAttachment (currently only IRC) — or no channel is present, the handler returns a bounded base64 image over the RPC response instead. The persisted file is not deleted after delivery, so generated images survive for later inspection (see the image_generate tool).
The video_generate tool persists generated videos to media/videos/ inside the agent’s workspace (~/.comis/workspace/media/videos/ for the default agent) the same way — a UUID filename via the media-persistence service, with a raised size cap appropriate for video. Because a video render outlives the turn, this persist-and-deliver step runs in the background poller (not the originating turn): when the render completes, the poller downloads the clip to a buffer and persists it before any delivery decision (an expiring provider URL cannot orphan it), then delivers it as a video attachment via sendAttachment from that durable path; a channel without attachment support (currently only IRC) degrades to a notice plus the persisted path. Persisted videos are not deleted after delivery (see the video_generate tool). The in-flight/completed render jobs themselves are tracked in the video_jobs table in memory.db — that is what the poller resumes against across a daemon restart.
Orchestrate resumable-run artifacts
Anorchestrate run uses the agent workspace directly: the runner writes the model’s script to the workspace root as <runId>.<language> and materializes every intermediate result under results/ as a size-capped, TTL’d ResultRef (sliced in-jail, so only the queried slice re-enters context — the full payload never does). Two of those artifacts become durable when autonomy.durability.orchestrateResume is on:
- a
checkpoint(stateJson)blob is a longer-TTLkind:"json"ResultRefunderresults/(the extended TTL outlasts a full run so a normal run-length GC never evicts it);resume()reads the last one back wrapped as untrusted data (never evaluated as control); - each side-effecting cap-socket call appends one content-free line to the run-scoped
results/replay.jsonl—{ seq, method, paramsDigest (sha256), result: <ResultRef pointer> }, digests + on-disk pointers only, never a raw param, body, or bearer.
results/ wipe, so the pinned <runId>.<language> script, the checkpoint, and replay.jsonl all survive for a later resume or replay; the orphan sweep reclaims them once the run is orphaned or its checkpoint TTL-expires. Everything here is default-off — with orchestrateResume off, an orchestrate run cleans its results/ at run end exactly as before. comis orchestrate replay <runId> re-runs the pinned bytes against a separate, operator-only replay socket that serves those recorded results back → byte-identical stdout (see comis orchestrate replay).
Written by: the daemon on first startup for each agent.
Safe to delete? Deleting a workspace erases the agent’s identity files, cron job definitions, and skill configurations. The daemon recreates the directory structure but not the content you or the agent wrote there.
workspace/trace-exports/
Output of comis trace export and the owner /export-trajectory slash command. Each export writes one comis-trace-<sid8>-<ts>/ directory under the agent’s workspace (workspace/trace-exports/ for the default agent, workspace-<agentId>/trace-exports/ for a named one), holding the redacted per-session incident bundle. The directory is created with mode 0o700 and every file with 0o600.
Written by: comis trace export and the /export-trajectory owner command.
Safe to delete? Yes. Exports are point-in-time diagnostic artifacts; delete them after handing off to an engineer.
terminal-drive/
Durable drive-state journals for the terminal-driver, written only when an agent runs a durable drive (drive.durable: true). Each journal lives at terminal-drive/<agentId>/journals/<sessionId>.json — one small JSON file per driven session, beside (but distinct from) the agent’s persistent projects/ working directory under workspace/.
The journal is what lets a long autonomous drive survive a daemon restart: a durable drive launches the driven CLI inside a detached tmux server that outlives the daemon, and the journal records the compact drive state — the objective, the last screen classification, the prompts already answered, and the steps tried — so on restart the driver re-attaches to the live tmux session and resumes instead of starting over or re-answering prompts. A genuinely-gone session is reported as failed with its journal preserved.
The journal is content-free and secret-redacted (no captured terminal output, no message bodies, no credentials — secrets are scrubbed before anything is recorded), written atomically with mode 0o600 inside a 0o700 per-agent directory. It is reaped at the session’s end of life.
Written by: the daemon, on each update to a durable drive’s state (and recovered on the next startup).
Safe to delete? Yes, when the daemon is stopped. Deleting a journal means an in-flight durable drive cannot resume after the next restart — the driver treats the session as gone rather than resuming it. Non-durable drives (the explicit drive.durable: false opt-out) never write here.
models/whisper/
The on-disk cache for the keyless in-process local STT engine (whisper, run via Transformers.js / ONNX Runtime). When voice transcription resolves to the local provider without a local.baseUrl server, the engine lazy-downloads a small model (integrations.media.transcription.local.model, default base) once over TLS from the model hub on the first voice note, writes it here, and reuses it for the rest of the process. The directory lives under the data dir, so it is already inside the daemon’s --allow-fs-write=${COMIS_DATA_DIR} scope — no extra permission grant is required (see Voice and Node Permissions).
It holds model weights only — no secret, no credential, nothing user-specific.
Written by: the local STT adapter, on the first transcription that uses the in-process engine.
Safe to delete? Yes. It re-downloads on the next local transcription; deleting it only forces one slower first call afterward. Never created (or needed) when you use a local.baseUrl server or a keyed cloud provider instead.
models/tts/
The on-disk cache for the keyless in-process offline TTS engine (local/piper, a single-speaker text-to-audio model run via Transformers.js / ONNX Runtime — the TTS twin of models/whisper/). When a voice reply resolves to the local/piper TTS provider, the engine lazy-downloads a small voice model once over TLS from the model hub, writes it here (a separate subdir from models/whisper/), and reuses it for the rest of the process — so synthesis needs no network thereafter. Already inside the daemon’s --allow-fs-write=${COMIS_DATA_DIR} scope — no extra permission grant (see Voice → Text-to-Speech).
It holds model weights only — no secret, no credential, nothing user-specific.
Written by: the local TTS adapter, on the first reply that uses the in-process offline engine.
Safe to delete? Yes. It re-downloads on the next local synthesis; deleting it only forces one slower first call. Never created (or needed) when TTS uses edge or a keyed cloud provider.
Voice observability adds no new on-disk artifact: a transcription/synthesis records onto the existing per-session trajectory (the same *.trajectory.jsonl that carries every other turn event), so comis explain reconstructs the voice turn and comis fleet surfaces the voice_health signal without a dedicated voice file (see Voice → Observability and cost).
agents/
Holds per-agent RPC state such as subagent result files written during multi-agent graph execution.
Written by: the daemon during subagent runs.
Safe to delete? Yes, between runs. Active runs may be disrupted.
background-tasks/
Persisted state for long-running background tasks promoted out of the LLM execution loop. Each task is a small JSON file.
Written by: the daemon when background tasks are created or updated.
Safe to delete? Yes, when no tasks are running. The daemon will lose track of any in-progress background work.
skill-provenance.json
The durable, daemon-private record of every imported skill. Because imported skills share the same skills/ directory that bundled (platform-trusted) skills seed into, this store is the only durable discriminator between an imported skill and a seeded one. Each record is keyed <scope>:<agentId|shared>:<name> and holds the acquisition source, a content hash over the installed files, the scan verdict, relative file paths, and timestamps.
It is content-free by construction — ids, hashes, counts, and relative paths only, never a skill body — and stored with mode 0o600. The read is fail-safe: a missing or corrupt file never throws and never blocks boot. A record is advisory downward only: its presence marks a skill imported, but its absence never elevates a skill’s trust tier.
Written by: the daemon on each successful import, and updated when an imported skill is deleted (record removed) or edited (hash re-pinned).
Safe to delete? Deleting it strips the imported stamp from every previously imported skill — they revert to a path-derived source and lose their provenance summary. The skill files themselves remain. Provenance is forward-only, so there is no backfill; to restore the stamp, delete and re-import the affected skills. Do not delete while the daemon is running.
skill-index-cache/
A bounded, daemon-private cache of well-known registry index metadata, created on the first source: "wellknown" import. One JSON file per registry origin holds only that index’s skill names and their advertised relative paths — never file bodies. A repeat import within the fixed 15-minute TTL can skip re-fetching the registry’s index.json; the advertised files themselves are always re-fetched, re-scanned, and re-hash-pinned, so a cache hit only ever saves the index round-trip and can never substitute content. The cache is bounded by entry count (the oldest origins are evicted past the cap), and an expired, missing, or shape-invalid file is treated as a miss rather than trusted. The directory is mode 0o700 and each file 0o600.
Written by: the daemon on a source: "wellknown" registry import.
Safe to delete? Yes. It is a pure performance cache holding no bodies and no secrets; deleting it only makes the next registry import re-fetch the index.
tmp/
Scratch space for the staged skill-import pipeline. Each in-flight import unpacks, maps, and scans under a tmp/skill-import-<id>/ subdirectory; only after the fail-closed scan passes are the kept text files moved into the live skills/ directory. Nothing untrusted is ever written directly into skills/. A commit.json intent marker inside the staging directory records the pending move so a crash mid-import can be reconciled.
Written by: the daemon during a skill import.
Safe to delete? Yes, when no import is in progress. A boot-time sweep reconciles and removes crash debris automatically (a half-committed import is either completed or rolled back, never left orphaned). Deleting the directory of an import that is actively running would break that import.
identity/device.json
An Ed25519 key pair that uniquely identifies this Comis installation. Generated on first startup. Used for device-level signing. Stored with mode 0o600.
Written by: the daemon on first startup (if it does not exist).
Safe to delete? Technically yes, but the device will get a new identity on next start. Any device-pairing records that reference the old key will become invalid.
devices/
Paired-device records, written when another Comis installation pairs with this one. Each file is a small JSON record signed against the device identity above.
Written by: the daemon on pairing.
Safe to delete? Yes. The pairing is invalidated and the other side will need to re-pair.
~/.comis-chanlive/<channel>.json (test-harness only, outside ~/.comis/)
A per-channel handle file written by the channel-emulation harness — the contributor test framework that drives Comis through the real channel adapter against an isolated, throwaway daemon. It lives in its own directory, ~/.comis-chanlive/ (a sibling of ~/.comis/, not inside it), and is created the first time you run the harness rig (tg up or a startRig scenario). It is not part of a normal install — a daemon you run for real never writes it. The directory can be relocated with the COMIS_CHANLIVE_DIR env var (the harness points cross-process tg invocations at one throwaway dir this way).
The handle records the rig’s three endpoints (the emulator /control/* base, the rig-control base, and the gateway /rpc + /health URL), the admin-scoped gateway token, the fixed test chat id (424242), and the rig’s throwaway COMIS_DATA_DIR / memory.db path — so the chan/tg CLI can discover-or-spawn a running rig. For a detached rig (tg up --detached, the cold-shell path) it also records the rig subprocess’s pid and a real rig-control HTTP endpoint, so a separate-shell tg down / tg restart can drive (or reap) the surviving rig.
Written by: the channel-emulation harness rig (test/live/), on tg up / startRig.
Safe to delete? Yes, when no harness rig is running. The next tg up re-creates it.
dead-letters.jsonl
Append-only JSONL of announcement-delivery failures that exhausted in-process retries (channel down, bot blocked, etc.). The delivery system replays these on provider recovery and expires entries after one hour. Only created when at least one announcement has been dead-lettered.
Written by: the announcement delivery subsystem.
Safe to delete? Yes. Pending re-deliveries are dropped; entries auto-expire after one hour anyway.
.daemon.lock
The daemon acquires this exclusive lock (O_EXCL) at startup to prevent two daemon processes from running against the same data directory simultaneously. If the file is present when the daemon starts, it attempts stale-lock recovery (opens the file, checks whether the recorded PID is still running, and removes the stale lock if not).
Written by: the daemon at startup.
Safe to delete? Yes — but ONLY when you are certain the daemon is stopped. Deleting this file while the daemon is running will break the lock invariant and may allow a second daemon instance to start.
To check if the lock is stale:
auth-profiles.json (file mode only)
Stores OAuth profiles (access and refresh tokens) when security.storage: file is configured. Each profile is a JSON entry identified by provider and email. The file is written atomically (tmp then rename) with mode 0o600.
Written by: comis auth login and the daemon’s OAuth refresh path.
Safe to delete? Deleting this file removes all stored OAuth credentials — you will need to re-run comis auth login for each provider. Do not delete while the daemon is running.
Not present when: security.storage: encrypted (the default) — OAuth profiles are stored in the oauth_profiles table inside secrets.db instead.
mcp-tokens/ (file mode only)
A directory holding per-MCP-server OAuth token JSON files when security.storage: file is configured. Each file is named after the MCP server identifier. These are the access tokens issued during comis mcp login.
Written by: comis mcp login and the daemon’s MCP OAuth refresh path.
Safe to delete? Deleting the directory (or individual files) clears stored MCP OAuth tokens for those servers — you will need to re-run comis mcp login. Do not delete while the daemon is running.
Not present when: security.storage: encrypted — MCP tokens are stored in the mcp_credentials table inside secrets.db instead. Also not present in security.storage: env mode, where MCP OAuth login is unavailable (there is no writable MCP token store — comis mcp login fails with an actionable storage-mode error rather than writing a plaintext file).
secrets.json (file mode only)
The plaintext secret store when security.storage: file is configured. Contains named secret key-value pairs (for example, ANTHROPIC_API_KEY). Mode 0o600. Written atomically.
Written by: comis secrets set and the daemon’s secrets RPC path.
Safe to delete? Deleting this file removes all stored named secrets — every agent that depends on those secrets will fail to make API calls until the secrets are re-added. Do not delete while the daemon is running.
Not present when: security.storage: encrypted (the default) — named secrets are stored in the secrets table inside secrets.db instead.
broker.sock
A Unix domain socket created by the credential broker at daemon startup when the broker is active. Agents connect to this socket to get ephemeral single-use bearer tokens injected into their outbound HTTP requests, so the raw API key never enters the agent namespace.
Written by: the daemon at startup (when executor.broker is configured).
Safe to delete? The socket is ephemeral — deleted and recreated on each daemon start. If you see a stale broker.sock after a crash, you can delete it safely before restarting.
Not present when: executor.broker is not configured or the broker is disabled.
cap.sock
A Unix domain socket (0600, owner-only) created for the loopback capability endpoint when an autonomy-bearing agent is configured. The jailed script surface dials this socket, presents its COMIS_CAP_LEASE bearer, and the endpoint validates the lease (timing-safe, not-expired, not-revoked, audience-bound) and dispatches through the same handler gates as every other origin. The lease the endpoint validates is held in memory (run-scoped) — there is no lease table on disk, so this socket is the only on-disk artifact of the capability layer.
Written by: the daemon at startup (when an agent resolves to an autonomy-bearing profile).
Safe to delete? The socket is ephemeral — 0600, deleted and recreated on each daemon start. A stale cap.sock after a crash is safe to delete before restarting.
Not present when: no agent resolves to an autonomy-bearing profile.
Backing up
To migrate to a new machine, back up these items:config.last-good.yaml, logs/, traces/, and ecosystem.config.js are machine-specific or regenerated and do not need to be included.
If you use
secrets.db, make sure you also have your SECRETS_MASTER_KEY stored somewhere safe. Without it the database is unreadable, even with the file itself.Custom location
By default Comis resolves config from~/.comis/config.yaml and ~/.comis/config.local.yaml. You can override this with COMIS_CONFIG_PATHS, a colon-separated list of absolute paths:
~/.comis/) is separately controlled by COMIS_DATA_DIR. See Configuration for a full reference.
