Skip to content

Releases: kalinbogatzevski/captain-memo

v0.25.1 — one active summarizer, surfaced everywhere

Choose a tag to compare

@kalinbogatzevski kalinbogatzevski released this 15 Jul 08:55

You can't run two summarizers — and now the tooling makes that obvious

A customer ran captain-memo install twice (codex, then agy) expecting both. Only one summarizer is ever active. Three fixes so this can't confuse anyone again:

1 — The install wizard announces the swap. Re-running with a different provider now prints:

summarizer changed: codex → agy (this REPLACES it — only one summarizer runs at a time)

It was silently overwriting before.

2 — An unrecognized provider fails LOUD. A combined value like codex,agy (or any typo) used to silently fall back to claude-oauth — which on a no-Claude box means nothing gets summarized. Now the worker logs the valid list and special-cases the mistake:

CAPTAIN_MEMO_SUMMARIZER_PROVIDER: you set more than one provider ("codex,agy") — only ONE is
supported, pick a single value. Valid: claude-oauth | codex | agy | anthropic | claude-code |
openai-compatible. Falling back to 'claude-oauth', which needs a Claude login — on a machine
with no Claude, set a real provider or nothing gets summarized.

(Resolution is now a pure, unit-tested resolveSummarizerProvider.)

3 — captain-memo stats and top show the active summarizer. A new Summarizer line reports the resolved provider (post-fallback, so a bad codex,agy shows as its real claude-oauth fallback — not the raw string), the model, and whether it's actually summarizing:

Summarizer ● agy · Gemini 3.5 Flash (Low)
Summarizer ○ claude-oauth (resolved, but NOT summarizing — check its login/config)

/stats gained a summarizer field. (This also fixes a gap: stats never showed the summarizer before.)

Docs: USAGE now leads with a "pick exactly ONE" callout — install replaces (doesn't add), a combined value is invalid, and how to check which one is live.

Suite: 1125/1125.

v0.25.0 — opt-in self-update for git-clone installs (security-reviewed)

Choose a tag to compare

@kalinbogatzevski kalinbogatzevski released this 15 Jul 08:09

Opt-in self-update for git-clone installs

Marketplace installs already self-update via Claude Code. A local git clone install didn't — it sat on old code until someone ran git pull. Now:

export CAPTAIN_MEMO_AUTO_UPDATE=1

On session start, Captain fast-forwards your checkout to the newest stable vX.Y.Z tag on its own origin, runs bun install, restarts the worker, and shows a ⚓ Captain Memo auto-updated: vX → vY banner — then verifies the worker actually booted and rolls back if it didn't. Off by default (auto-pulling a developer's checkout should be a deliberate choice). No-op on a marketplace / non-git install. Throttled to one git fetch per 6h (CAPTAIN_MEMO_AUTO_UPDATE_INTERVAL_MS).

The git mechanics are a moat-safe re-lift of the battle-tested federation self-updater.

This was security-reviewed before release — two serious holes never shipped

The feature went through a 4-lens adversarial review with per-finding verification. It caught two real defects:

RCE via git argument-injection (fixed)

The current branch name (git rev-parse --abbrev-ref HEAD) flowed unsanitized into git fetch … origin <branch>. A repo whose HEAD is a branch named --upload-pack=… executed arbitrary code on an opted-in user's next session — reproduced end-to-end on git 2.47.3, and it fired before the clean-tree / ff-only gates. Fixed two ways: the branch positional is dropped from the fetch entirely, and any dash-leading ref is refused. Re-verified against a real dash-named-HEAD repo — it now bails before any git command runs.

Origin invariant not actually enforced (fixed)

Candidate tags came from git tag --list — the entire local tag namespace — so a contributor who added a fork remote could get a malicious v99.0.0 fast-forwarded in, despite the "only from your own origin" promise. Now candidates are scoped to git ls-remote --tags origin, and git fetch --tags --force guarantees a name in that set resolves to origin's exact sha.

Also hardened from the same review

  • Rollback if the new code crash-loops (capture prior HEAD sha → reset + reinstall + restart old code).
  • Repo-identity gate — only touch a checkout whose package.json name is captain-memo, so a marketplace install nested in an unrelated git repo can't mis-target it.
  • Concurrency lock — only one session updates at a time.
  • bun install gets its own 300s budget instead of the 20s git fetch cap (a half-written node_modules would crash the worker).
  • GIT_TERMINAL_PROMPT=0 + ssh BatchMode — an auth prompt can't stall session start.
  • Suppressed a redundant second worker restart.

20 unit tests (fake git port, no real git) cover every gate and both exploits; the git path was also dry-run and the RCE reproduction re-run against the real repo. Suite: 1119/1119.

