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

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)
├── ecosystem.config.js      # pm2 process definition (created by pm2 setup)
├── logs/
│   └── daemon.N.log         # structured JSON log (pino-roll; live file = newest N)
├── traces/                  # per-session JSONL execution traces
├── workspace/               # default agent workspace (files, git, skills)
├── workspace-<agentId>/     # named-agent workspaces (one per extra agent)
├── agents/                  # per-agent RPC state (subagent results, etc.)
├── background-tasks/        # background task state files
├── identity/
│   └── device.json          # Ed25519 device identity (auto-generated)
└── devices/                 # paired device records (if pairing is used)
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.
  • ecosystem.config.js — created only by comis pm2 setup.
  • Named workspaces like workspace-myagent/ — appear only when you define more than one agent.

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:
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.
Never commit .env to version control. It contains unencrypted credentials. Comis sets its file mode to 0o600 on startup.

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.

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:
LayerTablesDurability
LCD storelcd_messages, lcd_message_parts, lcd_context_items, lcd_summariesDurable — the truth. Every turn is persisted verbatim (auto-condensed past 75%). Survives daemon restarts, redeploys, and JSONL deletions.
RAG memorymemory_*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>.jsonlDisposable — the SDK’s per-session working transcript. Routinely deleted by the operator (housekeeping) and wiped by comis sessions reset.
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.
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:
# 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 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 indexdaemon.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.
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.

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.

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
  • Subdirectories seeded by Comis: projects/, scripts/, documents/, media/, data/, output/
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.

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.

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.

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

Backing up

To migrate to a new machine, back up these items:
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.
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_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 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:
PathRequired mode
~/.comis/ (directory)0o700 (owner-only)
config.yaml0o600
config.local.yaml0o600
.env0o600
secrets.db0o600
ecosystem.config.js0o600 (set by pm2 setup)
identity/device.json0o600
Do not run the daemon as root. The 0o700 directory mode means only your user can read or write the data directory.

Resetting

To start completely fresh:
# 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:
rm ~/.comis/memory.db
# The daemon recreates an empty database on next start
To reset logs only:
rm ~/.comis/logs/daemon.log*