thread-keeper

MCP serverAI & models

Multi-agent shared brain across Claude, Codex, Antigravity, Gemini, Copilot, and VS Code.

Unavailable. This server has no hosted endpoint yet, so ahel can't serve it.

Connect ahel once, and every AI you use reads what you have installed.

From the project's README

As published by po4erk91/thread-keeper in README.md.

Multi-agent shared brain across Claude Code/Desktop, Codex, Antigravity CLI (agy), Copilot, and VS Code. Cross-session memory, self-improving skill loops, and inter-agent signaling — one local MCP server turns parallel agent instances into a coordinated multi-agent system instead of N isolated chats.

Every connected client (Claude Code, Claude Desktop, Codex CLI + desktop, Antigravity CLI, Copilot, every MCP-aware VS Code extension) shares one SQLite store, one set of threads, one user model, and one learning loop that improves the skill library autonomously over time.

The brief format is dense — structural tags, opaque IDs, ~6 KB per session-start injection. Optimized for agent consumption, not human reading.


Why

Every agent CLI starts cold. Context dies at session boundaries. Skills you taught Claude don't transfer to Codex. Threads you closed in yesterday's Antigravity chat are invisible to today's Copilot. Parallel agent instances running the same task don't know about each other and duplicate work or step on each other's writes.

thread-keeper is the substrate underneath. Three things that together make it more than a memory store:

  • Collective memory — threads, notes, verbatim quotes, dialectic claims about you. Survives session, restart, CLI swap. One agent records, every other agent (any CLI) reads. The brief injected at session start gives a new agent everything the previous one knew.
  • Multi-agent coordinationspawn primitive launches child agents in parallel, each gets a self_cid + sees the same memory. broadcast / whisper / inbox / wait / ask / respond let concurrent sessions signal each other across CLIs. Parent / children / sibling agents become a coordinated swarm, not isolated chats.
  • Self-improving skill library — autonomous background loops (auto-review on thread close, shadow-review daemon, extract harvester, candidate-reviewer, weekly Curator, and a thread-janitor that auto-closes idle threads so abandoned work reaches the harvest path — closing is reversible, a note reopens a closed thread) materialize class-level skills as the agents work. Adapted to multi-CLI: SKILL.md is the primary write target and gets mirrored to every known/configured skills root simultaneously (~/.claude/skills/, ~/.codex/skills/, ~/.gemini/config/skills/ for Antigravity, existing ~/.agents/skills/, extra roots from THREADKEEPER_EXTRA_SKILLS_DIRS, and ~/.threadkeeper/skills/), with lessons.md as a fallback for CLIs without a native skills loader.

Foreground MCP servers also run a daily self-update check by default. Source checkouts fast-forward their tracked git branch and reinstall the editable package; PyPI/pipx/venv installs run pip install --upgrade in the current interpreter environment only after the latest PyPI release files have matching Integrity API provenance from the expected GitHub Trusted Publisher. Dirty or diverged git checkouts are skipped rather than overwritten. Restarts are gated on install/setup success plus a subprocess import smoke check, so a broken or unverified update is recorded but the current server keeps running. Upstream PyPI publishing is intentionally gated: green merge-to-main builds are auto-tagged, but every upload pauses for a human approval on the protected pypi GitHub Environment (a maintainer-signed annotated v* tag remains the manual override path), as described in docs/RELEASING.md.

They also run a twice-weekly installed-skill updater by default. It keeps all configured CLI skill roots in sync, adopts newer local copies installed into a non-primary root, and updates GitHub-backed skills when a tracked upstream source changes.


Quickstart

The shortest path — PyPI + pipx (recommended):

pipx install 'threadkeeper[semantic]' && thread-keeper-setup

thread-keeper-setup detects every CLI you have installed (Claude Code / Claude Desktop / Codex CLI + desktop / Antigravity CLI agy / Copilot / VS Code), registers the MCP server in each one's config, copies hooks to ~/.threadkeeper/hooks/, and writes a managed instructions block into each CLI's per-user instructions file (CLAUDE.md / AGENTS.md / copilot-instructions.md — Claude Desktop and VS Code have no global instructions file, so that step is skipped for them).