v0.24.2 — fix agy summarizer on Windows (home isolation + token EPERM)

Choose a tag to compare

@kalinbogatzevski kalinbogatzevski released this 15 Jul 07:12

agy summarizer now works on Windows

The agy provider shipped in 0.24.0 assuming POSIX. Two Windows-only bugs made it unusable there — both fixed. (Only agy on Windows was affected; claude-oauth — the recommended Windows provider — and codex were always fine.)

1. Home isolation silently failed → would have polluted the real ~/.gemini

agy is a Go binary using os.UserHomeDir(), which reads $HOME on POSIX but %USERPROFILE% on Windows. The transport set only HOME, so on Windows agy ignored it, fell back to the real user profile, and would have grown the user's real ~/.gemini history unbounded — the exact thing the isolated home exists to prevent. Now sets both HOME and USERPROFILE.

2. The token symlink threw EPERM on Windows → every summarize crashed

fs.symlinkSync needs Administrator or Developer Mode on Windows. The token is now copied on Windows (and re-copied when the real token is newer, so a re-login still propagates), while POSIX keeps the symlink so token refresh flows through the real home for free.

Also

  • Home setup is now fail-safe: a missing token or a placement failure surfaces agy's own auth error on spawn instead of crashing the worker.
  • New unit tests exercise the Windows branch on the POSIX CI (via injectable isWindows/tokenSource): copy-not-symlink, re-copy-on-newer, POSIX-symlink, missing-token-no-throw.
  • Linux behavior re-verified live: real ~/.gemini conversation count stays at delta 0, token still symlinked.

Suite: 1099/1099.

v0.24.1 — pin qs + hono past their advisories (bun audit: 10 → 0)

Choose a tag to compare

@kalinbogatzevski kalinbogatzevski released this 14 Jul 21:46

bun audit: 10 findings → zero

qs and hono are now pinned past their advisories via an overrides block.

Both arrived transitively through @modelcontextprotocol/sdk:

sdk › express › body-parser › qs      moderate — remotely triggerable DoS
sdk › @hono/node-server › hono        1 high + 8 moderate

The high was GHSA-88fw-hqm2-52qc — hono's CORS middleware reflecting any Origin with credentials when origin defaults to the wildcard.

Reachability — checked before fixing, not assumed

Neither package is ever loaded by captain-memo.

  • We import exactly two SDK entrypoints: server/stdio.js (the MCP server) and server/webStandardStreamableHttp.js (the worker gateway). Neither imports express or hono — the WebStandard transport is plain fetch Request/Response.
  • The SDK's express-dependent code lives in server/express.js and server/auth/*, which we never import.
  • The gateway is served by Bun.serve, not express.
  • No file under src/ or bin/ references express, hono, or qs.

So these advisories were not exploitable here.

Fixed anyway

"Unreachable today" is a property of the current import graph, not a guarantee. A future HTTP or auth code path would have silently inherited a known-vulnerable CORS middleware, and nothing would have flagged it. The overrides block makes the fix hold regardless of what we import later — and it keeps bun audit at zero, so a real finding can't hide among ten dismissible ones.

Bumping the SDK wouldn't have helped: it's already at latest (1.29.0) and still resolves the vulnerable ranges. Both overrides are semver-compatible patches within the existing major (qs 6.15.1 → 6.15.3, hono 4.12.18 → 4.12.30).

Suite: 1095/1095. Typecheck clean.

v0.24.0 — agy summarizer: zero-key observations on a plain Google account

Choose a tag to compare

@kalinbogatzevski kalinbogatzevski released this 14 Jul 15:32

Zero-key observations on a plain Google account

v0.23.0 gave ChatGPT subscribers a key-free summarizer. This one covers everyone else.

agy shells out to the Antigravity CLI and authenticates off the Google OAuth token it already stored. No Claude plan. No ChatGPT plan. No API key.

agy                                        # once, to log in (needs agy >= 1.1.1)
captain-memo install --summarizer agy

Three of the six providers now need no key at all:

Provider Needs Key-free?
claude-oauth / claude-code Claude Max/Pro
codex ChatGPT Plus/Pro
agy a Google account ✅ ← most people
anthropic / openai-compatible a paid API key

It's also the fastest agent-CLI transport we have: ~3.4–5.5 s/call on Gemini 3.5 Flash (Low) — the Flash tier, which is both the cheapest and (measured) the quickest. Like the others it's an agent-runtime boot rather than inference, and like the others it runs on the worker's 5 s background tick and collapses a whole prompt window into one call — so it never lands on your keystrokes.

Model names are the display names agy models prints (Gemini 3.5 Flash (Low)), not slugs. A typo exits 1 and lists the valid ones, so it fails loudly instead of silently.

The isolated $HOME — required, not hygiene

