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

# Data Directory

> A complete reference for everything Comis keeps in ~/.comis/ — what each file is, when it's written, and what's safe to delete.

Here's everything Comis keeps on disk — and why.

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

<Note>
  The opt-in [Prometheus & Grafana](/operations/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.
</Note>

## Directory layout

```
~/.comis/
├── config.yaml              # your main configuration file
├── config.last-good.yaml    # last successful startup snapshot
├── config.local.yaml        # optional local overrides (not committed)
├── .env                     # secret overrides (LLM keys, tokens)
├── memory.db                # SQLite — conversations, memory, sessions
├── secrets.db               # SQLite — AES-256-GCM encrypted secrets (encrypted mode)
├── auth-profiles.json       # OAuth profiles — file mode only (0o600)
├── mcp-tokens/              # MCP OAuth tokens — file mode only (per-server JSON)
│   └── <server-name>.json
├── secrets.json             # secret store — file mode only (0o600)
├── .daemon.lock             # exclusive lock held while daemon is running
├── daemon.console.log       # raw daemon stdout/stderr capture (CLI launcher only)
├── broker.sock              # Unix socket for credential broker (when active)
├── cap.sock                 # Unix socket for the capability endpoint (0600; when an autonomy-bearing agent is configured)
├── ecosystem.config.js      # pm2 process definition (created by pm2 setup)
├── logs/
│   ├── daemon.N.log         # structured JSON log (pino-roll; live file = newest N)
│   └── security-audit.jsonl # durable security-decision audit trail (0600, rotated; one scrubbed JSON line per event)
├── traces/                  # per-session JSONL execution traces
├── support-bundles/         # comis support-bundle output — one comis-support-<ts>/ dir per run
├── workspace/               # default agent workspace (files, git, skills, projects/)
│   └── trace-exports/       # comis trace export output — one comis-trace-<sid8>-<ts>/ dir per export
├── workspace-<agentId>/     # named-agent workspaces (one per extra agent)
├── terminal-drive/          # durable terminal-driver state (default; absent only with drive.durable: false)
│   └── <agentId>/journals/<sessionId>.json
├── models/                  # auto-downloaded ML model caches
│   └── whisper/             # local STT whisper model (downloaded once on first use, reused)
├── agents/                  # per-agent RPC state (subagent results, etc.)
├── background-tasks/        # background task state files
├── skills/                  # seeded skill library — bundled claude-code/codex/skill-creator skills (re-seeded on version change) + any added skills
│   └── <name>/SKILL.md      # one dir per skill (the agent auto-discovers these by description)
├── skill-provenance.json    # import provenance store — file mode 0o600, daemon-private (one record per imported skill)
├── skill-index-cache/       # bounded 15-min-TTL cache of well-known registry index metadata (names + rel-paths only, never bodies)
├── tmp/                     # scratch for staged skill imports — one skill-import-<id>/ per in-flight import (swept at boot)
├── identity/
│   └── device.json          # Ed25519 device identity (auto-generated)
└── devices/                 # paired device records (if pairing is used)
```

<Note>
  Some entries only appear under specific conditions.

  * `secrets.db` — created only when `SECRETS_MASTER_KEY` is set and `security.storage: encrypted` (the default).
  * `auth-profiles.json`, `mcp-tokens/`, `secrets.json` — only present when `security.storage: file`. In `encrypted` mode, OAuth profiles and secrets live in `secrets.db` tables 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 (the `comis init` wizard's start step or `comis 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: true` in `config.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 — `0600` owner-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 no `cap.db` / lease-secret table on disk. The transient [bounded-autonomy](/agents/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: when [`autonomy.durability.enabled`](/reference/config-yaml#autonomy-agentsautonomy) is on (**on by default** — full capability out of the box), the daemon persists each run's checkpoint and its outward-send ledger to `memory.db` (the [`durable_runs` + `outward_send_ledger`](#memory-db) tables), 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](/agents/autonomy#durability-resume).
  * `ecosystem.config.js` — created only by `comis pm2 setup`.
  * `support-bundles/` — created only after the **first** `comis support-bundle` run; each run writes one timestamped `comis-support-<ts>/` directory. Absent on a fresh install.
  * `workspace/trace-exports/` — created only after the **first** `comis trace export` (or an owner `/export-trajectory`); each export writes one `comis-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 explicit `drive.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 (when `integrations.media.transcription.provider` resolves to `local` without a `local.baseUrl` server). 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 (when `integrations.media.tts.provider` resolves to `local`/`piper`). A separate subdir from `models/whisper/`. Absent on a fresh install and whenever local TTS never runs.
  * `skill-provenance.json` — created on the **first** successful skill [import](/skills/importing). Absent on a fresh install and whenever no skill has ever been imported.
  * `skill-index-cache/` — created on the **first** `source: "wellknown"` registry [import](/skills/importing#registry-import-well-known-index). 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 a `skill-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 stray `skill-import-*` directory here is safe to ignore.
</Note>

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

```bash theme={}
cp ~/.comis/config.last-good.yaml ~/.comis/config.yaml
```

***

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

<Warning>
  Never commit `.env` to version control. It contains unencrypted credentials. Comis sets its file mode to `0o600` on startup.
</Warning>

***

### `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](/agent-tools/media).

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`](/reference/config-yaml#autonomy-agentsautonomy) — **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`](/reference/config-yaml#autonomy-agentsautonomy) 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/`](#orchestrate-resumable-run-artifacts).

#### 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` ∈ {`skill`,`profile`,`topic`} (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](/agents/cache#learned-skill-listing-stability)). 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`](/reference/cli#comis-memory), and a per-session skill outcome is reconstructable via [`comis explain`](/reference/cli#comis-explain) (the `learning` signal). See [Learning in config-yaml](/reference/config-yaml#learning-agents-learning).

#### 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](/reference/config-yaml#learning-agents-learning).

* **`memory_usefulness`** — the per-memory recall-utility counters (`used_count` / `ignored_count`) plus a **`failure_count`** column: an outcome-attributed task **failure** for a memory recalled into a `failure`/`corrected` trajectory. It is the **outcome-reward write** gated by `learning.enabled` (`recordUsage` on success, `recordFailure` on failure/corrected) and the **`failure_count` source** that forgetting reads (the `learning.forget.failureEvictionFloor` consumer). It is **distinct from `ignored_count`** — a correct-but-uncited memory accrues `ignored_count` (recalled, not used), never `failure_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 the `memories` rows) — **soft, reversible eviction.** When forgetting is enabled, the lifecycle sweep marks a low-strength memory (its strength lowered by the bounded `failurePenalty` × `failure_count` coupling) by **setting `evicted_at`** — it is **never hard-deleted**. An evicted memory is **excluded from every recall path** (an always-applied `evicted_at IS NULL` filter) yet stays **resolvable via `asOf` / inspect / audit** reads, and can be **un-evicted** (cleared back to `NULL`) on renewed usefulness. High-corroboration memories are **exempt** and never soft-evicted regardless of strength: `pinned`, `trust_level='system'`, or a high `proof_count`. There is **no `halfLifeDays` knob** (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 a `kind:"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:

| Layer                 | Tables                                                                    | Durability                                                                                                                                                                                                                                                                    |
| --------------------- | ------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **LCD store**         | `lcd_messages`, `lcd_message_parts`, `lcd_context_items`, `lcd_summaries` | **Durable — the truth.** Every turn is persisted verbatim (auto-condensed past 75%). Survives daemon restarts, redeploys, and JSONL deletions.                                                                                                                                |
| **RAG memory**        | `memory_*`                                                                | **Durable.** Distilled facts learned from conversations. Cleared per-session via `comis sessions reset --memory` (deletes paired + LCD-distilled memories by `source_session_key` and unlinks consolidated observations; `--purge-derived` also purges derived observations). |
| **SDK session JSONL** | `~/.comis/workspace/sessions/.../<key>.jsonl`                             | **Disposable** — the SDK's per-session working transcript. Routinely deleted by the operator (housekeeping) and wiped by `comis sessions reset`.                                                                                                                              |

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

**Bootstrap crash recovery.** Each turn's messages are written to the SDK session JSONL first and persisted to the durable LCD store at the end of the turn. If the daemon is killed mid-turn — after the JSONL write but before that end-of-turn persist — those last messages would otherwise never reach the durable store. To close that gap, on **session start** the daemon runs a crash-recovery sweep: it continue-appends any live-transcript messages that the previous turn did not persist, so the next turn sees the complete history. The sweep is **exactly once** — a durable per-conversation cursor records how far the store has caught up, so the message recovered at startup is never re-appended by the turn that follows (no gap, no duplicate, no sequence collision). It is a one-way catch-up: the LCD store is the source of truth and the disposable JSONL is only read, never used to overwrite stored history. If the transcript's identity is ambiguous (a malformed or mismatched session scope), the sweep **refuses the import** rather than risk re-attaching a transcript to the wrong conversation (fail-closed). There is no configuration key — the sweep always runs in dag mode.

**Forgetting is explicit.** To clear a conversation so the model starts with a clean slate in **both dag and pipeline modes**, use the complete reset command:

```bash theme={}
# Reset a conversation (clears LCD history + session transcript — irreversible):
comis sessions reset <sessionKey> --yes
# Also clear RAG memories for this session (the full-forget path):
comis sessions reset <sessionKey> --memory --yes
# Nuclear forget — also purge consolidated observations derived from this session:
comis sessions reset <sessionKey> --memory --purge-derived --yes
```

`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](/reference/cli#comis-sessions) 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.

<Tip>
  Under pm2, Comis skips the stdout transport to avoid duplicating entries. The canonical structured log lives under `~/.comis/logs/` — the newest `daemon.N.log` is the active file.
</Tip>

***

### `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](/security/audit).

**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`](/reference/cli#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 with `cron-jobs.json` and execution logs
* A `skills/` subdirectory for agent-specific prompt skills
* A `projects/` subdirectory — the persistent working root for [terminal-driver](/agent-tools/terminal-driver) sessions (present only if the agent uses that tool). Each named coding project gets its own auto-created folder `projects/<name>`, so a driven session's work (e.g. a Claude Code build) lands at `workspace/projects/<name>` and survives the session. This `projects/` subtree is the only part of `~/.comis` re-exposed read-write inside the session's bwrap jail; the agent's sibling `sessions/`, `skills/`, `memory.db`, and secrets stay masked.
* Other subdirectories seeded by Comis: `scripts/`, `documents/`, `media/`, `data/`, `output/`

The seeded `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](/agent-tools/media)).

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](/agent-tools/media)). The in-flight/completed render jobs themselves are tracked in the `video_jobs` table in [`memory.db`](#memory-db) — that is what the poller resumes against across a daemon restart.

#### Orchestrate resumable-run artifacts

An [`orchestrate`](/agent-tools/orchestrate) 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`](/reference/config-yaml#autonomy-agentsautonomy) is on:

* a `checkpoint(stateJson)` blob is a longer-TTL `kind:"json"` `ResultRef` under `results/` (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.

A resumable **timeout** skips the run-end `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`](/reference/cli#comis-orchestrate)).

**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`](/operations/incident-bundle) 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](/agent-tools/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](/media/voice#transcription-speech-to-text) and [Node Permissions](/reference/node-permissions#keyless-local-stt-whisper-engine)).

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](/media/voice#text-to-speech-tts)).

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](/media/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](/skills/importing) 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](/skills/importing#registry-import-well-known-index). 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](/developer-guide/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.

<Warning>
  Because it carries the gateway token, the handle is written with mode `0o600` (owner-only) so the admin token cannot leak off-box. The token is a real admin-scoped credential for the throwaway rig's loopback gateway — treat the file like any other secret-bearing file. If you see a stale `~/.comis-chanlive/` after a harness run, it is safe to delete when no rig is running.
</Warning>

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

```bash theme={}
cat ~/.comis/.daemon.lock    # shows the PID that holds the lock
ps -p <PID>                  # check if that PID is running
```

***

### `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`](/reference/environment-variables#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:

```bash theme={}
tar -czf comis-backup.tar.gz \
  ~/.comis/config.yaml \
  ~/.comis/.env \
  ~/.comis/memory.db \
  ~/.comis/secrets.db \
  ~/.comis/workspace
```

`config.last-good.yaml`, `logs/`, `traces/`, and `ecosystem.config.js` are machine-specific or regenerated and do not need to be included.

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

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

```bash theme={}
COMIS_CONFIG_PATHS="/etc/comis/config.yaml:/home/me/.comis/config.yaml" node packages/daemon/dist/daemon.js
```

The data directory (`~/.comis/`) is separately controlled by `COMIS_DATA_DIR`. See [Configuration](/installation/configuration) for a full reference.

## Permissions

Comis enforces strict file modes on startup. If any of the following are wrong, the daemon corrects them automatically and logs what it changed:

| Path                    | Required mode                |
| ----------------------- | ---------------------------- |
| `~/.comis/` (directory) | `0o700` (owner-only)         |
| `config.yaml`           | `0o600`                      |
| `config.local.yaml`     | `0o600`                      |
| `.env`                  | `0o600`                      |
| `secrets.db`            | `0o600`                      |
| `ecosystem.config.js`   | `0o600` (set by `pm2 setup`) |
| `identity/device.json`  | `0o600`                      |

<Warning>
  Do not run the daemon as root. The `0o700` directory mode means only your user can read or write the data directory.
</Warning>

## Resetting

To start completely fresh:

```bash theme={}
# Stop the daemon first
pm2 stop comis   # or pkill -f daemon.js

# Remove everything
rm -rf ~/.comis

# Re-run setup
node packages/cli/dist/cli.js init
```

To reset only conversation history while keeping your config and credentials:

```bash theme={}
rm ~/.comis/memory.db
# The daemon recreates an empty database on next start
```

To reset logs only:

```bash theme={}
rm ~/.comis/logs/daemon.log*
```