Restart your CLI of choice. Hook-capable clients inject a brief on the first message; hookless clients such as Codex and Antigravity CLI either follow the managed instructions block and call brief() / context() before answering, or — on hosts that support MCP resources — pull the brief as the read-only memory://brief resource the host attaches automatically (see MCP primitives).

Alternative installs

If you don't have pipx and don't want to install it:

# uv (Rust-fast Python tool runner) — no clone, single binary on PATH
uv tool install 'threadkeeper[semantic]' && thread-keeper-setup

# Plain pip into a venv
python3 -m venv ~/.threadkeeper-venv
~/.threadkeeper-venv/bin/pip install 'threadkeeper[semantic]'
~/.threadkeeper-venv/bin/thread-keeper-setup

For development (editable install from a git checkout) or to track the bleeding edge:

# One-liner installer — clones to ~/thread-keeper, makes a venv,
# editable-installs, wires every detected CLI. Idempotent — re-run to
# update (it git-pulls + reinstalls).
curl -fsSL https://raw.githubusercontent.com/po4erk91/thread-keeper/main/install.sh | bash -s -- --semantic

# Or fully manual
git clone https://github.com/po4erk91/thread-keeper ~/thread-keeper
cd ~/thread-keeper && python3 -m venv .venv
.venv/bin/pip install -e '.[semantic]'
.venv/bin/thread-keeper-setup

To preview without writing anything:

thread-keeper-setup --dry-run

Multi-CLI integration