agy has no --ephemeral equivalent and no home-override env var. It derives everything from $HOME, and every run persists a conversation. Measured: one call writes ~364 KB across 3 conversation entries.

Left alone, a summarizer running on every prompt window would grow ~/.gemini without bound and poison your agy --continue history — your next agy -c would resume a summarizer conversation instead of your actual work.

So captain-memo points $HOME at a private dir (<DATA_DIR>/agy-home), symlinks your real OAuth token in (a symlink, so re-logging-in stays in sync), and prunes its own conversations after each call. Verified live: your real ~/.gemini conversation count stays at delta 0.

It also always passes --sandbox — the summarizer must never execute model-authored commands, and session tool-logs are semi-untrusted input.

Requires agy ≥ 1.1.1

Two upstream fixes are non-negotiable for subprocess use:

  • 1.1.1 fixed agy -p hanging when run inside a subprocess (it read stdin). A hang here would strand processBatch's in-flight guard and silently halt the observation queue — forever.
  • 1.1.1 fixed print mode exiting 0 with empty output on a server-side failure, which is otherwise indistinguishable from success.

One trap, documented so it can't come back

--print/-p is a string flag whose value IS the prompt — not a boolean. So:

agy -p --model "<m>" "<prompt>"     # WRONG: -p swallows the literal string "--model"

agy then answers "I am running on Gemini 3.5 Flash", silently discards your real prompt, and exits 0. It doesn't fail — it succeeds against the wrong input. Correct form, with the prompt always last:

agy --sandbox --model "<m>" -p "<prompt>"

summarizer-agy.test.ts asserts the prompt sits directly after -p, so nobody can "tidy" the flag order and reintroduce it.

Also

  • Checked the codex CLI for updates: 0.144.1 → 0.144.4 contains a Guardian prompt revert plus two version-only releases. Nothing touches codex exec, the --json envelope, or ChatGPT-account model gating — the 0.23.0 codex provider is unaffected.

Suite: 1095/1095.

v0.23.0 — Codex summarizer (no Claude plan needed) + auto-discovered multi-AI memory

Choose a tag to compare

@kalinbogatzevski kalinbogatzevski released this 14 Jul 14:07

Two things, both aimed at the same gap: Captain assumed you were a Claude user.

1. codex summarizer — the zero-key option when you have no Anthropic subscription

Until now every provider needed either a Claude plan (claude-oauth, claude-code) or a paid API key (anthropic, openai-compatible). If all you had was ChatGPT Plus/Pro, you got no observations at all.

npm i -g @openai/codex && codex login
captain-memo install --summarizer codex

Shells out to codex exec --json. No API key, ever.

What the benchmark actually showed. ~6–7 s/call — and the model doesn't change that. Across the whole ladder (gpt-5.4-minigpt-5.6-sol) the latency is flat, because you're paying for codex exec booting an agent runtime, not for inference. Picking a small model saves quota, not wall-clock. Codex exposes no non-agent completion entrypoint, so there's no way under that floor.

It still doesn't cost you anything interactively. Summarization runs on the worker's 5 s background tick and groups by (session_id, prompt_number) — a whole prompt window, however many tool calls it contained, collapses into one request. You're never waiting on it.

You don't need to know which models your plan allows. A ChatGPT account gates the model list server-side (gpt-5.4-nano and every gpt-5.1-* slug are rejected outright). So the fallback chain ends at the sentinel default — "send no model at all" — which the account always accepts. Default is gpt-5.4-mini, the Haiku-tier pick.

Runs --ignore-user-config --ephemeral --sandbox read-only. The first one matters more than it looks: your ~/.codex/config.toml may set a heavyweight reasoning effort and register MCP servers — including captain-memo itself — which would otherwise be booted as child processes on every single summarize call.

Bonus: Codex reports real token usage, so observations now carry accurate work-token costs — something the claude-code transport can't provide.

2. CAPTAIN_MEMO_WATCH_MEMORY=auto — index every assistant's memory, not just Claude's

Captain only ever read the one glob you hand-wrote, which in practice meant Claude's memory and nothing else — even with Codex, Gemini and Cursor installed right next to it.

