Storage
Dline does not keep its persistent data in VS Code’s ExtensionContext. It writes to three independent file roots instead. Every persistent value has one owning module; when changing storage, go through that owner rather than building paths or writing JSON yourself.
Three roots
Section titled “Three roots”| Root | Resolver | Override | Default |
|---|---|---|---|
| State/data root | getDlineDataDir() | DLINE_DIR; the actual path is <DLINE_DIR>/data | ~/.dline/data |
| Resource home | getDlineHomePath() | DLINE_HOME_DIR | ~/.dline |
| Documents root | getDlineDocumentsPath() | DLINE_DOCS_DIR, used as is | dline under the system Documents directory |
All three resolvers are in src/core/storage/documents-path.ts. DLINE_DIR and DLINE_HOME_DIR both default to locations under ~/.dline, but they are separate overrides. When DLINE_DOCS_DIR is set, Dline does not append another dline directory.
The system Documents directory is resolved by querying MyDocuments through PowerShell on Windows, running xdg-user-dir DOCUMENTS on Linux, and otherwise falling back to ~/Documents. At startup, warmupDocumentsPathCache() caches the result for synchronous callers. Tests that need full isolation should set all three environment variables.
State/data root
Section titled “State/data root”<data-root>/├── globalState.json # non-setting global state├── secrets.json # typed secrets managed by StateManager├── settings/│ ├── settings.json # global settings│ └── api_profiles.json # Profile catalog├── secrets/│ ├── api_keys.json # API keys keyed by Profile ID│ ├── provider_secrets.json # provider secrets keyed by Profile ID│ └── ... # per-Profile OAuth credential files└── workspaces/<hash>/ ├── workspaceState.json # workspace state └── settings.json # workspace settingsSecret files are created with owner-only permissions (0o600). Do not confuse settings/settings.json with settings/mcp_settings.json under the documents root.
Documents root
Section titled “Documents root”<documents-root>/├── tasks/│ ├── taskHistory.db # task index (SQLite)│ └── <task-id>/├── rules/ workflows/ skills/ subagents/ mcp/ hooks/├── settings/mcp_settings.json # MCP server settings└── checkpoints/ # checkpoint shadow repositoriesWorkspace capability files are not stored here: rules, workflows, skills, subagents, and hooks live in the project’s .agents/rules, .agents/workflows, .agents/skills, .agents/subagents, and .agents/hooks, and workspace MCP descriptors live in .agents/mcp/.
The resource home holds provider metadata (providers/), caches (cache/, including the MCP Marketplace catalog cache), and Puppeteer data (puppeteer/).
Per-task files
Section titled “Per-task files”All of these are under <documents-root>/tasks/<task-id>/:
| File | Owner |
|---|---|
journal.db | Task runtime commits: journal entries, command publications, and payloads; the conversation sent to the model is rebuilt from it |
state.json, state.json.bak | The durable kernel snapshot used to reopen and recover the task; the previous version is kept as .bak |
messages.db | UIMessage, the chat projection shown in the Webview |
activities.db | TaskActivityPersistence, task activity events |
metrics.db | API rate metrics and request/response execution data |
context.json, context_history.jsonl | Task context and context-management records |
settings.json | Task-level setting overrides |
task_metadata.json | Task metadata |
subagents/<subagent-id>/ | One directory per subagent with the same layout, including subagents spawned by other subagents |
artifacts/ | Task artifacts (such as generated images); only task-relative paths are allowed |
tmp/ | command-logs/, shell-diagnostics/, image-previews/, image-viewer/ |
src/core/storage/instance/InstanceLayout.ts is the single source of these paths, and each database has exactly one writing module.
tmp/command-logs/ survives across sessions because chat rows and the Activities view link to those logs. It is bounded by a per-task total size budget (200 MiB by default) that drops the oldest logs first; age alone never deletes a log. api_rate_metrics.jsonl is a legacy format that is imported into metrics.db with fingerprint checks.
Deleting a task must go through the task deletion coordinator, which handles locks, active Controllers, panels, TaskHistory, database files, and checkpoints. When you add a task-owned file, decide who is responsible for it during deletion and recovery.
State ownership and consistency
Section titled “State ownership and consistency”| Category | Authority | Consistency |
|---|---|---|
| Non-setting global state | globalState.json | Batched writes with a 2-second debounce and an explicit flush |
| Global settings | settings/settings.json through SettingsRepository | Cross-process file lock; reads the latest revision inside the lock, writes a temporary file, then renames it atomically; watches for external changes |
| Workspace settings | A separate SettingsRepository for workspaces/<hash>/settings.json | Same as global settings |
| Workspace state | workspaces/<hash>/workspaceState.json | Batched writes |
| Task settings | tasks/<task-id>/settings.json | Read, merge, write a temporary file, then rename atomically |
| Session overrides | StateManager memory only | Never persisted |
| Profile catalog | settings/api_profiles.json through ProfileCatalogRepository | Merges this change, keyed by stable ID, into the latest version on disk inside a cross-process lock |
| Task index | taskHistory.db through TaskHistory | SQLite WAL; watches both the database and the WAL file; queues metadata writes |
StateManager.flushPendingState() keeps flushing until no pending changes remain; at shutdown it rejects new changes before the final flush.
Setting resolution order
Section titled “Setting resolution order”Task-aware settings resolve in this order; the first hit wins:
- Remote configuration
- Session override
- Task setting
- Global setting
- Legacy fallback during migration
- Declared default
autoApprovalSettings is an exception: it reads only the global setting, so a historical task snapshot cannot override the current approval permissions.
Capability toggles (rules, workflows, skills, and so on) use three sparse scopes: global, workspace, and task, with the task scope strongest. A resource with no entry in a scope means “inherit”, not “disabled”. Use getScopedCapabilityToggles() and mutateScopedCapabilityToggles() to read and write a specific scope.
Migration boundaries
Section titled “Migration boundaries”- Cline data: Dline never reads or imports legacy Cline paths. Users copy configuration by hand; see Migrating from Cline.
- Exporting VS Code storage:
exportVSCodeStorageToSharedFiles()(insrc/hosts/vscode/vscode-to-file-migration.ts) reads legacyExtensionContextglobal state, workspace state, and secrets and writes them to file storage. Global state and each workspace record their version separately in__vscodeMigrationVersion; values already in files take precedence. - Settings migration:
StateManageruses__settingsMigrationVersionto move supported legacy global-state settings into the settings file, and removes the legacy copies only after the settings file owns them. - Task history: the legacy
taskHistory.jsonlis imported only whentaskHistory.dbdoes not exist. Once the database has been created, later launches do not attempt the import again. - Message and activity files: a legacy
ui_messages.jsonl(or an olderui_messages.json/claude_messages.json) is imported intomessages.dbon first open, andactivities.jsonintoactivities.db. Each file is renamed to<name>.migratedonly after its rows are committed and read back, so an interrupted import is retried on the next open. - Metrics database: a legacy
<task-id>.dband its WAL are moved tometrics.dbonce; an existingmetrics.dbis never overwritten. - Task runtime upgrade:
snapshot.jsonfrom earlier versions is read only to import its input queue intostate.json, once; the earlier completion state is not imported, so such a task may reopen as interrupted. The first time a task from an earlier version is continued, itsapi_conversation_history.jsonlis imported intojournal.dbwhile the journal holds nothing but that import; the file itself is left unchanged. A task opened by this version cannot be continued by an older one.
Adding persistent state
Section titled “Adding persistent state”- Decide the owner, root, scope, consistency requirement, and cleanup path first.
- Add a key to
src/shared/storage/state-keys.tsonly when the value truly belongs toStateManager-managed global state, settings, workspace state, or secrets. - Change user settings through the global or workspace
SettingsRepository; use the scoped APIs for capability toggles. - Carry a user-visible setting through the full Proto, Controller, and Webview round trip; a field added only to in-memory state is not persisted.
- Define a finite flush and close behavior for buffered writes, and invoke it at task, Controller, host shutdown, and recovery boundaries.
- Test restart recovery, concurrent windows, stale writers, workspace and task isolation, migration retries, malformed or partial files, and cleanup on deletion.