CLIMCP configInstructions fileHooksTranscripts ingested
Claude Code~/.claude.json mcpServers~/.claude/CLAUDE.md~/.claude/settings.json hooks~/.claude/projects/**/*.jsonl
Claude Desktop~/Library/Application Support/Claude/claude_desktop_config.json mcpServers (macOS); %APPDATA%\Claude\… (Win); ~/.config/Claude/… (Linux)none (GUI-only)not supported by the appnone — chats live in Electron IndexedDB
Codex (CLI + desktop)~/.codex/config.toml [mcp_servers] (shared between CLI and Codex.app)~/.codex/AGENTS.mdnot supported~/.codex/sessions/**/rollout-*.jsonl
Antigravity CLI (agy)~/.gemini/config/mcp_config.json mcpServers~/.gemini/config/AGENTS.mdnot wired yetnot yet parsed — sqlite/protobuf under ~/.gemini/antigravity-cli/conversations/*.db
Copilot~/.copilot/mcp-config.json mcpServers~/.copilot/copilot-instructions.md~/.copilot/hooks.json~/.copilot/session-store.db (sqlite)
VS Code~/Library/Application Support/Code/User/mcp.json servers (macOS); %APPDATA%\Code\User\mcp.json (Win); ~/.config/Code/User/mcp.json (Linux)none (per-workspace only)not supportednone — extensions own their history

Every CLI that produces parseable transcripts feeds the same dialog_messages table with a source tag, so dialog_search() finds matches regardless of where the conversation happened. Claude Desktop, Antigravity CLI, and the VS Code adapter are the exceptions — MCP registration only; their chats don't reach the table for now (Electron IndexedDB on the Claude Desktop side; sqlite/protobuf on the Antigravity side; per-extension stores on the VS Code side).

VS Code's user-level mcp.json is the central host that every MCP-aware VS Code extension consumes — GitHub Copilot Chat, the Anthropic Claude IDE plugin, the OpenAI Codex IDE plugin, Continue, Cline, … — so a single registration there reaches all of them at once.

Adding a new CLI = one file under threadkeeper/adapters/ implementing the CLIAdapter contract. See CONTRIBUTING.md.

MCP primitives (tools, resources, prompts, elicitation)

MCP has three server primitives. thread-keeper uses all three, mapped to the read/act split, plus MCP elicitation for host-native confirmations:

PrimitiveControlWhat thread-keeper exposesWhen to use
Toolsmodel-controlled (may act)the full surface — brief, note, spawn, search, curator_review, …the agent decides to call them
Resourcesapplication-controlled, read-onlymemory://brief, memory://context, memory://dashboard, memory://agent-statusthe host attaches/pulls them automatically
Promptsuser-controlled templatesreview_recent_threads, run_library_curation, audit_threadkeeperthe user runs them (Claude Code: /mcp__thread-keeper__<name>)

Resources back the genuinely read-only memory views with the same render functions as the matching tools, so the content is identical — memory://brief is brief(), memory://context is context(), and so on. The win is for hookless CLIs: instead of depending on the agent remembering to call brief() (agents focused on their task often skip it), a resource lets the host surface memory as attachable / @-mentionable context through a mechanical channel. The brief resource renders lean and agent-status uses a cached snapshot, so an automatic host pull is side-effect-free.

Prompts turn the curation / audit / review flows into discoverable, parameterized commands; each just drives the existing tools.

Elicitation is a client feature, not a server primitive. When a host advertises form-mode elicitation, high-stakes mutations can pause for a structured user choice instead of relying on an ignorable text nudge. The first flow using it is dialectic_supersede: supported hosts get a flat confirm/reject form before a user-model claim is replaced; unsupported hosts keep the previous immediate tool behavior.

Everything here is additive and capability-gated: a host that advertises the resources / prompts capabilities sees those primitives; one that advertises elicitation.form gets structured confirmations for covered high-stakes writes. Hosts without a capability fall back to the SessionStart hook plus the brief() / context() tools and the existing write behavior — same content, no regression. Static URIs only for now (resource templates with {param} are still unevenly supported across hosts).

Memory egress (cross-provider privacy)

thread-keeper is "one user model … shared across CLIs," and that sharing is by design. The flip side: the most sensitive memory it holds — verbatim_user quotes and the dialectic user-model (claims about you: style, values, workflow) — is rendered into every brief(), and brief() is consumed by whichever LLM vendor backs the active or spawned CLI. So by default, a quote you said to Claude, or a trait inferred about you, can be transmitted to OpenAI (Codex), Google (Antigravity), or Microsoft-GitHub (Copilot) on the next session-start or spawn under that CLI. This is a deliberate default, not a leak — but it's worth stating plainly, and it's controllable.

THREADKEEPER_MEMORY_EGRESS scopes the egress of personal-class memory (verbatim + dialectic user-model). work-class (threads/notes/tasks) and shared-class (skills/lessons/concepts) memory always egress.

ValuePersonal-class memory egresses to…
all (default)every vendor — current behavior, brief is byte-identical to pre-policy
same-vendorClaude / Anthropic only; omitted for OpenAI / Google / Microsoft CLIs
work-onlyno vendor — personal memory never leaves the machine

Under a restricted policy, the gated brief() drops the verbatim and user_model (dialectic) sections and leaves a one-line egress policy=…: personal memory … withheld from <vendor> disclosure so the consuming agent knows personal context exists but was intentionally not sent. The native vendor is Anthropic because the brief format and personal memory are authored in Claude sessions. The gate applies on every consumption path: the foreground brief and any spawned child — spawn() tells the child which vendor will consume its brief, so a child spawned to a third-party CLI cannot retrieve more than the policy allows for that vendor. Set it in ~/.threadkeeper/.env (a real env override wins over .env):

THREADKEEPER_MEMORY_EGRESS=same-vendor

Core systems

Spawn — primary parallelism primitive

spawn(prompt, slim=True, role=..., visible=False, ...) launches a child Claude session via a claude -p subprocess. By default slim=True: the child loads only the thread-keeper MCP, no embeddings, no third-party servers. ~500 MB RSS versus ~1.3 GB for a full child. Heuristic for the parent: N≥2 modular independent units of ≥5 min each = spawn signal. Spawn also marks children with THREADKEEPER_SPAWNED_CHILD=1, so autonomous learning daemons cannot recursively start inside review forks.

A daemon in the foreground parent measures combined child RSS every 10 s; spawned children do not start their own ps polling loop, failed ps RSS samples keep the last-known value, and the liveness sweep covers every open task row so dead children stop counting against the cap. Admission control refuses a new spawn that would exceed THREADKEEPER_SPAWN_BUDGET_MB (3 GB default). Slim children that need semantic search delegate to the parent via search_via_parent — no per-child copy of the embedding model. Admission uses a SQLite BEGIN IMMEDIATE reservation: spawn() re-checks the budget and inserts the child task row with its RSS estimate before Popen, so two concurrent spawns cannot both squeeze through the cap.

When cwd is inside a Git checkout, spawn() also requires the source checkout's tracked files to be clean, then starts the child from a unique branch/worktree under THREADKEEPER_TASK_LOG_DIR/worktrees/. Parallel children therefore never share a mutable checkout or Git index. Non-Git directories keep their existing behavior; a dirty Git checkout is refused before a child starts.

The spawn wrapper also records each completed child's duration_s, tokens_in, tokens_out, tokens_total, and cost_usd when the underlying CLI emits a recognizable usage trailer. Optional daily ceilings THREADKEEPER_SPAWN_TOKEN_BUDGET and THREADKEEPER_SPAWN_COST_BUDGET_USD admission-deny new children once the recorded 24h spend reaches the configured limit; both default to 0 (disabled), so existing installs behave the same until a budget is set. Claude children keep their positional prompt argv under a conservative 96 KiB byte ceiling; larger prompts are written to THREADKEEPER_TASK_LOG_DIR/<task>.stdin.txt with owner-only permissions and fed on stdin, so Linux's per-argument MAX_ARG_STRLEN limit cannot turn a large curator/reviewer prompt into an opaque E2BIG spawn failure.

Visible (visible=True, Terminal.app) children persist pid=0, so the daemon resolves their live pid from the --session-id it carries in ps argv and measures the real RSS tree — they count their true memory, not the static estimate. A visible row whose session-id never resolves to a live process is reaped once it outlives THREADKEEPER_SPAWN_VISIBLE_TTL_S (1 h default; 0 disables), so an unresolvable row can't pin budget capacity forever.

The same daemon is also a wall-clock watchdog: a child that hangs while still alive — a wedged WebFetch/gh/git, an agent loop that never converges, a prompt that never arrives — would otherwise stall its loop's single-flight slot and burn tokens forever. Any child whose row outlives THREADKEEPER_SPAWN_MAX_RUNTIME_S (1 h default; 0 disables) is SIGTERM'd, then SIGKILL'd after THREADKEEPER_SPAWN_KILL_GRACE_S (10 s), and its row is closed with the timeout return_code 124 so the loop's single-flight releases. The watchdog then immediately starts a capped continuation retry: the new child receives the original assignment plus the previous task/cid/log and is instructed to inspect current workspace state, preserve completed work, repair partial work, and continue rather than restart blindly. THREADKEEPER_SPAWN_TIMEOUT_RETRY_LIMIT (default 3; 0 disables) bounds the retry chain, with THREADKEEPER_SPAWN_TIMEOUT_RETRY_DELAY_S available for a non-zero delay. Timed-out children are surfaced as tasks_timed_out in mp_dashboard and timed_out in agent_status.

tk-agent-status exposes autonomous learning loop status as structured JSON or compact text for external monitors:

tk-agent-status
tk-agent-status --json
tk-agent-status --cleanup-memory

apps/macos-agent-status/ contains a small macOS menu-bar app that polls this command every 15 seconds and shows every autonomous learning loop: enabled/off, running/idle/ready, last pass, backlog, and active child RSS when that loop has spawned a worker. PyPI wheels and sdists also bundle the same Swift source under threadkeeper/assets/macos-agent-status/, so a normal pipx/uv tool install does not need a git checkout for the widget to build. Active loops are sorted first (running, then ready), so background work stays at the top of the panel. tk-agent-status --cleanup-memory runs the safe cleanup path used by the widget: request server cache trims, apply the RSS guard, and remove orphan MCP server processes without killing active spawned child agents. The popover also has a power button that flips THREADKEEPER_DISABLE_BG_DAEMONS in ~/.threadkeeper/.env and requests a ThreadKeeper restart, so autonomous loops can be paused or re-enabled without opening Settings. The menu-bar status item is backed by AppKit NSStatusItem: it shows the black memorychip icon while idle, then swaps fixed-center, synchronized gear frames whenever running_loop_count reports at least one active autonomous loop. The status item is icon-only; loop counts live in the popover and tooltip. The app also has a Clean memory button, self-restarts when its own RSS crosses THREADKEEPER_MENUBAR_RESTART_RSS_MB (1024 MB default), requests macOS notification permission, and sends a notification when a newly completed autonomous child task produces a useful result in recent_results; the first poll only marks existing results as seen, so old completions do not spam notifications. Status polling and cleanup commands run off the main actor, so opening the popover does not wait for tk-agent-status --json. The header gear opens a separate Settings window for ~/.threadkeeper/.env: a sidebar separates CLI Agents, LLM-backed Learning Loop Agents, mechanical System Automation, Memory & Budgets, and Advanced .env. Model catalogs come from installed CLIs at runtime and show installed and latest official cloud versions, source, freshness, and discovery errors; an Update button appears only when those versions differ and runs the CLI's allowlisted vendor updater after confirmation. Each agent has its own CLI, provider-filtered model, effort, inherited effective values, schedule, and read/write impact. Guided controls are dropdown-only, with schedules labelled in hours; custom values and raw unknown keys remain editable in Advanced .env alongside three compact presets. Probe backlog is due objective probes only, not every registered probe, so a healthy cooldown shows 0 due probes instead of looking stuck. On macOS, python -m threadkeeper.server automatically installs and launches it on MCP startup. The installed app records a source fingerprint, so package upgrades rebuild the helper even when an older bundle has a newer file timestamp, then restart any stale running menu-bar process. Set THREADKEEPER_MENUBAR_AUTO_LAUNCH=0 to disable that behavior.

Auto Update

The MCP server starts an auto-update daemon in foreground parent processes. By default it checks once per day (THREADKEEPER_AUTO_UPDATE_INTERVAL_S=86400):

  • editable git checkout: skip if tracked files are dirty, otherwise fetch the tracked remote branch, fast-forward with git pull --ff-only, reinstall the editable package, and run the configured post-update setup check;
  • installed package: run pip install --upgrade threadkeeper or threadkeeper[semantic] in the current interpreter environment, preserving semantic extras when they are already installed, but only after the candidate PyPI release's non-yanked files have PyPI Integrity API provenance from the expected GitHub Trusted Publisher (po4erk91/thread-keeper, publish.yml, environment pypi), then run the configured post-update setup check when the installed version changes.

Auto-update is standing consent for thread-keeper to fetch and run future maintainer code. A packaged update whose provenance is missing, whose publisher identity does not match policy, or whose attested subject digest does not match PyPI metadata is refused before pip runs and is recorded as auto_update_pass with mode=pip and refused. After a successful update, the daemon exits the current MCP process by default so the host can restart it on the new code. Before scheduling that exit, it imports threadkeeper.server in a subprocess; install/setup/import failures are recorded as auto_update_pass with restart=suppressed, and the current known-working process stays alive. Post-update setup defaults to THREADKEEPER_AUTO_UPDATE_SETUP=check, which runs thread-keeper-setup --dry-run only. It records setup=checked status=unchanged when configs already match and logs/records status=changes_pending if MCP registrations, hooks, or managed instruction blocks would be rewritten; it does not re-add config the user removed. Set THREADKEEPER_AUTO_UPDATE_SETUP=apply to give standing consent for auto-update to run the full setup writer after future successful updates, or skip to avoid even the dry-run check. Disable restart with THREADKEEPER_AUTO_UPDATE_RESTART=0, or disable the updater entirely with THREADKEEPER_AUTO_UPDATE_INTERVAL_S=0. The provenance gate is on by default; THREADKEEPER_AUTO_UPDATE_VERIFY_PROVENANCE=0 is a break-glass opt-out for private mirrors or disconnected installs. If a packaged release needs manual rollback, pin the previous version explicitly, for example pip install threadkeeper==<previous>. Each real check records an auto_update_pass event that appears in dashboard/status telemetry.

Skill Update

The MCP server also starts a skill updater in foreground parent processes. By default it checks twice per week (THREADKEEPER_SKILL_UPDATE_INTERVAL_S=302400):

  • local root sync: scan every configured skill root, import the newest local copy of a skill into the primary ~/.claude/skills root, then mirror it back to ~/.codex/skills, Antigravity, ~/.agents/skills, extra roots, and the canonical ~/.threadkeeper/skills fallback;
  • source-tracked updates: skills with .threadkeeper-skill-source.json, or skills whose name can be inferred from THREADKEEPER_SKILL_UPDATE_SOURCES, are compared with upstream GitHub directories and updated when the remote tree changes.

Shortened here. Read the whole README on GitHub.

Signals

GitHub stars
11
Forks
2
Last commit
Sep 2026
Advanced
Delivery
thread-keeper MCP server → your ahel gateway (mcp.ahel.ai) → every connected AI client.
Catalog kind
mcp-server
Gateway key
io-github-po4erk91-thread-keeper
Source
github.com/po4erk91/thread-keeper