auto probes the machine and expands to whatever actually exists: ~/.claude/CLAUDE.md, per-project Claude memories, ~/.codex/, ~/.gemini/, ~/.cursor/rules/, and repo-level AGENTS.md / CLAUDE.md / .github/copilot-instructions.md. It composes — auto,/my/notes/*.md is a union — and it's now the recommended install default. On the dev box that's 390 files vs the 381 the old single glob saw.

Every indexed doc carries a tool provenance tag, because filename_id stops being unique the moment three assistants can each own an AGENTS.md.

Credentials can't get indexed — structurally, not by blocklist. Every discovery glob must end in .md/.mdc, and a test enforces it. That single rule is what keeps ~/.codex/auth.json, ~/.gemini/oauth_creds.json, ~/.codex/sessions/**.jsonl (53 MB of transcripts) and *.sqlite out of the corpus. A blocklist is the thing you forget to update when a vendor adds a file.

Fixed

  • The watcher was blind to hidden directories, so repo-level memory never indexed. Both glob scans defaulted to dot: false, which meant ~/projects/*/.claude/CLAUDE.md matched zero files despite existing — as would any .github/ or .cursor/ rule. Verified: 0 hits before, 2 after.
  • Auto-discovery silently dropped globs whose wildcard is in the filename. Probing "everything before the first *" turns ~/.claude/CLAUDE*.md into ~/.claude/CLAUDE — not a path — so your global CLAUDE.md went un-indexed. Found only by running discovery live against a real machine; regression-tested now.

Upgrade

captain-memo install          # re-run: preserves your config, offers the new options

Suite: 1087/1087.

v0.22.2 — hermetic integration tests (no dev worker.env leak)

Choose a tag to compare

@kalinbogatzevski kalinbogatzevski released this 12 Jul 11:54

Fixed

  • Integration tests silently inherited the developer's real worker.env. A spawned test worker calls loadWorkerEnv(), which reads CONFIG_DIR/worker.env and injects every variable not already in process.env. On any machine that also runs a real captain, that meant ~/.config/captain-memo/worker.env — the developer's own live config (EMBEDDER_ENDPOINT, EMBEDDER_API_KEY, QM_DEDUP, TIDE_*, RECALL_AUDIT, FEDERATION_*, …) — quietly became the fixture's config. Tests therefore behaved differently depending on whose box they ran on: green in CI, and able to fail (or falsely pass) locally. Every worker-spawning integration test now pins CAPTAIN_MEMO_CONFIG_DIR to its own temp dir (paths.ts honours it), so no worker.env is found and nothing is inherited. Suite: 1072/1072.

v0.22.1 — stop() drains in-flight background jobs

Choose a tag to compare

@kalinbogatzevski kalinbogatzevski released this 12 Jul 11:25

Fixed

  • Background jobs could write to the database after the worker shut it down. stopResources() cleared all six interval timers and then immediately closed the stores — but clearInterval cancels the schedule, not work already in flight. Every background slice (ingest batch, Tide sweep, Quartermaster dedup/supersede, promotion) is async and yields to the loop, so a slice that started before stop() kept running and then wrote its result / audit row into a closed DB: RangeError: Cannot use a closed database, thrown from a timer callback — an unhandled rejection with no caller to attribute it to. stop() now drains every in-flight job (Promise.allSettled) before releasing the handles.
    • Symptom in the wild: a phantom test failure that attached itself to whichever test happened to be running when an orphaned tick fired, so the "failing test" migrated between files run-to-run and never reproduced in isolation. The full suite is green again (1072/1072).

v0.22.0 — connect kimi — shared corpus + local-Ollama setup

Choose a tag to compare

@kalinbogatzevski kalinbogatzevski released this 12 Jul 11:22

Added

  • captain-memo connect kimi — share the corpus with Kimi CLI, and set it up on your own local Ollama. Registers the captain-memo MCP server into kimi (kimi mcp add captain-memo -- …~/.kimi/mcp.json) so Kimi reads the SAME local memory as Claude Code, Codex, Gemini, opencode and the rest. It also writes ~/.kimi/config.toml for you: the local Ollama provider (openai_legacy at http://127.0.0.1:11434/v1) plus one [models."<id>"] alias per model from ollama list — so Kimi runs entirely on your own machine, with no Moonshot key and no login, and kimi -m "<id>" reaches every model you have pulled. Verified against kimi-cli 1.48.0.
    • Honest by construction: a box with no local Ollama models gets nothing written (a base_url-only config would claim a capability you don't have), and it tells you to pull a model.
    • The root default_model is only kept if it still resolves to a declared alias, so an ollama rm can never leave Kimi pointing at a model that's gone (it would die with "LLM not set" while the installer claimed success).
    • An embedding model is never chosen as the default — ollama list returns embedders (this project's own docs tell you to pull one) and an embedder cannot chat.

v0.21.0 — opencode as a first-class cross-AI tool

Choose a tag to compare

@kalinbogatzevski kalinbogatzevski released this 12 Jul 11:22

Changed

  • opencode is a first-class cross-AI tool in the connect docs. De-staled the connect/install prose and the KNOWN_TOOLS comment that still enumerated only "Codex, Gemini, Cursor" — opencode (and the other adapters) are auto-detected and wired by captain-memo connect; the help text and comments now reflect that.