Skip to content

local-deep-research

Local Deep Research (LDR) is a research agent with a web UI, written in Python. Its parser is in the backlog (issue 56): research history and chat live in a per-user SQLCipher (encrypted SQLite) database encrypted with the user's login password (Credentials), so the analyzer cannot parse them without that password.

Source and evidence

LearningCircuit/local-deep-research (LDR), MIT, open source, Python, commit 3b965431351bb17c523fa38fdcfced4a3f891d49. All claims are from source, apart from the pre-1.0 layout, which comes from the repository's own docs. Platform directory resolution was checked in tox-dev/platformdirs at 960ff632eb575deb6f0d5bf49519d5a2ee2098b6. LDR pins platformdirs~=4.5 (pyproject.toml:62), and that checkout is newer.

Observed

  • No real install: the evidence is source and the repository's docs.

Source reviews

  • 2026-10-19: HEAD 3c9eea9 against the previously cited 3b96543, from source only. The paths were unchanged; it added the model, backup, deletion and notification records inside the encrypted database (Database detail).

Collection

Per-user storage

The data directory is $LDR_DATA_DIR if set (absolute path required), otherwise platformdirs.user_data_dir("local-deep-research") (config/paths.py:28-61; the same paths at 3c9eea9, where paths.py was reorganised and the resolution is at paths.py:16-63):

  • Linux: $XDG_DATA_HOME/local-deep-research, else ~/.local/share/local-deep-research (platformdirs unix.py:40-42).
  • macOS: ~/Library/Application Support/local-deep-research (macos.py:22-23, 31-33). Newer platformdirs lets XDG_* override this on macOS (macos.py:171-179).
  • Windows: %LOCALAPPDATA%\local-deep-research\local-deep-research. With no appauthor, platformdirs repeats the app name as the author (windows.py:32-51). The comment in LDR's paths.py:52 omits the doubled segment.

Contents, all created on demand (paths.py:64-213):

Scratch directories

None. LDR creates no working directory for the model; research_outputs/ and the download library (Document library) are its own output stores. research_outputs/ is collected with the data directory; the library is under the collected Documents/LocalDeepResearch but excluded (Exclusions).

Document library

The downloaded-document library defaults to ~/Documents/LocalDeepResearch/Library, outside the data directory, on every OS (library_settings.json:2-13, library_service.py:1212-1224).

Pre-1.0 layout

Pre-1.0 (v0.x) kept a single unencrypted ldr.db in the data directory (MIGRATION_GUIDE_v1.md:70-80, env_configuration.md:244-248).

Docker

The Docker deployment sets LDR_DATA_DIR=/data on a named volume (docker-compose.yml:57, 127-128), which is outside any home directory. Since collector 1.5.0 the DOCKER_VOLUMES table collects *ldr_data volumes, with data-relative exclusions.

Database detail

The records inside the encrypted user database, at 3c9eea9, readable only with the user's password:

  • Permissions. None on disk. LDR runs no host tools that ask for approval; operator approvals of private search-engine origins come from environment variables, not a file (env_definitions/security.py:104). No stored decision exists (grepped for approv and permission in database/: no hit in database/models, and the other hits are file-mode hardening).

  • Model. research_history.research_meta keeps submission with model_provider and model for each research (web/routers/research.py:1356-1360, 1388-1390, 1494); token_usage (research_id, timestamp, model_provider, model_name, token counts, costs) records each call, and model_usage the totals per model (models/metrics.py:20-45, 106-115). The user's default is the llm.provider and llm.model settings (default_settings.json:568, 681).

  • Backups. backup.enabled is on by default, with backup.max_count 1 and backup.max_age_days 7 (default_settings.json:5410-5450); a backup is taken at login (web/routers/auth.py:566) and older ones are pruned (backup_service.py:258-274, 865). The backup is the whole database as it was at the last login, so it still holds a research or chat deleted since, under the same password.
  • Deletion. Deleting a research deletes it and every table that references it in one transaction, in database/research_cascade.py, a module new since 3b96543 (research_cascade.py:499-530), then unlinks the report file named by report_path when it lies inside the reports root (web/routers/research.py:1919-1931, 2090-2092). With file backup on, the storage layer also removes <id>.md and <id>_metadata.json (storage/file.py:209-224).
  • Notifications. notifications.enabled, notifications.service_url (one or more URLs for Apprise, a library that posts to Discord, Telegram, email and other services; the URLs embed the service's credentials) and per-event switches such as notifications.on_research_completed are settings in the encrypted database (default_settings.json:5072-5184). The completion message carries the query, a summary and a link to the result (research_completed.jinja2:1-7), so a copy of each research's question and summary sits in the destination service, outside the host.

Credentials

  • Provider API keys are kept in the api_keys and settings tables inside the encrypted user database (models/settings.py:43-58). No keychain is used. LDR reads server environment variables, not a .env file (error_handling/report_generator.py:396).
  • Encryption: SQLCipher, key derived with PBKDF2-HMAC-SHA512 (256,000 iterations) from the login password and the 32-byte .salt file; a missing salt file means the legacy fixed salt b"no salt" (sqlcipher_utils.py:44-76, 355-358, 399). With bootstrap.allow_unencrypted the database is plain SQLite (encrypted_db.py:522-528), and the keys are then readable. Passwords are held only in memory (session_passwords.py:1-12).
  • .secret_key: secret, since it can forge web sessions.
  • config/config.toml may embed keys if the operator put them there (not verified).
  • The pre-1.0 ldr.db is unencrypted, so it holds settings and keys in plaintext. Collect it unflagged as evidence.

Exclusions

cache, models, journal_data and library under the data directory, and Documents/LocalDeepResearch/Library. Embedding models, vector indexes, journal datasets and downloaded PDFs are large and are not agent state. Each excluded directory is one manifest row with its total size, not a list of its files (collectors/docs/output.md). Keep encrypted_databases/backups: the files are encrypted copies, but they show when the database existed.

Project-local files

None. LDR is not a coding agent and does not write into repositories.

Where the project path is recorded

Nowhere. LDR has no project concept. The research query (research_history.query) is inside the encrypted database.

Parsing

LDR is backlog: its parser is issue 56, and nothing it writes is parsed yet. The history and chat are in the per-user SQLCipher database, whose key derives from the login password (Credentials), so a parser needs that password, a database kept plain with bootstrap.allow_unencrypted, or the unencrypted pre-1.0 ldr.db. The tables, the login backups and the plaintext copies are in Per-user storage and Database detail. The opt-in log files are the only line-oriented files, and they are zip-compressed on rotation.

Confidence

  • High: data directory resolution on all three OSes, every file name, the encryption scheme and the key storage (source).
  • High: the Windows doubled local-deep-research segment, from platformdirs _append_parts (the pinned 4.5 line was not checked separately).
  • Medium: the pre-1.0 ldr.db location (docs only, no older tag read).
  • Not determined: the contents of config/config.toml, and whether Documents is redirected (OneDrive) on Windows, which would move the library.