{"vulnerability": "CVE-2024-27980", "sightings": [{"uuid": "608ad8c2-cf97-4c87-b3d5-a40a91c49954", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2024-27980", "type": "seen", "source": "https://www.cisa.gov/news-events/ics-advisories/icsa-24-319-08", "content": "", "creation_timestamp": "2024-11-14T12:00:00.000000Z"}, {"uuid": "28414134-1fa4-4b0b-bd40-23b69d2ba5c2", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2024-27980", "type": "seen", "source": "https://infosec.exchange/users/cve/statuses/113795734483752976", "content": "", "creation_timestamp": "2025-01-09T01:11:49.829762Z"}, {"uuid": "f8785e08-59f0-45a0-853a-a8992de4e5bb", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2024-27980", "type": "seen", "source": "https://bsky.app/profile/cve-notifications.bsky.social/post/3lfbiw6zurf2f", "content": "", "creation_timestamp": "2025-01-09T01:15:56.235909Z"}, {"uuid": "318536b1-45c9-437b-b1d0-d7a3474f3485", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2024-27980", "type": "seen", "source": "https://bsky.app/profile/cve.skyfleet.blue/post/3lfbk6pxyqh2g", "content": "", "creation_timestamp": "2025-01-09T01:38:39.653161Z"}, {"uuid": "c58fcc3d-94c9-41d1-a769-a823244fbb91", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2024-27980", "type": "seen", "source": "https://bsky.app/profile/cve.skyfleet.blue/post/3lfbk6q6y742e", "content": "", "creation_timestamp": "2025-01-09T01:38:40.855987Z"}, {"uuid": "f74c6ab9-ab9d-4627-8ec2-750e4548e3ba", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2024-27980", "type": "seen", "source": "https://mastodon.social/users/CyberSignaler/statuses/113795880011713163", "content": "", "creation_timestamp": "2025-01-09T01:48:49.798251Z"}, {"uuid": "1fa13db7-93d8-4086-8b84-f76a0cf79fbb", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2024-27980", "type": "seen", "source": "https://t.me/DarkWebInformer_CVEAlerts/868", "content": "\ud83d\udd17 DarkWebInformer.com - Cyber Threat Intelligence\n\ud83d\udccc CVE ID: CVE-2024-27980\n\ud83d\udd39 Description: Due to the improper handling of batch files in child_process.spawn / child_process.spawnSync, a malicious command line argument can inject arbitrary commands and achieve code execution even if the shell option is not enabled.\n\ud83d\udccf Published: 2025-01-09T00:33:47.722Z\n\ud83d\udccf Modified: 2025-01-09T00:33:47.722Z\n\ud83d\udd17 References:\n1. http://www.openwall.com/lists/oss-security/2024/04/10/15\n2. http://www.openwall.com/lists/oss-security/2024/07/11/6\n3. http://www.openwall.com/lists/oss-security/2024/07/19/3\n4. https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/5MZN6PFXHTCCUENAKZXTGWPKUAHI6E2W/\n5. https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/JUWBYDVCUSCX7YWTBX75LADMCVYFBGKU/", "creation_timestamp": "2025-01-09T01:15:01.000000Z"}, {"uuid": "2360b5b5-d2b3-42b5-a9f2-6778570864f3", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2024-27980", "type": "seen", "source": "https://t.me/DarkWebInformer_CVEAlerts/14192", "content": "\ud83d\udd17 DarkWebInformer.com - Cyber Threat Intelligence\n\ud83d\udccc CVE ID: CVE-2024-27980\n\ud83d\udd25 CVSS Score: 8.1 (cvssV3_0, Vector: CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H)\n\ud83d\udd39 Description: Due to the improper handling of batch files in child_process.spawn / child_process.spawnSync, a malicious command line argument can inject arbitrary commands and achieve code execution even if the shell option is not enabled.\n\ud83d\udccf Published: 2025-01-09T00:33:47.722Z\n\ud83d\udccf Modified: 2025-04-30T22:25:25.133Z\n\ud83d\udd17 References:\n1. http://www.openwall.com/lists/oss-security/2024/04/10/15\n2. http://www.openwall.com/lists/oss-security/2024/07/11/6\n3. http://www.openwall.com/lists/oss-security/2024/07/19/3\n4. https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/5MZN6PFXHTCCUENAKZXTGWPKUAHI6E2W/\n5. https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/JUWBYDVCUSCX7YWTBX75LADMCVYFBGKU/", "creation_timestamp": "2025-04-30T23:14:31.000000Z"}, {"uuid": "c48b27ab-7408-49f4-9e8b-dac1528ba417", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2024-27980", "type": "seen", "source": "https://t.me/DarkWebInformer_CVEAlerts/14199", "content": "\ud83d\udd17 DarkWebInformer.com - Cyber Threat Intelligence\n\ud83d\udccc CVE ID: CVE-2024-36138\n\ud83d\udd25 CVSS Score: 8.1 (cvssV3_0, Vector: CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H)\n\ud83d\udd39 Description: Bypass incomplete fix of CVE-2024-27980, that arises from improper handling of batch files with all possible extensions on Windows via child_process.spawn / child_process.spawnSync. A malicious command line argument can inject arbitrary commands and achieve code execution even if the shell option is not enabled.\n\ud83d\udccf Published: 2024-09-07T16:00:36.011Z\n\ud83d\udccf Modified: 2025-04-30T22:25:18.920Z\n\ud83d\udd17 References:\n1. https://nodejs.org/en/blog/vulnerability/july-2024-security-releases", "creation_timestamp": "2025-04-30T23:14:43.000000Z"}, {"uuid": "323c4ecb-1816-4664-a821-7eb079da4580", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2024-27980", "type": "seen", "source": "https://gist.github.com/aehrt55/1498ba92410e551ac7835ab585984906", "content": "", "creation_timestamp": "2026-04-28T05:39:56.000000Z"}, {"uuid": "7dd03d1d-786d-4fba-84e2-f00611c3b299", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2024-27980", "type": "seen", "source": "https://t.me/cvedetector/14754", "content": "{\n  \"Source\": \"CVE FEED\",\n  \"Title\": \"CVE-2024-27980 - Node.js Child Process Code Execution\", \n  \"Content\": \"CVE ID : CVE-2024-27980 \nPublished : Jan. 9, 2025, 1:15 a.m. | 21\u00a0minutes ago \nDescription : Due to the improper handling of batch files in child_process.spawn / child_process.spawnSync, a malicious command line argument can inject arbitrary commands and achieve code execution even if the shell option is not enabled. \nSeverity: 8.1 | HIGH \nVisit the link for more details, such as CVSS details, affected products, timeline, and more...\",\n  \"Detection Date\": \"09 Jan 2025\",\n  \"Type\": \"Vulnerability\"\n}\n\ud83d\udd39 t.me/cvedetector \ud83d\udd39", "creation_timestamp": "2025-01-09T02:41:56.000000Z"}, {"uuid": "1771d9d4-4a46-4029-bb8c-ac2b06724339", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2024-27980", "type": "seen", "source": "https://t.me/cvedetector/5029", "content": "{\n  \"Source\": \"CVE FEED\",\n  \"Title\": \"CVE-2024-36138 - Microsoft Windows Child Process Execution Vulnerability\", \n  \"Content\": \"CVE ID : CVE-2024-36138 \nPublished : Sept. 7, 2024, 4:15 p.m. | 42\u00a0minutes ago \nDescription : Bypass incomplete fix of CVE-2024-27980, that arises from improper handling of batch files with all possible extensions on Windows via child_process.spawn / child_process.spawnSync. A malicious command line argument can inject arbitrary commands and achieve code execution even if the shell option is not enabled. \nSeverity: 8.1 | HIGH \nVisit the link for more details, such as CVSS details, affected products, timeline, and more...\",\n  \"Detection Date\": \"07 Sep 2024\",\n  \"Type\": \"Vulnerability\"\n}\n\ud83d\udd39 t.me/cvedetector \ud83d\udd39", "creation_timestamp": "2024-09-07T19:20:31.000000Z"}, {"uuid": "d7bfab33-81a9-4115-a654-d16bc2e424b2", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2024-27980", "type": "seen", "source": "https://gist.github.com/ukind/2e6e8aa37861a40d5f3ad455cef9918b", "content": "# Remnic + QMD Windows Setup (LLM Instructions)\n\n**Audience:** LLM agent configuring Remnic on Windows from zero. Execute steps in order. Verify each step before proceeding. Stop and report on any verification failure.\n\n**Goal end state:**\n\n```\nSearch backend: available cli=true daemon=true session=true\n  cliPath=C:\\nvm4w\\nodejs\\qmd.cmd\n  cliPathSource=configured\n  cliVersion=qmd 2.5.3\n```\n\nPlus LLM extraction working (no 401, facts auto-persisted):\n\n```\nremnic: persisted: 2 facts, 0 entities, 2 questions, 2 profile updates\n```\n\n---\n\n## Prerequisites (verify before step 1)\n\n```bash\nnode --version        # expect: v18+ (v24.x recommended)\nnpm --version         # expect: 10+\nwhere node            # note install root, e.g. C:\\nvm4w\\nodejs\\node.exe\n```\n\nIf Node missing \u2192 install via nvm-windows first. Do **not** proceed without Node.\n\n**Required minimum versions** (older versions have the Windows spawn bug):\n\n- `@remnic/cli` \u2265 **9.3.644** (verified through **9.7.15** as of 2026-07-31)\n- `@tobilu/qmd` = **2.5.3** (must match Remnic's `QMD_SUPPORTED_VERSION` \u2014 currently `\"2.5.3\"`)\n\n**\u26a0 Check for conflicting package managers before step 1.** If `bun`, `nub` (`@nubjs/nub`), `pnpm`, or `yarn` are installed globally alongside npm, `npm install -g @tobilu/qmd` can fail with `spawn C:\\WINDOWS\\system32\\cmd.exe ENOENT` during the `better-sqlite3` postinstall build. This is the #1 cause of a failed QMD install on a machine that already has Node. Detect and remove:\n\n```bash\n# detect\nwhere bun; where nub; where pnpm; where yarn\nnpm list -g --depth=0 2&gt;&amp;1 | grep -iE \"bun|nub|pnpm|yarn\"\n\n# remove (example: bun + nub found)\nnpm uninstall -g bun @nubjs/nub nub\nrm -rf ~/.bun          # bun native install dir\n# pnpm: npm uninstall -g pnpm; rm -rf ~/AppData/Local/pnpm\n# yarn (classic):  npm uninstall -g yarn\n# yarn (berry via corepack): corepack disable yarn\n```\n\nAlso remove any dead `~/.bun/bin` (or equivalent) entries from PATH \u2014 they become harmless once the binary is gone, but a clean PATH avoids confusion. After removal, re-run step 1.\n\n---\n\n## Step 1 \u2014 Install QMD\n\n```bash\nnpm install -g @tobilu/qmd\n```\n\n**Verify:**\n\n```bash\nqmd --version\n# expect: qmd 2.5.3\nwhere qmd\n# expect two lines: \\qmd  and  \\qmd.cmd\n```\n\n**Capture** the `.cmd` path. Use it as `` for the rest of this doc.\nExample: `C:\\nvm4w\\nodejs\\qmd.cmd`\n\nIf `qmd --version` prints a version \u2260 2.5.3 \u2192 install exact version:\n\n```bash\nnpm install -g @tobilu/qmd@2.5.3\n```\n\n---\n\n## Step 2 \u2014 Install Remnic\n\n```bash\nnpm install -g @remnic/cli@latest\n```\n\n**Verify:**\n\n```bash\nnpm list -g @remnic/cli\n# expect: @remnic/cli@9.3.644 or newer\n```\n\nIf installed version &lt; 9.3.644 \u2192 **stop**. The Windows `child_process.spawn` bug is unfixed below this version (see GitHub issue #1476). Force latest:\n\n```bash\nnpm install -g @remnic/cli@latest\n```\n\n---\n\n## Step 3 \u2014 Create Remnic config\n\nThere are **two** candidate paths. Write to BOTH to be safe \u2014 they serve different readers:\n\n1. `%USERPROFILE%\\.config\\remnic\\config.json` \u2014 the documented modern path (forward-compat).\n2. `%USERPROFILE%\\.pi\\agent\\remnic.config.json` \u2014 **the path newer Remnic (\u2265 9.3.7xx) actually resolves at runtime when the Pi connector is installed.** `remnic config` prints this as the first line. If you only write path 1, the server loads an empty/placeholder config \u2192 `cliPathSource=auto-path` and `no gateway default AI`.\n\n&gt; `.config/engram/config.json` is the legacy pre-rename path; older Remnic still falls back to it. **Verify which path your install reads with `remnic config` (first line is the resolved path) and ensure the file there has the full schema below.** When in doubt, write the same content to all three locations.\n\n&gt; \u26a0\u26a0 **CRITICAL \u2014 `resolveConfigPath()` checks `process.cwd()/remnic.config.json` FIRST.** Config resolution order is: `--config` flag \u2192 `REMNIC_CONFIG_PATH`/`ENGRAM_CONFIG_PATH` env \u2192 `process.cwd()/remnic.config.json` \u2192 `process.cwd()/engram.config.json` \u2192 `~/.config/remnic/config.json` \u2192 `~/.config/engram/config.json`. **This means the daemon's config depends on the LAUNCH working directory.** A stray `remnic.config.json` left in `%USERPROFILE%` (the user home root) gets picked FIRST whenever the daemon is launched from home (login tasks, manual `daemon start` from `~`, some MCP hosts) \u2014 silently overriding the canonical config. This is the #1 cause of split-brain: two configs with different `memoryDir` / `model` / preset \u2192 two memory stores, broken extraction, recall reading the wrong tree.\n&gt;\n&gt; **Hard rule: after writing the canonical config, DELETE any `remnic.config.json` / `engram.config.json` from `%USERPROFILE%` itself (the home root, NOT `.config\\`).** Verify:\n&gt;\n&gt; ```bash\n&gt; # MUST print 'absent' \u2014 a home-root remnic.config.json is always a bug\n&gt; test -e \"$USERPROFILE/remnic.config.json\" &amp;&amp; echo \"PRESENT (delete it!)\" || echo absent\n&gt; # all candidate configs must be byte-identical\n&gt; md5sum \"$USERPROFILE/.config/remnic/config.json\" \"$USERPROFILE/.config/engram/config.json\" \"$USERPROFILE/.pi/agent/remnic.config.json\"\n&gt; ```\n&gt;\n&gt; Only the three paths under `.config\\` and `.pi\\agent\\` should exist. The home-root file is never legitimate.\n\nCreate parent dirs if missing:\n\n```bash\nmkdir -p \"$USERPROFILE/.config/remnic\"\nmkdir -p \"$USERPROFILE/.pi/agent\"   # for path 2 above\n```\n\n**Generate a random auth token first** (the server REQUIRES this \u2014 without it, all requests get 401):\n\n```bash\n# Generate a 64-char hex token. Use this as  below.\nnode -e \"console.log(require('crypto').randomBytes(32).toString('hex'))\"\n```\n\nWrite file (substitute `` from step 1, `` from above, and `` from your gateway \u2014 use double backslashes in JSON):\n\n```json\n{\n  \"qmdPath\": \"C:\\\\nvm4w\\\\nodejs\\\\qmd.cmd\",\n  \"remnic\": {\n    \"qmdPath\": \"C:\\\\nvm4w\\\\nodejs\\\\qmd.cmd\",\n    \"openaiApiKey\": \"\",\n    \"openaiBaseUrl\": \"https://your-gateway.example.com/v1\",\n    \"model\": \"\",\n    \"memoryDir\": \"C:\\\\Users\\\\\\\\.pi\\\\agent\\\\.remnic\\\\memory\",\n    \"memoryOsPreset\": \"balanced\"\n  },\n  \"server\": {\n    \"host\": \"127.0.0.1\",\n    \"port\": 4318,\n    \"authToken\": \"\"\n  },\n  \"engram\": { \"qmdPath\": \"C:\\\\nvm4w\\\\nodejs\\\\qmd.cmd\" }\n}\n```\n\nAll three top-level keys (`qmdPath`, `remnic`, `engram`) required for forward-compat with the `engram \u2192 remnic` rename. Forward slashes (`C:/nvm4w/nodejs/qmd.cmd`) also work \u2014 use whichever your file-write tool handles safely.\n\n**Critical fields explained:**\n\n| Field | Purpose | Why it matters |\n| --- | --- | --- |\n| `server.authToken` | Master auth token for the HTTP server | **REQUIRED.** Without it, server log prints `No auth token set \u2014 server will reject all requests` and ALL requests return 401. Generate via `node -e \"console.log(require('crypto').randomBytes(32).toString('hex'))\"` |\n| `server.host` / `server.port` | Server bind address | `127.0.0.1:4318` is the default. Change only if port conflicts |\n| `openaiApiKey` | API key for your LLM gateway | Extraction engine uses this for fact/entity extraction |\n| `openaiBaseUrl` | Gateway URL (OpenAI-compatible) | **Must match the gateway that issued the key.** If omitted, Remnic sends the key to `api.openai.com` \u2192 401 |\n| `model` | Model ID the extraction LLM requests | **REQUIRED on most gateways.** Remnic hardcodes `DEFAULT_REASONING_MODEL = \"gpt-5.5\"` and uses it whenever `remnic.model` is empty/unset. The chunk filename is **version-specific** (it changes every release \u2014 was `chunk-2HAG7JOI.js:1067` in 9.3.x, is `chunk-INELTPRG.js:1187` in 9.7.15). Find it in your install with: `grep -rn \"DEFAULT_REASONING_MODEL =\" \"$(npm root -g)/@remnic/cli/node_modules/@remnic/core/dist/\"`. If your gateway does not serve `gpt-5.5`, extraction fails (`M302 Model \"gpt-5.5\" is not available` / `404 model \"gpt-5.5\" not found`) \u2192 `fallback_no_parsed_output` \u2192 **zero new memories extracted**. Set this to a model your gateway actually serves (e.g. Manifest `auto`, Ollama-cloud `minimax-m3`, OpenRouter `gpt-4o-mini`). Verify available IDs via `curl -s $OPENAI_BASE_URL/models -H \"Authorization: Bearer $OPENAI_API_KEY\"` |\n| `memoryDir` | Where extracted memories are written | **QMD `index.yml` (step 4) MUST point at this exact path.** Mismatch = QMD indexes empty dir, recall falls back to slow file scan |\n| `memoryOsPreset` | OS-specific path handling | `balanced` is the safe default |\n| `gatewayConfig` (optional, see Issue G) | Model config for the fallback LLM client (scheduled functions: hourly summaries, some Dreams phases) | **REQUIRED to enable scheduled/background functions.** Without it, the hourly summarizer is silently disabled (`no gateway default AI and local LLM disabled \u2014 hourly summarization disabled`). Must use `provider/model` format (e.g., `\"openai/minimax-m3\"`). Requires a matching provider config in `gatewayConfig.models.providers` to override the built-in `openai` base URL. See Issue G for full schema. |\n\n&gt; \u26a0 **`gatewayConfig` vs `model`:** These are separate fields for separate code paths. `model` feeds the **direct client** (extraction). `gatewayConfig` feeds the **fallback client** (scheduled functions). Both are needed for full functionality. Do NOT set `modelSource: \"gateway\"` \u2014 that routes extraction through the fallback client and breaks it.\n\n**Verify:**\n\n```bash\nremnic config\n# First line shows the resolved path. Accept ANY of:\n#   Config: %USERPROFILE%\\.config\\remnic\\config.json      (documented modern)\n#   Config: %USERPROFILE%\\.pi\\agent\\remnic.config.json    (newer Remnic \u2265 9.3.7xx with Pi connector)\n#   Config: %USERPROFILE%\\.config\\engram\\config.json      (legacy pre-rename)\n# JSON body must contain qmdPath AND remnic.model. If the body shows\n# placeholder ${OPENAI_API_KEY} / ${REMNIC_AUTH_TOKEN} and no qmdPath/model,\n# you are reading the Pi EXTENSION template, not the server config \u2014\n# overwrite it with the full schema above.\n```\n\nIf the resolved path is any of the three above, ensure that exact file has the full schema (qmdPath, remnic.model, remnic.openaiApiKey, remnic.openaiBaseUrl, server.authToken). The Pi extension file at `%USERPROFILE%\\.pi\\agent\\extensions\\remnic\\remnic.config.json` is a DIFFERENT file (connector-facing: recall/observe settings + its own `remnic_p\u2026` token) \u2014 do not confuse the two.\n\n---\n\n## Step 4 \u2014 Create QMD collections config\n\nPath: `%USERPROFILE%\\.config\\qmd\\index.yml`\n\nCreate parent dir if missing:\n\n```bash\nmkdir -p \"$USERPROFILE/.config/qmd\"\n```\n\n**Determine collection name first.** Remnic derives it from workspace identity. Default Pi agent collection is `openclaw-engram` (the legacy name was kept after the rename to preserve embedding indexes). The exact name will appear in the daemon log after step 5 if you guess wrong \u2014 adjust this file and re-run `qmd update` if so.\n\n&gt; \u26a0 **The `openclaw-engram` path MUST match `remnic.memoryDir` from step 3.** The Remnic server writes extracted memories to the configured `memoryDir`, NOT to the default `~/.remnic/memory`. If `index.yml` points at a different directory, QMD indexes an empty tree and recall silently degrades to file-scan fallback. This is the #1 cause of \"recall works but QMD never fires.\"\n\nWrite file:\n\n```yaml\ncollections:\n  pi-memory:\n    path: C:\\Users\\\\.pi\\agent\\memory\n    pattern: \"**/*.md\"\n  openclaw-engram:\n    # MUST match remnic.memoryDir from step 3 config.\n    # Default Pi agent: .pi\\agent\\.remnic\\memory\n    path: C:\\Users\\\\.pi\\agent\\.remnic\\memory\n    pattern: \"**/*.md\"\nmodels:\n  embed: hf:ggml-org/embeddinggemma-300M-GGUF/embeddinggemma-300M-Q8_0.gguf\n  generate: hf:tobil/qmd-query-expansion-1.7B-gguf/qmd-query-expansion-1.7B-q4_k_m.gguf\n  rerank: hf:ggml-org/Qwen3-Reranker-0.6B-Q8_0-GGUF/qwen3-reranker-0.6b-q8_0.gguf\n```\n\nSubstitute every `` placeholder above with your actual Windows username (`echo $USERNAME` or `%USERNAME%`). OpenClaw integration is **not** part of this Windows setup \u2014 its workspace collection is intentionally omitted. Skip `remnic openclaw install` and ignore the doctor warning about the missing `openclaw.json`.\n\n**Verify:**\n\n```bash\nqmd doctor 2&gt;&amp;1 | head -20\n# expect: collections discovered, no YAML parse errors\n```\n\n---\n\n## Step 5 \u2014 Install Pi connector (REQUIRED for Pi agent integration)\n\nIf using Remnic with the Pi Coding Agent, install the connector. This creates the Pi extension at `%USERPROFILE%\\.pi\\agent\\extensions\\remnic\\` and generates a **separate connector-scoped token** (different from `server.authToken` \u2014 both are accepted by the server).\n\n```bash\nremnic connectors install pi\n```\n\n**Verify:**\n\n```bash\n# Extension files exist\ntest -f \"$USERPROFILE/.pi/agent/extensions/remnic/remnic.config.json\" &amp;&amp; echo \"OK pi extension\" || echo \"FAIL pi extension\"\n\n# Connector shows as installed (\u2713)\nremnic connectors list 2&gt;&amp;1 | grep \"pi\"\n# expect: \u2713 pi  Pi Coding Agent ...\n```\n\nThe extension's `remnic.config.json` contains its own `authToken` and `remnicDaemonUrl`. Do **not** manually edit this file unless the daemon URL or port changes.\n\n&gt; \u26a0 **Token relationship:** The server accepts BOTH the master `server.authToken` (from step 3 config) AND any connector token generated by `remnic token generate` / `remnic connectors install`. They are different tokens by design \u2014 the master token guards the HTTP API, connector tokens are scoped per-connector. Do not try to make them match.\n\n---\n\n## Step 6 \u2014 Set LLM gateway env vars (REQUIRED for extraction)\n\nIf using a non-OpenAI gateway (Manifest, OpenRouter, Groq, Ollama, etc.), the OpenAI SDK reads `OPENAI_API_KEY` from the environment. Without `OPENAI_BASE_URL` set, the SDK sends that key to `api.openai.com` \u2192 **401 Incorrect API key**. This breaks all LLM extraction (fact/entity/profile extraction, hourly summaries).\n\nSet both env vars to match your `config.json` gateway from step 3:\n\n```bash\nsetx OPENAI_API_KEY \"\"\nsetx OPENAI_BASE_URL \"https://your-gateway.example.com/v1\"\n```\n\n&gt; \u26a0 **`setx` only affects NEW processes spawned after it runs.** The current shell, any running daemon, and any running MCP host will NOT see the new env vars until they are restarted. A full Windows reboot is the most reliable way to ensure all processes inherit the new env.\n&gt;\n&gt; **Avoiding a reboot for immediate testing:** `remnic daemon start` spawns a detached process that inherits the *spawning shell's* environment at fork time \u2014 NOT the registry values `setx` just wrote. So `setx` + immediate `remnic daemon start` in the same shell still yields `Fatal: Environment variable OPENAI_API_KEY is not set`. Export the vars into the current shell FIRST, then start the daemon:\n&gt;\n&gt; ```bash\n&gt; # write env script from config (avoids hand-copying secrets)\n&gt; node -e \"const c=require(require('path').join(process.env.USERPROFILE,'.pi','agent','remnic.config.json'));process.stdout.write('export OPENAI_API_KEY=\\\"'+c.remnic.openaiApiKey+'\\\"\\nexport OPENAI_BASE_URL=\\\"'+c.remnic.openaiBaseUrl+'\\\"\\n')\" &gt; /tmp/r.sh &amp;&amp; source /tmp/r.sh\n&gt; remnic daemon stop; remnic daemon start\n&gt; ```\n&gt;\n&gt; The `setx` (registry) values still take effect for future reboots / Task Scheduler; the `source` just bridges the current session.\n\n**Verify (in a NEW shell opened after `setx`):**\n\n```bash\nenv | grep OPENAI\n# expect both OPENAI_API_KEY and OPENAI_BASE_URL present\n```\n\n**Gateway compatibility notes:**\n\n| Gateway type | Known issues | Fix |\n| --- | --- | --- |\n| Manifest (`app.manifest.build`) | Only serves model `auto` for most agents; Remnic defaults to `gpt-5.5` \u2192 `M302 Model \"gpt-5.5\" is not available`. Also forces SSE streaming by default; routes to reasoning model that returns `reasoning_content` not `content` | **Set `remnic.model: \"auto\"`** in config (step 3). Disable streaming on gateway dashboard. Remnic's default `extractionMaxOutputTokens: 16384` is sufficient for reasoning overhead |\n| Ollama-cloud (`ollama.com/v1`) | Reasoning models (`minimax-m3`, `glm-5`, etc.) with `thinkingFormat: zai` | Set `remnic.model` to a served ID (e.g. `minimax-m3`). Verified working: extraction returns parseable JSON in `content`, `persisted: N facts` |\n| OpenRouter / Groq / Together | Standard OpenAI-compatible | Usually works out of the box |\n| Ollama / LM Studio (local) | Set `openaiBaseUrl` to `http://localhost:11434/v1` (Ollama) or `http://localhost:1234/v1` (LM Studio) | Use dummy key like `ollama` |\n\n---\n\n## Step 7 \u2014 Pull QMD models (REQUIRED before first daemon start)\n\nThe daemon's startup QMD-sync and `qmd embed` both need the 3 GGUF models (~2.3 GB: embedding + query-expansion + reranker). They are **not** bundled \u2014 pull them BEFORE starting the daemon, otherwise the first daemon start races the download and logs `QMD update \u2026 timed out after 90000ms` (the #1 fresh-install stumble).\n\n```bash\nqmd pull\n# expect: downloads embed/generate/rerank GGUFs into ~/.cache/qmd/models/\n```\n\n**Verify:**\n\n```bash\nqmd doctor 2&gt;&amp;1 | grep -iE \"model cache|missing\"\n# expect: no \"missing\" lines (all 3 present)\n```\n\n`qmd pull` is idempotent \u2014 re-running only fetches missing/changed models. Skip on reinstall if `~/.cache/qmd/models/` already has all three.\n\n---\n\n## Step 8 \u2014 Start Remnic daemon\n\nWindows has **no service install** \u2014 `remnic daemon install` returns `Unsupported platform: win32`. You must `remnic daemon start` manually after every reboot (or wire it into Task Scheduler \u2014 see Persistence section).\n\n```bash\nremnic daemon stop          # safe if not running\nremnic daemon start\n```\n\n**Verify (wait 3 seconds first):**\n\n```bash\nsleep 3\nnetstat -ano | findstr \":4318\" | findstr LISTENING\n# expect one LISTENING line \u2014 proves the daemon is actually up\ngrep -i \"Search backend\" \"$USERPROFILE/.remnic/server.log\" | tail -1\n```\n\n&gt; \u26a0 **`remnic daemon status` now works on Windows as of 9.7.15.** Previously (\u22649.3.x) it reported `Running: no` and never wrote the PID file after detach \u2014 that bug is fixed; `server.pid` is now written and `daemon status` reports the correct PID. `netstat -ano | findstr :4318` remains a reliable cross-check (and the source of truth on older versions).\n\n**Expected success line:**\n\n```\nremnic: Search backend: available cli=true daemon=true session=true\n  cliPath=C:\\nvm4w\\nodejs\\qmd.cmd\n  cliPathSource=configured\n  cliVersion=qmd 2.5.3\n```\n\nA harmless `cliProbeError=spawn /opt/homebrew/bin/qmd ENOENT` may appear in the first few startup lines (the probe tries a hardcoded macOS path before the configured path wins). Ignore it once the success line above shows `cli=true`.\n\n**If failure** \u2192 see Troubleshooting matrix below before continuing.\n\n---\n\n## Step 9 \u2014 Index existing memory\n\n```bash\nqmd update\n```\n\n**Expected output:** lists each collection, shows `Indexed: N new` counts.\n\nIf a collection shows `0 new, 0 updated, 0 unchanged, 0 removed` and the directory contains `.md` files \u2192 check the path in `index.yml` matches the actual directory.\n\n**Then embed:**\n\n```bash\nqmd embed\n```\n\n**Expected:**\n\n```\n\u2713 Done! Embedded N chunks from M documents in Xs\n```\n\n&gt; \u26a0 **Embedding uses the GPU (node-llama-cpp).** On a PC without an NVIDIA GPU + drivers, `qmd embed` falls back to CPU (far slower) or errors with `ggml-cuda \u2026 CUDA error`. Force CPU if needed: `QMD_FORCE_CPU=1 qmd embed`. The first embed of a large store is the slowest run \u2014 subsequent embeds are incremental.\n\n---\n\n## Step 10 \u2014 Restart MCP host\n\nIf Remnic is consumed via MCP (Pi agent, Claude Desktop, Cursor, Codex), **restart the host application** after the steps above. The Pi connector talks to the **standalone daemon at `127.0.0.1:4318`** (see its `remnicDaemonUrl` in the extension config) \u2014 it does NOT spawn its own daemon, so in-place env/config changes won't reach an already-connected host until it reconnects. (Other MCP hosts may spawn their own embedded daemon over stdio \u2014 same restart principle applies.)\n\n**Verify via MCP tool (in host context):**\n\n```\nremnic_console_state\n```\n\nExpected fragment:\n\n```json\n\"qmdProbe\": {\n  \"available\": true,\n  \"daemonMode\": true,\n  \"debug\": \"cli=true daemon=true session=true cliPath=...qmd.cmd...\"\n}\n```\n\n---\n\n## Extraction pipeline (how it works + how to verify)\n\nRemnic's core loop: each `observe` (conversation turn) is queued \u2192 an LLM extracts structured memories \u2192 they're persisted to the memory store and QMD-indexed. Understanding the stages prevents the two most common confusions: *\"I called observe but nothing persisted\"* and *\"I fixed the model and suddenly a burst of extractions ran.\"*\n\n### It's asynchronous \u2014 don't trust the in-turn response\n\n`observe` / `remnic_observe` returns immediately with `\"extractionQueued\": true`. The LLM call + persistence happen **seconds later** on the extraction queue. The response only confirms the turn was *accepted*, not that memories were extracted.\n\n**Verify extraction health from the log, never from the response:**\n\n```bash\n# positive signal \u2014 facts were extracted and written\ngrep -aE \"persisted: [0-9]+ facts\" \"$USERPROFILE/.remnic/server.log\" | tail -5\n# expect e.g.: remnic: persisted: 3 facts, 1 entities, 2 questions, 0 profile updates\n\n# negative signal \u2014 all extraction stages failed\ngrep -aE \"extraction fallback returned no parsed output|model \\\"gpt-5.5\\\" not found\" \"$USERPROFILE/.remnic/server.log\" | tail -5\n```\n\nA `persisted: 0 facts, \u2026 1 questions` line is still a **successful** run (the LLM decided nothing was worth keeping) \u2014 only `extraction fallback returned no parsed output` / model-404s indicate failure.\n\n### The 3-stage fallback chain\n\nExtraction tries LLM backends in order; the first to return parseable JSON wins:\n\n1. **Direct client** (`remnic.model`, bare ID) \u2014 primary path, OpenAI SDK against `openaiBaseUrl`.\n2. **Gateway AI** (fallback client, `gatewayConfig\u2026provider/model`) \u2014 only if the direct client errors (404 / timeout / parse-fail). Needs `gatewayConfig` (Issue G).\n3. **Gateway default AI** \u2014 last resort; absent `gatewayConfig` \u2192 `fallback LLM: no models configured in gateway`.\n\nLog signature per stage: `extraction: direct client failed, falling back to gateway AI` \u2192 `fallback LLM: no models configured in gateway` \u2192 `extraction fallback returned no parsed output`. **Stage 1 alone is sufficient for extraction**; stages 2\u20133 also serve scheduled functions (Issue G). Do NOT set `modelSource: \"gateway\"` \u2014 it forces extraction onto the fallback client and breaks it.\n\n### Backlog self-heal (why a burst appears after a fix)\n\nA successful extraction marks the conversation **fingerprint** processed; a *failed* extraction (all stages) does **NOT** mark it. So every subsequent `observe` re-queues any unprocessed fingerprints. Consequence: when you fix a broken model/gateway, the daemon replays the accumulated backlog \u2014 a burst of `persisted:` lines. This is correct, not a bug. It is bounded by the circuit breaker + backoff below.\n\n### Circuit breaker + retry\n\nWhen the gateway is failing, extraction does not hammer it:\n\n- **Circuit breaker**: after **5** consecutive failures (`extractionBreakerFailureThreshold`) extraction pauses **5 min** (`extractionBreakerCooldownMs`); a **401/auth** failure pauses **30 min** (`extractionBreakerAuthCooldownMs`). A paused daemon logs nothing new \u2014 it looks idle, not erroring. Don't confuse silence with health; check `persisted:` and the error grep.\n- **Retry/backoff** (`extractionRetryEnabled`, **on** by default): schedule `[60s, \u2026]`, `extractionRetryMaxBackoffMs` = 6 h, jitter 0.2.\n\n### Gating thresholds (why short chats extract nothing)\n\nConversations below these floors skip extraction entirely (no LLM call, no error logged):\n\n| Tunable | Default | Effect |\n| --- | --- | --- |\n| `extractionMinChars` | **40** | Turns shorter than this are skipped |\n| `extractionMinUserTurns` | **1** | Need \u22651 real user turn |\n| `extractionMaxTurnChars` | **4000** | Per-turn truncation |\n| `extractionMaxFactsPerRun` | **12** | Cap facts per run |\n| `extractionMaxEntitiesPerRun` | 6 \u00b7 `\u2026Questions` 3 \u00b7 `\u2026ProfileUpdates` 4 | Per-type caps |\n| `extractionDedupeEnabled` | **on** (5-min `extractionDedupeWindowMs`) | Suppresses near-duplicate facts within window |\n| `extractionMaxOutputTokens` | **16384** | LLM response budget (covers reasoning overhead) |\n\nMechanical/telemetry transcripts are also pre-filtered (`extractionTelemetryPrefilterEnabled`) \u2014 action logs with no durable cues skip extraction.\n\n### Faithfulness gate / judge (optional, off by default)\n\n`extractionJudgeEnabled` runs a **second** LLM pass scoring extracted facts for faithfulness before persisting (issue #1576 / #1581). Off by default. Enabling requires `extractionJudgeModel` (bare ID, same gateway) and roughly doubles LLM cost. `extractionJudgeShadow: true` logs scores without gating (safe way to evaluate). Leave off unless you need quality gating.\n\n### What gets extracted\n\nEach run can emit: **facts** (categories: `fact`, `decision`, `preference`, `entity`, `relationship`, `principle`, `commitment`, `moment`, `skill`, `rule`, `procedure`, `reasoning_trace`), **entities**, **profileUpdates** (behavioral profile), **questions** (open curiosities), and **relationships** (entity-graph edges).\n\n---\n\n## Troubleshooting matrix\n\nEach row: log signature \u2192 root cause \u2192 fix.\n\n| Log line / symptom | Root cause | Fix |\n| --- | --- | --- |\n| **Split-brain: `remnic config` shows canonical path BUT daemon writes to wrong `memoryDir` / uses `gpt-5.5` / preset differs** | A stray `remnic.config.json` (or `engram.config.json`) exists in `%USERPROFILE%` **home root**. `resolveConfigPath()` checks `process.cwd()/remnic.config.json` FIRST, so a daemon launched from home loads that file instead of the canonical `~/.config/remnic/config.json`. Two configs \u2192 two memory stores \u2192 extraction/recall disagree. | `test -e \"$USERPROFILE/remnic.config.json\" &amp;&amp; rm \"$USERPROFILE/remnic.config.json\"`. Then `md5sum` the three legitimate configs to confirm identical. Restart daemon from the same cwd each time (or rely on `~/.config/remnic/config.json` fallthrough). See Step 3 warning + Issue A. |\n| **Extraction 404 `NotFoundError: 404 model \"gpt-5.5\" not found`** (Ollama-cloud / OpenRouter / any non-OpenAI gateway) | `remnic.model` unset in the config the daemon ACTUALLY loaded (often because a home-root rogue config won the cwd race). Falls back to hardcoded `DEFAULT_REASONING_MODEL = \"gpt-5.5\"`. | Set `remnic.model` to a served ID (e.g. `minimax-m3`) in the file `remnic config` resolves to \u2014 AND ensure no home-root config shadows it. Verify the model is served: `curl -s $OPENAI_BASE_URL/models -H \"Authorization: Bearer $OPENAI_API_KEY\"`. Restart daemon. Grep log for `persisted: N facts` to confirm. |\n| **Two memory stores: recall returns paths under `.pi\\agent\\.remnic\\memory` but new extractions vanish / land in `~/.remnic\\memory`** | Divergent configs with different `memoryDir`. The daemon writes to ITS config's `memoryDir`; QMD `index.yml` indexes the other \u2192 newly written memories are never recalled. | Pick ONE `memoryDir` (recommend `.pi\\agent\\.remnic\\memory`, the project-scoped store), set it identically in all 3 configs, align `index.yml` `openclaw-engram.path` to match, merge any unique files from the abandoned store, then `qmd update &amp;&amp; qmd embed`. See Step 3 + Issue A. |\n| `runExtraction: failed to load meta for retry bookkeeping (non-fatal) SyntaxError: Unexpected token ' ', \"            \"... is not valid JSON` at `_StorageManager.loadMeta` | `state/meta.json` in the active `memoryDir` is corrupted \u2014 typically all-NUL bytes (136K of `\\0`) from an aborted/crashed write. `JSON.parse` throws on every extraction. | Quarantine the bad file: `mv /state/meta.json /state/meta.json.nul-corrupt.bak`. The server recreates a fresh valid `meta.json` on the next write. Non-fatal but pollutes the log and breaks extraction-retry bookkeeping \u2014 fix it. |\n| `cliProbeError=spawn /opt/homebrew/bin/qmd ENOENT` AND `cli=false` | Remnic &lt; 9.3.644 (pre cross-spawn fix) | `npm install -g @remnic/cli@latest`, restart daemon + MCP host |\n| `cliProbeError=spawn ... EINVAL` | Windows blocking `.cmd` spawn (Node \u226518 CVE-2024-27980 enforcement); Remnic version too old | Same as above |\n| `cliPathSource=auto-path` but everything works | Config file not present or not loaded | Optional \u2014 works via PATH. To silence the fallback, complete step 3 |\n| `Search backend: available cli=true` BUT `QMD collection \"\" not found` | Collection missing from `index.yml` | Add collection name from log to step 4 file, run `qmd update` |\n| `Search collection missing for Remnic memory store` | Memory dir empty or never indexed | Run `qmd update &amp;&amp; qmd embed` |\n| `QMD update failed for collection X: ... aborted` on fresh install | Models not yet downloaded (Step 7 `qmd pull` skipped or incomplete) | Run `qmd pull` (Step 7), then `qmd update &amp;&amp; qmd embed`. Avoided entirely if Step 7 is done first |\n| Daemon shows old version after `npm install -g` | Daemon was running during upgrade | `remnic daemon stop &amp;&amp; remnic daemon start`, then restart MCP host |\n| Server returns 401 for ALL requests (`{\"error\":\"unauthorized\"}`) | `server.authToken` missing from config.json | Add `\"server\": { \"authToken\": \"\" }` to config. Generate hex via `node -e \"console.log(require('crypto').randomBytes(32).toString('hex'))\"`. Restart daemon |\n| Server log: `No auth token set \u2014 server will reject all requests` | Same as above \u2014 no `server.authToken` and no `REMNIC_AUTH_TOKEN` env var | Same fix |\n| Pi agent not calling recall/observe (no memory extraction from conversations) | Pi connector not installed, or installed with stale token | Run `remnic connectors install pi --force`. Verify `remnic connectors list` shows `\u2713 pi` |\n| `Incorrect API key provided: mnfst_...` in extraction logs | `OPENAI_BASE_URL` env var not set \u2014 SDK sends gateway key to real `api.openai.com` | `setx OPENAI_BASE_URL \"https://your-gateway/v1\"` then **reboot Windows** (daemon inherits env at spawn time) |\n| Extraction returns empty (`direct client returned no result`) | Gateway forces SSE streaming, or reasoning model fills `reasoning_content` not `content` | Disable streaming on gateway side. Ensure `extractionMaxOutputTokens` \u2265 4096 (default 16384 is fine) |\n| `extraction fallback returned no parsed output` | Same as above \u2014 LLM responded but content field was empty | Same fix. Verify with `curl` test (see Step 5 gateway notes) |\n| QMD recall uses `fallbackUsed: true` / `source: \"recent_scan\"` | QMD `index.yml` path \u2260 server's configured `memoryDir` | Align `index.yml` `openclaw-engram.path` to match `remnic.memoryDir` from config. Re-run `qmd update &amp;&amp; qmd embed` |\n| `ggml-cuda.cu:98: CUDA error` during QMD reranking | Intermittent GPU crash in node-llama-cpp reranker (RTX cards) | Rerun usually works. Persistent? Set `QMD_FORCE_CPU=1` env var, or use `qmd query --no-rerank` |\n| `qmd doctor` shows `model cache: invalid ... not valid GGUF` | Corrupted `.etag` sidecar from older QMD version | `qmd pull --refresh`, then delete orphan `*.etag` files in `~/.cache/qmd/models/` |\n| `remnic daemon status` says `Running: no` but `netstat` shows :4318 LISTENING | **Fixed in 9.7.15.** On \u22649.3.x the server detached without writing `.remnic/server.pid`, so `daemon status` lied. | Upgrade to \u22659.7.15 (PID file + status now correct). On old versions: use `netstat -ano \\| findstr :4318`; do not run `daemon start` again (spawns a 2nd process) |\n| `remnic daemon install` \u2192 `Unsupported platform: win32` | No Windows service support yet | Use Task Scheduler (see Persistence section below) |\n| Doctor warns `OpenClaw config file ... (not found)` | OpenClaw not used in this setup | Ignore. Do **not** run `remnic openclaw install` |\n| `cliProbeError=spawn /opt/homebrew/bin/qmd ENOENT` in startup log AND `cli=true` later | Cosmetic \u2014 macOS path is probed first before configured path resolves | Ignore. Only matters if `cli=false` persists |\n| `npm install -g @tobilu/qmd` fails with `spawn C:\\WINDOWS\\system32\\cmd.exe ENOENT` during `better-sqlite3` postinstall | Conflicting global package manager (bun / `@nubjs/nub` / pnpm / yarn) is intercepting build scripts | Detect via `where bun; where nub; where pnpm; where yarn`. Remove with `npm uninstall -g bun @nubjs/nub nub` (and equivalents for pnpm/yarn) and `rm -rf ~/.bun`. Re-run step 1 |\n| `extractWithDirectClient: failed to parse JSON from response (first 200 chars: [\ud83e\udd9a Manifest M302] Model \"gpt-5.5\" is not available for this agent...)` then `extraction fallback returned no parsed output` | Remnic hardcodes `DEFAULT_REASONING_MODEL = \"gpt-5.5\"` (chunk filename is version-specific \u2014 grep for it in `@remnic/core/dist/`) and your gateway does not serve that model | Add `remnic.model: \"\"` to server config. Verify what's served via `curl -s $OPENAI_BASE_URL/models -H \"Authorization: Bearer $OPENAI_API_KEY\"`. Restart daemon. See step 3 `model` field |\n| `QMD warmup: complete` followed by `QMD update failed for collection X: qmd update timed out after 90000ms` on first daemon start | QMD models not yet downloaded when the daemon's startup sync runs \u2014 **prevented by Step 7 (`qmd pull` before daemon start)** | If Step 7 was skipped: run `qmd pull`, then `qmd update &amp;&amp; qmd embed`, then restart the daemon |\n| `qmd pull` reaches 99% then crashes with `ENOENT: rename .ipull -&gt; .gguf` (Node `triggerUncaughtException`) | QMD temp-file race on the final rename step | Re-run `qmd pull` \u2014 other models are cached, only the failed one re-downloads. Second run completes the rename successfully |\n| `no gateway default AI and local LLM disabled \u2014 hourly summarization disabled` | `gatewayConfig.agents.defaults.model.primary` not set AND no local LLM AND `modelSource` is not `\"gateway\"`. The fallback client has no model to use for scheduled/background functions. | Add `gatewayConfig` with `provider/model` format (e.g., `\"openai/minimax-m3\"`) to the `remnic` config block. See Issue G. Do NOT set `modelSource: \"gateway\"` \u2014 that breaks extraction. |\n| `fallback LLM: invalid model format: minimax-m3` | Fallback client's `parseModelString` requires `provider/model` format (e.g., `openai/minimax-m3`). A bare model ID like `minimax-m3` has no `/` separator \u2192 rejected. | Change `gatewayConfig.agents.defaults.model.primary` to `\"provider/\"` and add a matching provider config in `gatewayConfig.models.providers`. |\n| `fallback LLM: no models configured in gateway` | Two causes: (1) model string format invalid (see above), or (2) provider config missing from `gatewayConfig.models.providers` so `parseModelString` can't resolve provider \u2192 empty model chain. | Fix model string format AND add provider override with `baseUrl`, `api`, `apiKey`, `models: []`. See Issue G. |\n| `fallback LLM: timed out after 2500ms` | Fallback client has a hard 2500ms timeout. Cloud reasoning models (minimax-m3, etc.) typically need 3-5s to respond. | No config override found as of 9.7.15. This affects scheduled functions (hourly summaries) but NOT extraction (uses direct client). Accept as known limitation or switch to a faster model. |\n| Daemon PID unchanged after Pi host \"restart\" (same PID on :4318) | `remnic daemon start` spawns a DETACHED process that survives Pi host restarts. The old daemon holds stale config in memory. | Kill the process explicitly: `Stop-Process -Id  -Force` from an elevated PowerShell, then `remnic daemon start`. Use `netstat -ano | findstr :4318` to confirm PID changed. |\n| `modelSource: \"gateway\"` set \u2192 extraction fails with `fallback LLM: no models configured in gateway` | `modelSource: \"gateway\"` routes extraction through the fallback client. If `gatewayConfig` is incomplete (wrong model format or missing provider), extraction breaks. | Remove `modelSource: \"gateway\"` from config (leave unset). Extraction uses the direct client with bare `remnic.model`. Add `gatewayConfig` separately for scheduled functions. |\n| `cliPathSource=auto-path` in daemon log | Daemon loaded a config WITHOUT `qmdPath` (likely the Pi extension template or old diverged config). Falls back to PATH lookup. | Check which path `remnic config` resolves to, ensure THAT file has `qmdPath` and full schema. Sync all 3 config files. See Issue A. |\n| `remnic daemon status` shows a PID but `netstat` shows :4318 LISTENING under a DIFFERENT PID | An orphan/second daemon holds the port while `daemon status` reads a stale `server.pid` | Kill the port-holder (`Stop-Process -Id  -Force`) then `remnic daemon start`. Use `netstat -ano \\| findstr :4318` for the real PID |\n\n---\n\n## Decision points (LLM should ask if ambiguous)\n\n| Question | When to ask | Default if no answer |\n| --- | --- | --- |\n| Which Node install root? | Multiple Node installs detected via `where node` | Use first result from `where qmd` |\n| Which collection name? | Daemon log shows name \u2260 `openclaw-engram` | Use whatever the log emits literally |\n| User wants `setx QMD_PATH` env var persistence? | User asks for fix to persist across reboots | Skip \u2014 config file in step 3 is sufficient |\n| Auto-start daemon on boot? | User wants persistence across reboots | Register Task Scheduler entry (see Persistence section). No native service path exists on Windows |\n| Which LLM gateway? | `openaiApiKey` in config is not a real OpenAI key (e.g. `mnfst_...` prefix) | Ask user for gateway URL. Set both `openaiBaseUrl` in config AND `OPENAI_BASE_URL` env var |\n| Gateway uses reasoning models? | Extraction returns empty content but non-empty `reasoning_content` | Ensure streaming is off on gateway side. Default `extractionMaxOutputTokens: 16384` handles reasoning overhead |\n\n---\n\n## Reference: file/path inventory\n\nAfter successful setup, these files exist:\n\n```\n%USERPROFILE%\\.config\\remnic\\config.json     # Remnic config (documented modern \u2014 \u2265 9.3.x)\n%USERPROFILE%\\.config\\engram\\config.json     # Legacy config path (pre-rename fallback)\n%USERPROFILE%\\.pi\\agent\\remnic.config.json   # Server config the newer Remnic actually reads at runtime (when Pi connector is installed). Mirror the schema here.\n%USERPROFILE%\\.pi\\agent\\extensions\\remnic\\remnic.config.json  # Pi EXTENSION config (connector-facing: recall/observe + remnic_p\u2026 token). Different file; do not overwrite with the server schema.\n%USERPROFILE%\\.config\\qmd\\index.yml          # QMD collections\n%USERPROFILE%\\.remnic\\server.log             # Daemon log\n%USERPROFILE%\\.remnic\\server.pid             # PID file \u2014 written correctly in \u22659.7.15 (was NOT written \u22649.3.x)\n%USERPROFILE%\\.remnic\\memory\\                # DEFAULT store (used ONLY if memoryDir is unset \u2014 RETIRE/avoid; see Issue A). Two coexisting stores = split-brain.\n%USERPROFILE%\\.engram\\engram.db              # SQLite store (legacy dir name retained)\n%USERPROFILE%\\.pi\\agent\\memory\\              # Pi agent memory\n%USERPROFILE%\\.pi\\agent\\.remnic\\memory\\      # CANONICAL Remnic memory store (set this as memoryDir in ALL configs + QMD index.yml)\n\\qmd                            # POSIX wrapper (don't use as qmdPath on Windows)\n\\qmd.cmd                        # Windows wrapper (USE THIS as qmdPath)\n\\node_modules\\@tobilu\\qmd\\      # QMD package\n\\node_modules\\@remnic\\cli\\      # Remnic package\n```\n\n`` = output of `npm root -g`, typically `C:\\nvm4w\\nodejs\\node_modules` or `%APPDATA%\\npm\\node_modules`.\n\n---\n\n## One-shot validation script\n\nRun this after all steps. Every line must pass:\n\n```bash\n# 1. QMD reachable\nqmd --version | grep -q \"qmd 2.5.3\" &amp;&amp; echo \"OK qmd\" || echo \"FAIL qmd\"\n# 1a. QMD models pulled (REQUIRED before embed/query \u2014 Step 7); need &gt;=3 GGUFs cached\ntest \"$(ls \"$USERPROFILE/.cache/qmd/models/\"*.gguf 2&gt;/dev/null | wc -l)\" -ge 3 &amp;&amp; echo \"OK qmd models\" || echo \"FAIL qmd models (run: qmd pull)\"\n\n# 2. Remnic version (&gt;= 9.3.644 required; verified through 9.7.15)\n# NOTE: a naive grep regex breaks across minor/patch boundaries (e.g. it misses 9.7.15). Use a real numeric compare:\nV=$(npm list -g @remnic/cli 2&gt;/dev/null | grep -oE \"@remnic/cli@[0-9]+\\.[0-9]+\\.[0-9]+\" | head -1 | cut -d@ -f3)\nnode -e \"const m='9.3.644'.split('.').map(Number),v=(process.argv[1]||'0.0.0').split('.').map(Number);const ok=v[0]&gt;m[0]||(v[0]===m[0]&amp;&amp;(v[1]&gt;m[1]||(v[1]===m[1]&amp;&amp;v[2]&gt;=m[2])));console.log(v.some(isNaN)||!ok?'FAIL remnic '+process.argv[1]+' (need &gt;= 9.3.644)':'OK remnic '+process.argv[1])\" \"$V\"\n\n# 0. Extraction model set (REQUIRED \u2014 Remnic defaults to gpt-5.5 which most gateways don't serve)\n# Resolve the live server config the same way `remnic config` does\nRESOLVED_CFG=$(remnic config 2&gt;/dev/null | head -1 | sed 's/^Config: //')\nif [ -n \"$RESOLVED_CFG\" ] &amp;&amp; [ -f \"$RESOLVED_CFG\" ]; then\n  node -e \"const c=require(process.argv[1]);const m=c?.remnic?.model;if(typeof m==='string'&amp;&amp;m.length&gt;0){console.log('OK extraction model = '+m)}else{console.log('FAIL extraction model: add remnic.model to '+process.argv[1]);process.exit(1)}\" \"$RESOLVED_CFG\" || true\nelse\n  echo \"FAIL extraction model: cannot resolve server config (run: remnic config)\"\nfi\n\n# 0a. gatewayConfig set (REQUIRED for scheduled/background functions)\n# Without gatewayConfig, hourly summarizer is silently disabled\nif [ -n \"$RESOLVED_CFG\" ] &amp;&amp; [ -f \"$RESOLVED_CFG\" ]; then\n  node -e \"const c=require(process.argv[1]);const g=c?.remnic?.gatewayConfig?.agents?.defaults?.model?.primary;if(typeof g==='string'&amp;&amp;g.length&gt;0){console.log('OK gateway model = '+g)}else{console.log('FAIL gatewayConfig: add gatewayConfig.agents.defaults.model.primary to '+process.argv[1]);process.exit(1)}\" \"$RESOLVED_CFG\" || true\nelse\n  echo \"FAIL gatewayConfig: cannot resolve server config\"\nfi\n\n# 3. Config files (accept any of the 3 paths)\n( test -f \"$USERPROFILE/.config/remnic/config.json\" || test -f \"$USERPROFILE/.config/engram/config.json\" || test -f \"$USERPROFILE/.pi/agent/remnic.config.json\" ) &amp;&amp; echo \"OK remnic config\" || echo \"FAIL remnic config\"\n# 3a. NO home-root config (cwd pick-first trap \u2014 silent split-brain if present)\ntest -e \"$USERPROFILE/remnic.config.json\" &amp;&amp; echo \"FAIL home-root config present (delete it)\" || echo \"OK no home-root config\"\ntest -e \"$USERPROFILE/engram.config.json\" &amp;&amp; echo \"FAIL home-root engram config present (delete it)\" || true\n# 3b. all legitimate configs byte-identical (no divergence)\nA=$(md5sum \"$USERPROFILE/.config/remnic/config.json\" 2&gt;/dev/null | cut -d' ' -f1)\nB=$(md5sum \"$USERPROFILE/.config/engram/config.json\" 2&gt;/dev/null | cut -d' ' -f1)\nC=$(md5sum \"$USERPROFILE/.pi/agent/remnic.config.json\" 2&gt;/dev/null | cut -d' ' -f1)\n[ -n \"$A\" ] &amp;&amp; [ \"$A\" = \"$B\" ] &amp;&amp; [ \"$A\" = \"$C\" ] &amp;&amp; echo \"OK configs identical\" || echo \"FAIL configs diverge (sync all 3)\"\ntest -f \"$USERPROFILE/.config/qmd/index.yml\" &amp;&amp; echo \"OK qmd config\" || echo \"FAIL qmd config\"\n\n# 4. Server authToken present in config (REQUIRED \u2014 without it ALL requests get 401)\n# Check ALL three candidate paths, not just one\ngrep -q \"authToken\" \"$USERPROFILE/.config/remnic/config.json\" 2&gt;/dev/null \\\n  || grep -q \"authToken\" \"$USERPROFILE/.config/engram/config.json\" 2&gt;/dev/null \\\n  || grep -q \"authToken\" \"$USERPROFILE/.pi/agent/remnic.config.json\" 2&gt;/dev/null \\\n  &amp;&amp; echo \"OK auth token\" || echo \"FAIL auth token (add server.authToken to config)\"\n\n# 5. Pi connector installed (REQUIRED for Pi agent recall/observe)\ntest -f \"$USERPROFILE/.pi/agent/extensions/remnic/remnic.config.json\" &amp;&amp; echo \"OK pi connector\" || echo \"FAIL pi connector (run: remnic connectors install pi)\"\n\n# 6. LLM gateway env vars present (REQUIRED for extraction)\ntest -n \"$OPENAI_API_KEY\" &amp;&amp; echo \"OK api key\" || echo \"FAIL api key env\"\ntest -n \"$OPENAI_BASE_URL\" &amp;&amp; echo \"OK base url\" || echo \"FAIL base url env (setx then reboot)\"\n\n# 7. Daemon actually listening (`netstat` is authoritative; `remnic daemon status` also works in \u22659.7.15)\nnetstat -ano | findstr \":4318\" | findstr -q LISTENING &amp;&amp; echo \"OK daemon port\" || echo \"FAIL daemon port\"\n\n# 8. Backend probe in log\ngrep -q \"Search backend: available cli=true daemon=true session=true\" \"$USERPROFILE/.remnic/server.log\" &amp;&amp; echo \"OK backend\" || echo \"FAIL backend\"\n\n# 9. Collection indexed\nqmd update 2&gt;&amp;1 | grep -q \"All collections updated\" &amp;&amp; echo \"OK collections\" || echo \"FAIL collections\"\n\n# 10. End-to-end memory roundtrip\nremnic query \"test\" --json 2&gt;&amp;1 | grep -q '\"results\"' &amp;&amp; echo \"OK query\" || echo \"FAIL query\"\n\n# 11. LLM extraction working (no 401, facts auto-persisted)\ngrep -q \"persisted:.*facts\" \"$USERPROFILE/.remnic/server.log\" &amp;&amp; echo \"OK extraction\" || echo \"FAIL extraction (check OPENAI_BASE_URL + remnic.model + gateway streaming)\"\n\n# 12. Hourly summarizer enabled (no \"disabled\" warning in startup log)\n# Check the most recent startup for the hourly summarizer warning\ngrep -nE \"hourly summariz\" \"$USERPROFILE/.remnic/server.log\" | tail -1 | grep -q \"disabled\" &amp;&amp; echo \"FAIL hourly summarizer (add gatewayConfig, see Issue G)\" || echo \"OK hourly summarizer enabled\"\n\n# 13. Dreams pipeline running (lightSleep + deepSleep have runCount &gt; 0)\n# This requires MCP; skip if not available. LightSleep/deepSleep run on cadence, give it time.\n```\n\nEvery numbered line above must print `OK ...`. Any `FAIL` \u2192 see Troubleshooting matrix.\n\n---\n\n## MCP Tool Validation Results (Tested 2026-07-18; re-verified 2026-07-31 on @remnic/cli 9.7.15 / qmd 2.5.3 / Node v24.14.0)\n\nAll Remnic MCP tools were exercised against a live Windows daemon. Below: tools that work, tools that don't, and exact fixes.\n\n### Overall health\n\n- **Core recall/store/observe**: 100% functional\n- **QMD search**: functional (`memory_search` returns results, `recall` returns 0 because namespace is empty of matching memories)\n- **Work layer (tasks/projects/boards)**: 100% functional\n- **Config-gated features**: ~15 tools disabled by default \u2014 not bugs, need config overrides or preset switch\n- **Real bugs found**: 2 originally \u2014 config divergence (Issue A) **RESOLVED 2026-07-31** (root cause = cwd-resolution + home-root rogue config; fix documented in Issue A); MCP host field injection (Issue B) status unchanged as of 9.7.15\n\n### What works (no issues)\n\n| Category | Tools |\n|----------|-------|\n| Core recall | `recall`, `recall_explain`, `recall_tier_explain`, `recall_xray`, `memory_search`, `memory_get`, `memory_timeline` |\n| Memory write | `memory_store`, `observe`, `suggestion_submit`, `memory_promote`, `memory_outcome` |\n| Debug/introspection | `console_state`, `memory_last_recall`, `memory_intent_debug`, `memory_qmd_debug`, `memory_graph_explain`, `profiling_report`, `dreams_status` |\n| Work layer | `work_task` (all CRUD), `work_project` (all CRUD), `work_board` (export) |\n| Lifecycle maintenance | `memory_governance_run`, `procedure_mining_run`, `contradiction_scan_run`, `memory_summarize_hourly` |\n| Dreams pipeline | `dreams_run` (lightSleep/REM/deepSleep, dry-run) |\n| Peer registry | `peer_list`, `peer_set`, `peer_get`, `peer_delete` |\n| Query/entity | `entity_get`, `review_list`, `review_queue_list`, `capsule_list` |\n| Continuity (read) | `continuity_incident_list` (reports disabled cleanly) |\n| Identity | `memory_identity`, `identity_anchor_get` |\n| Correction | `memory_correct_plan`, `memory_correct_apply` |\n\n### Issues found and fixes\n\n#### Issue A \u2014 Config file divergence (CRITICAL)\n\n**Symptom:** `remnic config` resolves to `%USERPROFILE%\\.pi\\agent\\remnic.config.json`, but `.config\\remnic\\config.json` exists with **different** gateway credentials and no `model` field. LLM-dependent features (extraction, briefing follow-ups, correction planner) fail with `[\ud83e\udd9a Manifest M302] Model \"gpt-5.5\" is not available`. Graph recall stays disabled even after changing `memoryOsPreset` to `research-max`.\n\n**Root cause:** Three config files exist, but the **CLI and Pi embedded daemon resolve DIFFERENT paths**:\n\n- CLI command `remnic config` \u2192 `.pi\\agent\\remnic.config.json`\n- Pi embedded daemon (MCP host) \u2192 `.config\\remnic\\config.json`\n\nWhen they have different values, CLI tools see one config and MCP tools see another. The Pi daemon was loading `.config\\remnic\\config.json` (old Manifest gateway, `balanced` preset, no `model`), while CLI showed `.pi\\agent\\remnic.config.json` (updated Ollama gateway, `research-max` preset).\n\n**Deeper mechanism (verified 2026-07-31):** `resolveConfigPath()` checks `process.cwd()/remnic.config.json` **FIRST**, before any `~/.config/...` path. So whichever process launches the daemon determines the winner via its working directory. The most insidious case is a **fourth** file \u2014 a stray `remnic.config.json` sitting in `%USERPROFILE%` (the home root) \u2014 which wins whenever the daemon is launched from home (login Task Scheduler entry, manual `daemon start` from `~`, some MCP hosts). That rogue file typically has no `model` (\u2192 `gpt-5.5` 404), a different `memoryDir` (\u2192 a second, near-empty memory store at `~/.remnic/memory`), and `${OPENAI_API_KEY}`/`${REMNIC_AUTH_TOKEN}` placeholders. Symptom triplet: extraction errors with `model \"gpt-5.5\"`, recall returns paths under a different memory tree, and `remnic status` shows 401 (rogue config's empty token).\n\n**Fix:**\n\n1. **Delete any home-root config** (the cwd-pick-first trap \u2014 this is the step most guides miss):\n\n   ```bash\n   # MUST be absent. A home-root remnic.config.json is always a bug.\n   test -e \"$USERPROFILE/remnic.config.json\" &amp;&amp; mv \"$USERPROFILE/remnic.config.json\" \"$USERPROFILE/remnic.config.json.rogue-bak\" || echo \"no home-root config (good)\"\n   test -e \"$USERPROFILE/engram.config.json\" &amp;&amp; mv \"$USERPROFILE/engram.config.json\" \"$USERPROFILE/engram.config.json.rogue-bak\" || true\n   ```\n\n2. Sync ALL THREE legitimate config files to identical content:\n   - `%USERPROFILE%\\.config\\remnic\\config.json`\n   - `%USERPROFILE%\\.config\\engram\\config.json` (legacy fallback)\n   - `%USERPROFILE%\\.pi\\agent\\remnic.config.json`\n\n3. **Reconcile `memoryDir` if a divergent store was created.** If the rogue config wrote to `~/.remnic/memory` (the daemon default) while the canonical config uses `.pi\\agent\\.remnic\\memory`, merge any unique memory files (facts/checkpoints/corrections) from the abandoned store into the canonical one, point all configs + QMD `index.yml` at the canonical `memoryDir`, then archive the abandoned store and run `qmd update &amp;&amp; qmd embed`. Verify the abandoned store gets ZERO new writes after restart.\n\n4. After ANY config change, restart **both** the standalone daemon AND the Pi CLI.\n\n```bash\n# Sync all three (run from the directory where your master config lives)\ncp \"%USERPROFILE%\\.pi\\agent\\remnic.config.json\" \"%USERPROFILE%\\.config\\remnic\\config.json\"\ncp \"%USERPROFILE%\\.pi\\agent\\remnic.config.json\" \"%USERPROFILE%\\.config\\engram\\config.json\"\n\n# Verify\nremnic config | head -1\n# \u2192 Config: C:\\Users\\\\.pi\\agent\\remnic.config.json\n\n# Restart standalone daemon\nremnic daemon stop\nremnic daemon start\n\n# Then restart Pi CLI for MCP-visible changes to take effect\n```\n\n**Verify which config the Pi daemon actually loaded:**\n\n```bash\n# Check if qmdPath is configured (not auto-path)\ngrep -i \"cliPathSource\" \"%USERPROFILE%\\.remnic\\server.log\" | tail -1\n# expect: cliPathSource=configured  (not auto-path)\n\n# Check graph recall state via MCP\nremnic_recall_explain\n# expect: graphDecision.graphRecallEnabled=true, multiGraphMemoryEnabled=true\n```\n\n#### Issue B \u2014 MCP host injects `sessionKey` / `cwd` into tool calls (BUG)\n\n**Symptom:** Two tools fail with validation error:\n\n```\nrequest validation failed: (root): Unrecognized key(s) in object: 'sessionKey', 'cwd'\n```\n\n**Affected tools:**\n\n- `remnic_action_confidence`\n- `remnic_capsule_export`\n\n**Root cause:** The Pi MCP host injects `sessionKey` and `cwd` metadata fields into every tool call. These tools' JSON schemas have `additionalProperties: false`, rejecting the injected fields.\n\n**Fix:** This is a Remnic server-side schema bug. Report to maintainers. Workaround: use these tools via CLI (`remnic capsule export`, direct HTTP) instead of MCP.\n\n#### Issue C \u2014 Graph recall disabled by preset\n\n**Symptom:** `remnic_recall_explain` shows `graphRecallEnabled: false`, `multiGraphMemoryEnabled: false`, and graph section says `\"reason\": \"graph recall disabled by config\"`.\n\n**Root cause:** `memoryOsPreset: \"balanced\"` sets both flags to `false`. Only `research-max` preset enables them.\n\n**Fix:** Switch preset in the config file the daemon reads:\n\n```json\n{\n  \"remnic\": {\n    \"memoryOsPreset\": \"research-max\",\n    ...\n  }\n}\n```\n\nThen **restart the Pi CLI** (MCP host) \u2014 the host caches config at startup. Daemon restart alone is insufficient for MCP-visible changes.\n\n**Presets compared:**\n\n| Feature | `balanced` | `research-max` |\n|---------|-----------|----------------|\n| `graphRecallEnabled` | false | **true** |\n| `multiGraphMemoryEnabled` | false | **true** |\n| `graphAssistInFullModeEnabled` | false | **true** |\n| `proactiveExtractionEnabled` | false | **true** |\n| `contextCompressionActionsEnabled` | false | **true** |\n| `compressionGuidelineLearningEnabled` | false | **true** |\n| `lcmEnabled` | false | **true** |\n| `explicitCueRecallEnabled` | false | **true** |\n| `maxProactiveQuestionsPerExtraction` | 2 | **4** |\n| `maxCompressionTokensPerHour` | 1500 | **3000** |\n| `behaviorLoopAutoTuneEnabled` | false | **true** |\n\n**Caveat:** `research-max` enables everything \u2014 more LLM calls, more disk I/O, more background processing. For production use, prefer individual overrides instead:\n\n```json\n{\n  \"remnic\": {\n    \"memoryOsPreset\": \"balanced\",\n    \"graphRecallEnabled\": true,\n    \"multiGraphMemoryEnabled\": true,\n    \"feedbackEnabled\": true,\n    \"contextCompressionActionsEnabled\": true\n  }\n}\n```\n\nUser config values override preset values due to `{ ...preset, ...baseCfg }` merge in `parseConfig`.\n\n#### Issue D \u2014 LLM-dependent features degraded (gateway-side)\n\n**Symptom:** `remnic_briefing` generates sections but follow-ups fail with:\n\n```\nLLM follow-ups failed: Unexpected token '\ud83e\udd9a', \"[\ud83e\udd9a Manifest M302]\" is not valid JSON\n```\n\nAlso: `remnic_memory_correct_plan` warns:\n\n```\nLLM unavailable: correction classify+draft: local LLM unavailable\n```\n\n**Root cause (original finding \u2014 RESOLVED 2026-07-21, reconfirmed 2026-07-31):** Earlier testing concluded `openaiBaseUrl: \"https://ollama.com/v1\"` was the Ollama **website**, not the API endpoint.\n\n**Correction (verified 2026-07-21):** `https://ollama.com/v1` IS a valid OpenAI-compatible API endpoint. Live tests confirmed:\n\n- `GET /v1/models` \u2192 HTTP 200, returns JSON model list (including `minimax-m3`)\n- `POST /v1/chat/completions` with `minimax-m3` \u2192 HTTP 200, returns valid JSON with `content` field populated\n\nThe original failure was caused by **config divergence** (Issue A), NOT the URL being wrong. The daemon was loading a config WITHOUT `remnic.model`, so extraction fell back to hardcoded `gpt-5.5` \u2192 `not_found_error`. The HTTP response was HTML only because the gateway returned an error page for the missing model, not because the URL was a marketing page.\n\n**Fix:** If using local Ollama (no `ollama.com` cloud account):\n\n```json\n\"openaiBaseUrl\": \"http://localhost:11434/v1\",\n\"openaiApiKey\": \"ollama\"\n```\n\nIf using **Ollama cloud** (`ollama.com/v1`) or any other cloud gateway (Manifest, OpenRouter, etc.), ensure `openaiBaseUrl` matches the gateway's actual API endpoint AND that `remnic.model` is set to a model the gateway serves (see Issue G below).\n\n#### Issue E \u2014 Features disabled by config (expected, not bugs)\n\nThese tools return `\"enabled\": false` because they are gated by config flags. They are not broken \u2014 they need explicit opt-in:\n\n| Tool | Config flag to enable |\n|------|----------------------|\n| `memory_feedback` | `feedbackEnabled: true` |\n| `memory_action_apply` | `contextCompressionActionsEnabled: true` |\n| `continuity_incident_open` / `continuity_loop_add_or_update` | `identityContinuityEnabled: true` |\n| `identity_anchor_update` | `identityContinuityEnabled: true` |\n| `shared_context_write_output` / `shared_feedback_record` / `shared_priorities_append` / `shared_context_cross_signals_run` / `shared_context_curate_daily` | `sharedContextEnabled: true` |\n| `compounding_weekly_synthesize` / `compounding_promote_candidate` | `compoundingEnabled: true` |\n| `compression_guidelines_optimize` / `compression_guidelines_activate` | `compressionGuidelineLearningEnabled: true` |\n| `conversation_index_update` / `lcm_search` / `lcm_compaction_flush` / `lcm_compaction_record` | `lcmEnabled: true` |\n| `wearables_sync` | `wearables.enabled: true` |\n| `graph_edge_decay_run` | `graphEdgeDecayEnabled: true` |\n| `pattern_reinforcement_run` | `patternReinforcementEnabled: true` |\n| `live_connectors_run` | Per-connector config in `connectors:` block |\n\nAll of the above are enabled by default in `research-max` preset. Individual overrides also work.\n\n#### Issue F \u2014 `work_task` id type must be string\n\n**Symptom:** Passing `id: 1` (number) to `remnic_work_task` update fails with:\n\n```\n- id: must be string\n```\n\n**Fix:** Pass `id` as a string: `\"task-1784348023747-...\"` not `1`. The task IDs are auto-generated strings returned by `work_task` create.\n\n#### Issue G \u2014 Scheduled/background functions silently disabled without `gatewayConfig` (CRITICAL)\n\n**Symptom:** Server log at startup:\n\n```\nremnic: no gateway default AI and local LLM disabled \u2014 hourly summarization disabled\n```\n\nExtraction works (facts persisted via direct client), but scheduled/background functions that rely on the **fallback LLM client** are silently disabled:\n\n- **Hourly summarizer** \u2014 explicitly disabled by the check\n- **Dreams REM phase** \u2014 0 runs (may need the fallback client)\n- Other background LLM tasks that route through `FallbackLlmClient`\n\nDreams `lightSleep` and `deepSleep` phases still run (they process memory files locally, not via LLM).\n\n**Root cause:** Remnic has TWO separate LLM code paths:\n\n| Path | Used by | Config field | Model format |\n|------|---------|-------------|-------------|\n| **Direct client** | Extraction (fact/entity/profile extraction) | `remnic.model` (string) | Bare model ID, e.g. `minimax-m3` |\n| **Fallback client** (`FallbackLlmClient`) | Hourly summaries, some Dreams phases, calibration, causal consolidation | `gatewayConfig.agents.defaults.model.primary` | `provider/model` format, e.g. `openai/minimax-m3` |\n\nThe `HourlySummarizer` constructor (`@remnic/core/src/summarizer.ts:73`) checks:\n\n```typescript\nif (!gatewayConfig?.agents?.defaults?.model?.primary &amp;&amp; !resolveLocalLlmCapabilities(config).localLlm &amp;&amp; config.modelSource !== \"gateway\") {\n  log.warn(\"no gateway default AI and local LLM disabled \u2014 hourly summarization disabled\");\n}\n```\n\nWhen ALL THREE conditions are true (no `gatewayConfig` model, no local LLM, `modelSource \u2260 \"gateway\"`), the summarizer is disabled. Setting `remnic.model` alone does NOT satisfy this check \u2014 it only feeds the direct client.\n\n**\u26a0 Do NOT set `modelSource: \"gateway\"` as a workaround.** While it satisfies the third condition (silencing the warning), it also routes **extraction** through the fallback client instead of the direct client. The fallback client's `parseModelString` requires `provider/model` format \u2014 a bare `minimax-m3` \u2192 `fallback LLM: invalid model format` \u2192 extraction breaks. You get the summarizer enabled but extraction broken. The correct fix is `gatewayConfig` (below).\n\n**Fix:** Add `gatewayConfig` to the `remnic` config block. The model string MUST be in `provider/model` format. The provider config overrides the built-in `openai` defaults (which point at `api.openai.com/v1`):\n\n```json\n{\n  \"remnic\": {\n    \"model\": \"minimax-m3\",\n    \"gatewayConfig\": {\n      \"agents\": {\n        \"defaults\": {\n          \"model\": {\n            \"primary\": \"openai/minimax-m3\"\n          }\n        }\n      },\n      \"models\": {\n        \"providers\": {\n          \"openai\": {\n            \"baseUrl\": \"https://your-gateway.example.com/v1\",\n            \"api\": \"openai-completions\",\n            \"apiKey\": \"same-key-as-openaiApiKey\",\n            \"models\": []\n          }\n        }\n      }\n    }\n  }\n}\n```\n\nKey details:\n\n- `gatewayConfig.agents.defaults.model.primary` must be `\"openai/\"` (with `/` separator). The `openai` prefix maps to the provider config.\n- `gatewayConfig.models.providers.openai.baseUrl` overrides the built-in default (`api.openai.com/v1`). Point at your actual gateway.\n- `gatewayConfig.models.providers.openai.api` must be `\"openai-completions\"` for OpenAI-compatible chat completions. Do NOT use `\"openai-responses\"` (the built-in default) unless your gateway serves the Responses API.\n- `gatewayConfig.models.providers.openai.apiKey` can be the same value as `remnic.openaiApiKey`. The fallback client resolves it via `resolveProviderApiKey` which checks provider config first.\n- Do NOT set `remnic.modelSource` \u2014 leave it unset. Extraction uses the direct client (with bare `remnic.model`), scheduled functions use the fallback client (with `provider/model` format from `gatewayConfig`).\n\n**Verify after fix + restart:**\n\n```bash\n# Startup log should show \"hourly summarizer initialized\" WITHOUT the disabled warning\ngrep -E \"hourly summarizer\" \"$USERPROFILE/.remnic/server.log\" | tail -2\n# expect: hourly summarizer initialized   (NOT \"hourly summarization disabled\")\n\n# Fallback client should recognize the model (no \"invalid model format\")\ngrep -E \"invalid model format|no models configured\" \"$USERPROFILE/.remnic/server.log\" | tail -2\n# expect: no matches (or old entries only)\n\n# Dreams status should show activity\nremnic_dreams_status\n# expect: lightSleep and deepSleep runCount &gt; 0\n```\n\n**Known limitation \u2014 fallback client timeout:** After fixing the model format, the fallback client may still fail with `fallback LLM: timed out after 2500ms`. The fallback client has a hard 2500ms timeout which is too short for reasoning models on cloud gateways (direct client gets responses in 3-5s). This affects hourly summary generation but does NOT affect extraction (which uses the direct client). This is a known issue \u2014 no config override found as of 9.7.15.\n\n### Config file after testing (verified working)\n\nThis is the minimal config that resolves Issues A through G (Ollama-cloud example \u2014 `minimax-m3` verified working 2026-07-31; substitute your gateway's served model ID \u2014 Manifest uses `auto`):\n\n```json\n{\n  \"qmdPath\": \"C:\\\\nvm4w\\\\nodejs\\\\qmd.cmd\",\n  \"remnic\": {\n    \"qmdPath\": \"C:\\\\nvm4w\\\\nodejs\\\\qmd.cmd\",\n    \"openaiApiKey\": \"\",\n    \"openaiBaseUrl\": \"https://your-gateway.example.com/v1\",\n    \"model\": \"minimax-m3\",\n    \"memoryDir\": \"C:\\\\Users\\\\\\\\.pi\\\\agent\\\\.remnic\\\\memory\",\n    \"memoryOsPreset\": \"research-max\",\n    \"feedbackEnabled\": true,\n    \"identityContinuityEnabled\": true,\n    \"sharedContextEnabled\": true,\n    \"compoundingEnabled\": true,\n    \"compressionGuidelineLearningEnabled\": true,\n    \"lcmEnabled\": true,\n    \"graphEdgeDecayEnabled\": true,\n    \"patternReinforcementEnabled\": true,\n    \"wearables\": { \"enabled\": true },\n    \"gatewayConfig\": {\n      \"agents\": {\n        \"defaults\": {\n          \"model\": {\n            \"primary\": \"openai/minimax-m3\"\n          }\n        }\n      },\n      \"models\": {\n        \"providers\": {\n          \"openai\": {\n            \"baseUrl\": \"https://your-gateway.example.com/v1\",\n            \"api\": \"openai-completions\",\n            \"apiKey\": \"\",\n            \"models\": []\n          }\n        }\n      }\n    }\n  },\n  \"server\": {\n    \"host\": \"127.0.0.1\",\n    \"port\": 4318,\n    \"authToken\": \"&lt;64-char-hex&gt;\"\n  },\n  \"engram\": { \"qmdPath\": \"C:\\\\nvm4w\\\\nodejs\\\\qmd.cmd\" }\n}\n```\n\n&gt; \u26a0 **After any config change, restart BOTH the daemon AND the Pi CLI** for MCP-visible changes to take effect. Daemon restart alone only affects CLI commands, not the MCP tool surface.\n\n---\n\n## Background context (skip if executing only)\n\n- The Windows blocker is GitHub issue [joshuaswarren/remnic#1476](https://github.com/joshuaswarren/remnic/issues/1476), fixed in PR #1486 via `cross-spawn` in 9.3.644.\n- Pre-fix, `child_process.spawn` on Windows could not exec `.cmd` (EINVAL), POSIX shell wrappers (ENOENT), or bare JS files (ENOENT).\n- Post-fix, all three launcher forms work, but `.cmd` is the canonical choice on Windows.\n- Remnic auto-probe still falls back to `/opt/homebrew/bin/qmd` if PATH lookup fails \u2014 this is harmless once the configured path takes priority via step 3.\n- The `engram \u2192 remnic` rename moved the config dir from `.config/engram/` to `.config/remnic/` but kept the SQLite store at `%USERPROFILE%\\.engram\\engram.db` and the daemon log at `%USERPROFILE%\\.remnic\\server.log`. The mismatch is intentional \u2014 do not try to consolidate them.\n- **Windows status bugs, version-split:** On \u22649.3.x the daemon detached without writing `.remnic/server.pid`, so `remnic daemon status` reported `Running: no` even while listening \u2014 on those versions use `netstat`. **Fixed in 9.7.15**: the PID file is written and `daemon status` reports correctly (verified 2026-07-31). Still no `daemon install` service support on Windows (use Task Scheduler \u2014 see Persistence). The cosmetic `cliProbeError=spawn /opt/homebrew/bin/qmd ENOENT` macOS-path probe still appears in startup logs and is harmless.\n- **Server config path divergence (\u2265 9.3.7xx):** `remnic config` may resolve to `%USERPROFILE%\\.pi\\agent\\remnic.config.json` instead of the documented `%USERPROFILE%\\.config\\remnic\\config.json` \u2014 because `resolveConfigPath()` checks `process.cwd()/remnic.config.json` first (see the cwd-resolution rule above). If that file holds `${OPENAI_API_KEY}` / `${REMNIC_AUTH_TOKEN}` placeholders and no `qmdPath` / `openaiBaseUrl` / `model`, the server loads a placeholder config \u2192 `cliPathSource=auto-path` + `no gateway default AI`. Fix: overwrite THAT resolved file with the full schema (real values, not env placeholders). Note: `remnic connectors install pi` writes its connector-facing file at `.pi\\agent\\extensions\\remnic\\remnic.config.json` (recall/observe settings + a `remnic_p\u2026` token) \u2014 it does NOT write `.pi\\agent\\remnic.config.json`; the two files are distinct and the server config is always user-authored.\n- **Config resolution is cwd-dependent (root cause of all split-brain, verified 2026-07-31):** `resolveConfigPath()` order is `--config` flag \u2192 `REMNIC_CONFIG_PATH`/`ENGRAM_CONFIG_PATH` env \u2192 `process.cwd()/remnic.config.json` \u2192 `process.cwd()/engram.config.json` \u2192 `~/.config/remnic/config.json` \u2192 `~/.config/engram/config.json`. The `process.cwd()` candidates come FIRST, so the daemon's effective config depends on its launch working directory. A stray `remnic.config.json` in the home root (`%USERPROFILE%`) wins for any daemon launched from home and silently overrides the canonical config \u2014 producing a second memory store (`~/.remnic/memory`, the daemon default), `gpt-5.5` extraction 404s, and `remnic status` 401. Hard rule: never keep a config in the home root; delete it so fallthrough always reaches `~/.config/remnic/config.json`.\n- **Corrupt `meta.json` \u2192 silent per-extraction noise:** `state/meta.json` (extraction retry ledger) can be written as all-NUL bytes (e.g. 136K of `\\0`) after a crash/aborted write. `JSON.parse` then throws `SyntaxError: Unexpected token ' '` on every extraction (`runExtraction: failed to load meta for retry bookkeeping (non-fatal)`). It is non-fatal, but it disables retry bookkeeping and floods the log. Fix: move the file aside (`meta.json` \u2192 `meta.json.nul-corrupt.bak`); the server recreates a fresh valid one on the next write.\n- **Extraction model default is `gpt-5.5` (hardcoded):** `DEFAULT_REASONING_MODEL = \"gpt-5.5\"`. The chunk filename is **version-specific** \u2014 it was `chunk-2HAG7JOI.js:1067` in 9.3.x and is `chunk-INELTPRG.js:1187` in 9.7.15. Do not trust a pinned filename; find yours with `grep -rn \"DEFAULT_REASONING_MODEL =\" \"$(npm root -g)/@remnic/cli/node_modules/@remnic/core/dist/\"`. The override key is `remnic.model` (string) in the server config \u2014 parseConfig: `const explicitModel = typeof cfg.model === \"string\" &amp;&amp; cfg.model.length &gt; 0 ? cfg.model : \"\"; const model = explicitModel || DEFAULT_REASONING_MODEL;`. Most non-OpenAI gateways do NOT serve `gpt-5.5` \u2192 extraction 404s with a gateway-specific error (Manifest `M302`, Ollama-cloud/OpenRouter `404 model \"gpt-5.5\" not found`, etc.). Always set `remnic.model` to a model your gateway actually serves.\n- **`setx` vs shell `export` for daemon env:** `setx` writes to `HKCU\\Environment` (persists, future processes). But `remnic daemon start` forks a detached process that inherits the *spawning shell's* env, not the registry. So in the same session you must `export` the vars into the shell before `daemon start`, or reboot. See step 6 workaround.\n- **Detached daemon survives Pi host restarts:** `remnic daemon start` spawns a detached `node` process (not a child of the Pi host). When the Pi host \"restarts\", this process survives and holds stale config in memory. If the daemon doesn't pick up config changes after restart, check `netstat -ano | findstr :4318` \u2014 if the PID hasn't changed, the old daemon is still running. Kill it from an elevated shell: `Stop-Process -Id  -Force` (or `taskkill //F //PID `), then start fresh.\n- **Two LLM code paths (direct vs fallback):** Remnic routes LLM calls through two different clients:\n  - **Direct client** \u2014 used for extraction (facts, entities, profiles). Reads `remnic.model` (bare model ID, e.g. `minimax-m3`). Built on the OpenAI SDK directly.\n  - **Fallback client** (`FallbackLlmClient`) \u2014 used for hourly summaries, some Dreams phases, calibration, causal consolidation. Reads `gatewayConfig.agents.defaults.model.primary` (must be `provider/model` format, e.g. `openai/minimax-m3`). Supports multiple providers and model chains.\n  These are independent. Setting `remnic.model` alone does NOT satisfy the fallback client's needs. Conversely, setting `modelSource: \"gateway\"` forces extraction onto the fallback client, which will break if the model string isn't in `provider/model` format. Leave `modelSource` unset and configure both `model` (direct) and `gatewayConfig` (fallback) separately.\n- **Fallback client model string format:** `parseModelString` in `@remnic/core/src/fallback-llm.ts` splits on `/` and expects `provider/model`. A bare model ID like `minimax-m3` \u2192 `fallback LLM: invalid model format` \u2192 empty model chain \u2192 `fallback LLM: no models configured in gateway`. The `provider` prefix must map to a provider config in `gatewayConfig.models.providers` (e.g., `openai` \u2192 `{ baseUrl, api, apiKey, models: [] }`). Built-in providers (`openai`, `anthropic`) default to their public URLs \u2014 override `baseUrl` to point at your gateway.\n- **Fallback client 2500ms timeout:** After fixing model format and provider config, the fallback client may still fail with `fallback LLM: timed out after 2500ms`. This is a hardcoded timeout in the fallback client (`@remnic/core/src/fallback-llm.ts`). Reasoning models on cloud gateways typically need 3-5s. This affects scheduled functions (hourly summaries) but NOT extraction (which uses the direct client). No config override found as of 9.7.15.\n- **`cliPathSource=auto-path` diagnostic signal:** If the daemon startup log shows `cliPathSource=auto-path` (instead of `configured`), it loaded a config WITHOUT `qmdPath`. This means it fell back to PATH lookup. Common causes: (1) Pi extension template loaded instead of real config, (2) wrong config file, (3) config file missing `qmdPath`. Fix: check which path `remnic config` resolves to, ensure THAT file has `qmdPath`.\n- **Dreams pipeline cadence:** `lightSleep` and `deepSleep` run on a timer (every few hours). `rem` phase runs less frequently and may need more memory volume or longer daemon uptime before triggering. Zero runs in the first few hours is normal.\n\n---\n\n## Persistence (optional \u2014 auto-start daemon on boot)\n\nNo native Windows service path exists. Register a Task Scheduler entry:\n\n```powershell\n$action  = New-ScheduledTaskAction -Execute \"C:\\nvm4w\\nodejs\\remnic.cmd\" -Argument \"daemon start\"\n$trigger = New-ScheduledTaskTrigger -AtLogOn -User \"$env:USERNAME\"\n$settings = New-ScheduledTaskSettingsSet -StartWhenAvailable -DontStopOnIdleEnd\nRegister-ScheduledTask -TaskName \"Remnic Daemon\" -Action $action -Trigger $trigger -Settings $settings -Description \"Start Remnic memory daemon at login\"\n```\n\n**Verify after next login:**\n\n```bash\nnetstat -ano | findstr \":4318\" | findstr LISTENING\n```\n\nRemove with: `Unregister-ScheduledTask -TaskName \"Remnic Daemon\" -Confirm:$false`\n\n---\n\n**End of instructions.** Execute steps 1\u201310 sequentially, verify after each, stop on first failure and consult the matrix.\n", "creation_timestamp": "2026-07-31T05:50:06.151340Z"}, {"uuid": "7ec3dbe3-0265-46df-b908-05c5f730b6eb", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2024-27980", "type": "seen", "source": "https://gist.github.com/ohad6k/586cd3e78282e37c1ecb930534637fb6", "content": "# What agents actually commit\n\nA three step agentic pipeline built for ctaio.dev. It reads commits that AI coding tools\nauthored in public repositories, identified by the trailers the tools write themselves\nrather than by anyone's claim, has an agent find one defensible claim in them, and renders\nthe result as a page.\n\n## The steps\n\n1. **ingest.py** shells out to `gh api search/commits` for five agent tool repos and keeps\n   commits carrying an agent trailer. Deterministic, no model involved. Output:\n   `out/1-ingested.json`.\n2. **transform.py** aggregates those rows into counts first, then hands only the counts to\n   Claude Code over stdin with a fixed question. The agent is asked for one non obvious\n   claim, the number behind it, and what would falsify it. Output: `out/2-facts.json`,\n   `out/2-finding.md`.\n3. **publish.py** renders the finding plus the underlying tables into a standalone page.\n   Output: `out/3-post.html`.\n\nThe handoff that matters is between 1 and 2. The agent never counts anything. Counting is\ndone in Python where it is checkable, and the model is only asked to interpret counts it\ncannot silently get wrong.\n\n## What it found\n\nIn the repositories of the agent tool vendors themselves, trailered commits skew to repair\nrather than construction: `fix` 40 against `feat` 30 across 97 commits, with only 6\ncarrying a `test:` prefix. 67 of the 97 come from repos that are themselves the tools.\n\nFalsifier, stated by the agent and not by me: pull the same prefixes from the same repos\nover the same window excluding trailered commits. If humans sit near the same ratio, the\nfinding is a property of these repos rather than of agent authored work, and it is dead.\n\n## What it does not show\n\nTrailer presence proves a tool touched a commit. It does not prove how much of it the tool\nwrote, and it undercounts every team that strips trailers before merge.\n\n## Three things broke\n\n**cp1252.** `subprocess` on Windows decoded `gh` output with the system codepage and died on\na UTF-8 commit message at byte 31254. Silent on four repos, fatal on the fifth. Fixed with\nan explicit `encoding=\"utf-8\", errors=\"replace\"`.\n\n**claude vs claude.cmd.** `subprocess.run([\"claude\", ...])` returns WinError 2 on Windows\nbecause the executable is `claude.cmd` and Node style shims are not resolved without a\nshell. Same class of bug I fixed in `cabinetai/cabinet` this morning, where `npm.cmd` was\nspawned without a shell and Node rejects that with EINVAL since CVE-2024-27980.\n\n**Prompt truncation through argv.** Passing a 6KB prompt as a command line argument silently\ntruncated it mid sentence, and the agent answered a question it had only half received. The\noutput looked like a model failure and was an argv limit. Fixed by piping the prompt over\nstdin, which is also how it should have been written the first time.\n\nThe third one is the interesting one, because nothing errored. A pipeline that hands work to\na model can fail by delivering a corrupted instruction and still return a confident,\nwell formed, wrong answer.\n\n## Run it\n\n    python ingest.py\n    python transform.py\n    python publish.py\n\nNeeds `gh` authenticated and `claude` on PATH. GitHub commit search is capped at 30 requests\nper hour, which is the first thing that will stop you adding repos.\n", "creation_timestamp": "2026-08-27T12:20:30.879621Z"}]}