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
3c9eea9against the previously cited3b96543, 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 letsXDG_*override this on macOS (macos.py:171-179). - Windows:
%LOCALAPPDATA%\local-deep-research\local-deep-research. With noappauthor, platformdirs repeats the app name as the author (windows.py:32-51). The comment in LDR'spaths.py:52omits the doubled segment.
Contents, all created on demand (paths.py:64-213):
ldr_auth.db: plain SQLite, tableusers(username,created_at,last_login,database_version) (database/auth_db.py:1-4, 33-35, models/auth.py:32-40). This is the list of LDR accounts on the host.encrypted_databases/ldr_user_<sha256(username)[:16]>.db, with a.saltsidecar and WAL files: the per-user SQLCipher database (paths.py:135-158, encrypted_db.py:384-390, 564-575, sqlcipher_utils.py:27-28, 656-666). Tables includeresearch_history(query,mode,status,created_at,report_content,progress_log, …; models/research.py:310-343),chat_sessions,chat_messages(models/chat.py:59-65, 140-147),settingsandapi_keys.encrypted_databases/backups/<user hash>/ldr_backup_*.db: encrypted backups (paths.py:211-242, backup_service.py:249).research_outputs/<research_id>.mdand<research_id>_metadata.json: plaintext report copies in a directory created with mode 0700 since3c9eea9, written only whenreport.enable_file_backupis on (defaultfalse) (storage/file.py:32-84, storage/factory.py:36-45, default_settings.json:925-937).logs/<name>.log: plaintext, only whenLDR_ENABLE_FILE_LOGGING=true; 10 MB rotation, 7-day retention, zip-compressed (log_utils.py:1095-1110). Messages pass a credential redactor first (log_utils.py:805-812).config/config.toml: written from the web UI (web/routers/research.py:2550-2560).server_config.jsonis legacy (web/server_config.py:84-86)..secret_key: web session signing key, mode 0600 (web/fastapi_app.py:176-188).cache/includingcache/rag_indices/<user hash>/vector indexes (library_rag_service.py:679-686),models/(embedding models),journal_data/(OpenAlex and DOAJ downloads, paths.py:93-103),library/,benchmark_results/(benchmark_commands.py:57).
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
approvandpermissionindatabase/: no hit indatabase/models, and the other hits are file-mode hardening). -
Model.
research_history.research_metakeepssubmissionwithmodel_providerandmodelfor 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, andmodel_usagethe totals per model (models/metrics.py:20-45, 106-115). The user's default is thellm.providerandllm.modelsettings (default_settings.json:568, 681). - Backups.
backup.enabledis on by default, withbackup.max_count1 andbackup.max_age_days7 (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 since3b96543(research_cascade.py:499-530), then unlinks the report file named byreport_pathwhen it lies inside the reports root (web/routers/research.py:1919-1931, 2090-2092). With file backup on, the storage layer also removes<id>.mdand<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 asnotifications.on_research_completedare 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_keysandsettingstables inside the encrypted user database (models/settings.py:43-58). No keychain is used. LDR reads server environment variables, not a.envfile (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
.saltfile; a missing salt file means the legacy fixed saltb"no salt"(sqlcipher_utils.py:44-76, 355-358, 399). Withbootstrap.allow_unencryptedthe 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.tomlmay embed keys if the operator put them there (not verified).- The pre-1.0
ldr.dbis 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-researchsegment, from platformdirs_append_parts(the pinned 4.5 line was not checked separately). - Medium: the pre-1.0
ldr.dblocation (docs only, no older tag read). - Not determined: the contents of
config/config.toml, and whetherDocumentsis redirected (OneDrive) on Windows, which would move the library.