Symptom-first diagnostics. Find the symptom, run the diagnostic command, compare against the expected healthy output, and apply the resolution. For alerting and dashboards see MONITORING.md; for the full API surface see API-REFERENCE.md.
Symptom Index
| Symptom | Go to |
|---|---|
| Daemon won't start | 1. Daemon won't start |
| Model fails to load | 2. Model fails to load |
Search returns model_loading | 3. Search returns model_loading |
| Queue grows without draining | 4. Queue grows without draining |
| High inference latency | 5. High inference latency |
| Authentication rejected | 6. Authentication rejected |
| WSL2 daemon unreachable from Windows | 7. WSL2 daemon unreachable from windows |
| Admin console shows errors | 8. Admin console shows errors |
| Export/import fails | 9. Export/import fails |
1. Daemon won't start
Diagnose:
mix agent_db.doctor --json
Healthy:
{"db":true,"status":"ok","provider":"local","queue":{"done":150,"failed":0},"pubsub":true}db false (the task exits non-zero with Database unreachable) — the store
directory is missing or not writable. Resolve: point AGENT_DB_DATA_DIR
at a writable directory and re-run. status degraded or a models-related
warning does not stop the daemon — it degrades to no-model behavior, so treat
those separately (symptoms 2 and 3).
2. Model fails to load
Log signature: component: "ModelManager", reason: "download_failed" or
reason: "model_load_failed".
Diagnose:
AgentDb.model_status()
# healthy: %{embedding: %{state: :ready, ...}, llm: %{state: :ready, ...}}
# unhealthy: %{embedding: %{state: :loading, ...}} (stuck) or %{state: :failed}Resolve, in order:
- Truncated cache — the most common cause: an interrupted download left a partial file. Delete the truncated cache file for the model and let it re-download (SETUP.md documents the model directory layout and pre-placement).
- Backend mismatch on Apple Silicon — the EXLA backend must match the chip. Re-check the backend selection from SETUP.md.
- Disk space — a full volume fails mid-download; free space and retry.
- Remote provider unreachable —
model_status/0reports the provider's health state;:unreachablemeans the endpoint did not answer, check the base URL and network.:unauthorizedmeans the token was refused.
3. Search returns model_loading
The embedding model is still loading; vector and hybrid search wait for it
(:keyword mode works without a model).
Diagnose: AgentDb.model_status() — any role in :loading explains the
error. Logs show reason: "model_loading".
Resolve: wait for the load (check last_load_ms after it succeeds). If a
role stays :loading for minutes, treat it as symptom 2. Keyword search and
find/grep stay available while a model loads — no action needed if the
caller can retry.
4. Queue grows without draining
Diagnose:
AgentDb.queue_stats()
# healthy: counts per status present, e.g. %{pending: 0, running: 0, done: 150, failed: 0}
# (a status with no rows is absent from the map; a queue that cannot be read reports %{})
AgentDb.queue_detail(20)
# watch oldest_pending_ms (nil when nothing is pending) and the failed list
# with classified last_error reasonsmix agent_db.doctor # reports db, status, provider, queue, and pubsub
Resolve, in order:
- Model loading — embedding/summarization jobs cannot finish without a model; resolve symptom 2 first.
- Throughput too low — raise the worker count (
job_workersin the application config) and restart. - Failed jobs — read the classified
last_errorper failed job inqueue_detail/1(e.g.inference_timeout— check symptom 5) and clear or retry as appropriate.
5. High inference latency
Diagnose: model_status/0 reports last_latency_ms per role;
queue_detail/1 reports pending age; telemetry carries
[:agent_db, :model, :stop] durations by role.
Resolve, in order:
- A role in
:loadingblocks inference — resolve symptom 2. - A long queue (symptom 4) delays every job downstream.
- A remote provider adds network round-trips —
model_status/0shows the provider's health;inference_timeoutin logs means the provider exceeded the request deadline. Switch to the local provider or raise the timeout. - Sustained load: check
queue_statsrunning depth; if workers are all busy, raisejob_workers.
6. Authentication rejected
The daemon returns 401 when auth is on and the token is missing or wrong.
Logs carry reason: "unauthorized".
Diagnose: confirm whether auth is enabled and which token you sent:
# from inside the daemon host: the listener and auth settings
mix agent_db.doctor
Resolve: with AGENT_DB_HTTP_AUTH=true, the client must send
Authorization: Bearer <token> with a token from
AGENT_DB_HTTP_AUTH_TOKENS. The default (auth off) binds loopback only, so
a non-loopback client gets refused regardless of token.
7. WSL2 daemon unreachable from Windows
Run the daemon and the installer inside WSL2, and point Windows editors
at http://localhost:6060/mcp.
Diagnose:
# from PowerShell
wsl bash tools/install.sh --check
wsl bash -c "curl localhost:6060/api/v1/health"
Resolve, in order:
- Installer refuses to run natively —
tools/install.ps1refuses native execution on purpose: native Windows has no supported EXLA path, so a native install would look done while inference cannot work. Run it inside WSL. - Windows firewall — allow the port (6060 by default) or reach the
daemon through WSL's forwarded
localhost. - Daemon bound inside WSL only — start the daemon inside WSL2 and verify from Windows with the curl check above.
8. Admin console shows errors
The console renders the same data as the health functions.
Diagnose:
AgentDb.recent_errors(20)
# bounded list of recent operational errors with their classified codesMatch the code against the API-REFERENCE error catalog
and jump to the matching symptom above. Console panels that stay empty with
the daemon up are usually the listener question (symptom 6/7).
9. Export/import fails
Diagnose: the failure reason from AgentDb.export_data/2 /
AgentDb.import_data/2, classified with
AgentDb.export_data_error_message/1; limits are AgentDb.data_transfer_limits/0.
Resolve, in order:
- Over a limit (entry count or size) — the bounds are in
data_transfer_limits/0; split the transfer or narrow thescope:. - Permission or disk — the destination path must be writable with free space.
- Corrupt archive — a partially written export; re-export.
Reading the Logs
All operational failures log structured fields (never content, prompts, URIs, or credentials):
| Field | Meaning |
|---|---|
component | Subsystem that logged ("ModelManager", "JobWorker", "Storage") |
operation | What was attempted (:embed, :summarize_abstract, :write, :search) |
kind | Subtype of the operation or job |
role | Model role (:embedding, :llm) |
outcome | :ok or :error |
reason | The classified error code — the same tag as the transport's error code |
trace_id / span_id | W3C trace correlation |
job_id | Background job identity |
Pattern: an outcome: :error line's reason names the failure mode and maps
1:1 to the error catalog, so reason: "model_load_failed" is symptom 2 and
reason: "inference_timeout" is symptom 5.