Files
Julius Brussee 783dbeafb5 fix(xplat): close Windows/macOS data-loss, encoding, and hook-budget gaps
Findings from a whole-repo cross-platform audit.

Data loss:
- evals/llm_run.py wrote the snapshot with write_text(ensure_ascii=False) and
  no encoding: on Windows that truncates results.json to 0 bytes, then raises
  at the first non-cp1252 char. Destroyed the committed snapshot and the run.
- compress.py read the user's target with errors="ignore", silently dropping
  undecodable bytes, backing up the mangled text, passing its own readback
  check, then overwriting the original. Now decodes strictly and refuses.

Broken on Windows:
- benchmarks/ and evals/ read SKILL.md, README.md and results.json without an
  encoding; all three raise on a cp1252 decode.
- cavemem's Python client used text=True with no encoding, so a non-ASCII
  memory failed to store and any recall came back mojibake.
- native-hook-fast spawned a second full Node process from inside a hook with
  no timeout at all.

Also fixed:
- Backported the pnpm cross-drive shim fix to the five copies that never got
  it; hoisted two inline parsers so all of them are unit-testable.
- portable-command now resolves through PATH/PATHEXT instead of silently
  no-op'ing on a bare command name.
- uninstall.ps1 writes a temp .js instead of node -e (matches install.ps1).
- Real backoff on the flag rename retry; EPERM/EBUSY retry when replacing a
  running binary.
- Hooks act on the first complete JSON payload instead of blocking on EOF;
  the stats child timeout drops below the host's hook budget.
- Statusline tests run the .ps1 on Windows instead of skipping.
- Added the macOS CI job the repo never had.

Left unfixed and annotated in place: the win32 hookCommand emits PowerShell
call-operator syntax while #835 describes the runner as git-bash /bin/sh.
Needs a real Windows host to settle; guessing would break whichever works.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NveRZs9mV97iX7HSgq3BYF
2026-08-20 04:14:48 +02:00
..

cavemem

Durable, compression-native agent memory: remember, recall, supersede, history, forget. Memories live in a local SQLite store; recall ranks them with BM25 behind a conservative threshold and compresses each hit through the Caveman engine, so the inferred token cost is honest and the dropped detail stays recoverable. Counts are inferred; component never claims verified savings.

Go core and binary ship under BSL 1.1. Thin JS/Python clients remain MIT. BSL runtime is source-available, not OSI Open Source before Change Date. See LICENSE and ../LICENSING.md.

CLI

go build -o cavemem ./mem/cmd/cavemem
./cavemem remember "the deploy key lives in vault under ops/deploy"
./cavemem recall   "where is the deploy key"      # JSON: { hits: [...], basis: "inferred" }
./cavemem recall   "full migration context" 5 0   # explicit 0 token budget = unlimited
./cavemem supersede mem_xxxxxxxx "deploy key moved to vault ops/deploy-v2"
./cavemem history   mem_yyyyyyyy                  # oldest → current
./cavemem forget   mem_xxxxxxxx
./cavemem          # runs the MCP server over stdio

MCP

{ "mcpServers": { "cavemem": { "command": "cavemem" } } }

Libraries

Thin clients shell text to the binary over stdin (avoiding OS argument-size limits; set CAVEMEM_BIN if it is not on PATH): an oversized remember exits with code 65; both clients export MEMORY_TOO_LARGE_EXIT_CODE so callers can branch without parsing stderr.

import { remember, recall, supersede, history, forget } from "cavemem"; // js/index.mjs
await remember("…"); await recall("…", 5, 2000); await recall("…", 5, 0); // unlimited
import cavemem                                            # py/cavemem.py
cavemem.remember("…"); cavemem.recall("…", limit=5, token_budget=2000)

Guarantees

  • Byte-safe write: raw text is stored before anything else, so a memory is never lost.
  • Off-topic query recalls nothing rather than guessing.
  • Recall packs at most 2000 inferred tokens by default. CLI, MCP, JS, and Python callers may explicitly request token_budget: 0 for unlimited recall; omitting it never disables the cap.
  • Recall excludes superseded facts by default; history stays local and auditable.
  • Every compressed recall hit carries a recovery_handle.