threadkeeper 0.4.0__tar.gz → 0.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- threadkeeper-0.5.0/PKG-INFO +578 -0
- threadkeeper-0.5.0/README.md +544 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/pyproject.toml +2 -2
- threadkeeper-0.5.0/tests/test_candidate_reviewer.py +267 -0
- threadkeeper-0.5.0/tests/test_dialectic_tier.py +394 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/tests/test_skill_hint.py +3 -3
- threadkeeper-0.5.0/tests/test_skill_tier.py +318 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/tests/test_skills.py +4 -5
- threadkeeper-0.5.0/tests/test_spawn_config.py +322 -0
- threadkeeper-0.5.0/tests/test_validate_threads.py +146 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/adapters/__init__.py +26 -1
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/adapters/base.py +25 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/adapters/claude_code.py +26 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/adapters/codex.py +18 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/adapters/copilot.py +16 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/adapters/gemini.py +16 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/brief.py +53 -10
- threadkeeper-0.5.0/threadkeeper/candidate_reviewer.py +341 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/config.py +19 -2
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/curator.py +4 -6
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/db.py +31 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/identity.py +89 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/nudges.py +6 -4
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/review_prompts.py +10 -9
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/server.py +2 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/shadow_review.py +4 -4
- threadkeeper-0.5.0/threadkeeper/spawn_config.py +203 -0
- threadkeeper-0.5.0/threadkeeper/tools/candidate_reviewer.py +95 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/tools/dialectic.py +242 -45
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/tools/skills.py +162 -22
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/tools/spawn.py +111 -30
- threadkeeper-0.5.0/threadkeeper/tools/validate.py +238 -0
- threadkeeper-0.5.0/threadkeeper.egg-info/PKG-INFO +578 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper.egg-info/SOURCES.txt +10 -1
- threadkeeper-0.4.0/PKG-INFO +0 -351
- threadkeeper-0.4.0/README.md +0 -317
- threadkeeper-0.4.0/threadkeeper.egg-info/PKG-INFO +0 -351
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/LICENSE +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/setup.cfg +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/tests/test_adapters.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/tests/test_brief_sections.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/tests/test_core_memory.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/tests/test_curator.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/tests/test_delegated_search.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/tests/test_dialectic.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/tests/test_error_paths.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/tests/test_extract_daemon.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/tests/test_i18n_multilang.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/tests/test_identity.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/tests/test_lessons.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/tests/test_missed_spawns.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/tests/test_nudges.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/tests/test_process_health.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/tests/test_shadow_review.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/tests/test_skill_use_parser.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/tests/test_skill_watcher.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/tests/test_spawn_budget.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/tests/test_spawn_hint.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/tests/test_spawn_slim.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/tests/test_threads.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/tests/test_tools_smoke.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/tests/test_vec_search.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/__init__.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/_mcp.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/_setup.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/adapters/_hook_helpers.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/adapters/claude_desktop.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/adapters/vscode.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/embeddings.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/extract_daemon.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/helpers.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/i18n.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/ingest.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/lessons.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/process_health.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/search_proxy.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/skill_watcher.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/spawn_budget.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/tools/__init__.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/tools/concepts.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/tools/consolidate.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/tools/core_memory.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/tools/correlation.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/tools/curator.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/tools/dialog.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/tools/distill.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/tools/extract.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/tools/graph.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/tools/invariants.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/tools/lessons.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/tools/missed_spawns.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/tools/peers.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/tools/pickup.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/tools/probes.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/tools/process_health.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/tools/session.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/tools/shadow_review.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/tools/style.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper/tools/threads.py +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper.egg-info/dependency_links.txt +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper.egg-info/entry_points.txt +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper.egg-info/requires.txt +0 -0
- {threadkeeper-0.4.0 → threadkeeper-0.5.0}/threadkeeper.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,578 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: threadkeeper
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: Multi-agent shared brain across Claude Code/Desktop, Codex, Gemini, Copilot, VS Code. Cross-session memory, self-improving skill loops, inter-agent signaling — one local MCP server.
|
|
5
|
+
Author: thread-keeper contributors
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/po4erk91/thread-keeper
|
|
8
|
+
Project-URL: Repository, https://github.com/po4erk91/thread-keeper
|
|
9
|
+
Project-URL: Issues, https://github.com/po4erk91/thread-keeper/issues
|
|
10
|
+
Project-URL: Documentation, https://github.com/po4erk91/thread-keeper#readme
|
|
11
|
+
Project-URL: Changelog, https://github.com/po4erk91/thread-keeper/releases
|
|
12
|
+
Keywords: mcp,model-context-protocol,claude,codex,gemini,copilot,memory,agents,self-improving,skills
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: MacOS
|
|
16
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
22
|
+
Requires-Python: >=3.11
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: mcp>=1.0.0
|
|
26
|
+
Provides-Extra: semantic
|
|
27
|
+
Requires-Dist: sentence-transformers>=2.2.0; extra == "semantic"
|
|
28
|
+
Requires-Dist: numpy>=1.24.0; extra == "semantic"
|
|
29
|
+
Requires-Dist: sqlite-vec>=0.1.9; extra == "semantic"
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
32
|
+
Requires-Dist: pytest-cov>=5.0; extra == "dev"
|
|
33
|
+
Dynamic: license-file
|
|
34
|
+
|
|
35
|
+
# thread-keeper
|
|
36
|
+
|
|
37
|
+
[](https://github.com/po4erk91/thread-keeper/actions/workflows/test.yml)
|
|
38
|
+
[](https://www.python.org/downloads/)
|
|
39
|
+
[](LICENSE)
|
|
40
|
+
[](https://pypi.org/project/threadkeeper/)
|
|
41
|
+
[](#multi-cli-integration)
|
|
42
|
+
|
|
43
|
+
**Multi-agent shared brain across Claude Code/Desktop, Codex, Gemini,
|
|
44
|
+
Copilot, and VS Code.** Cross-session memory, self-improving skill
|
|
45
|
+
loops, and inter-agent signaling — one local MCP server turns parallel
|
|
46
|
+
agent instances into a coordinated multi-agent system instead of N
|
|
47
|
+
isolated chats.
|
|
48
|
+
|
|
49
|
+
Every connected client (Claude Code, Claude Desktop, Codex CLI +
|
|
50
|
+
desktop, Gemini, Copilot, every MCP-aware VS Code extension) shares
|
|
51
|
+
one SQLite store, one set of threads, one user model, and one learning
|
|
52
|
+
loop that improves the skill library autonomously over time.
|
|
53
|
+
|
|
54
|
+
The brief format is dense — structural tags, opaque IDs, ~6 KB per
|
|
55
|
+
session-start injection. Optimized for agent consumption, not human reading.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Why
|
|
60
|
+
|
|
61
|
+
Every agent CLI starts cold. Context dies at session boundaries.
|
|
62
|
+
Skills you taught Claude don't transfer to Codex. Threads you closed
|
|
63
|
+
in yesterday's Gemini chat are invisible to today's Copilot. Parallel
|
|
64
|
+
agent instances running the same task don't know about each other and
|
|
65
|
+
duplicate work or step on each other's writes.
|
|
66
|
+
|
|
67
|
+
thread-keeper is the substrate underneath. Three things that together
|
|
68
|
+
make it more than a memory store:
|
|
69
|
+
|
|
70
|
+
- **Collective memory** — threads, notes, verbatim quotes, dialectic
|
|
71
|
+
claims about you. Survives session, restart, CLI swap. One agent
|
|
72
|
+
records, every other agent (any CLI) reads. The brief injected at
|
|
73
|
+
session start gives a new agent everything the previous one knew.
|
|
74
|
+
- **Multi-agent coordination** — `spawn` primitive launches child
|
|
75
|
+
agents in parallel, each gets a self_cid + sees the same memory.
|
|
76
|
+
`broadcast` / `whisper` / `inbox` / `wait` / `ask` / `respond` let
|
|
77
|
+
concurrent sessions signal each other across CLIs. Parent /
|
|
78
|
+
children / sibling agents become a coordinated swarm, not isolated
|
|
79
|
+
chats.
|
|
80
|
+
- **Self-improving skill library** — five autonomous background loops
|
|
81
|
+
(auto-review on thread close, shadow-review daemon, extract
|
|
82
|
+
harvester, candidate-reviewer, weekly Curator) materialize
|
|
83
|
+
class-level skills as the agents work. Adapted to multi-CLI:
|
|
84
|
+
SKILL.md is the primary write target and gets mirrored to every
|
|
85
|
+
detected CLI's skills directory simultaneously
|
|
86
|
+
(`~/.claude/skills/`, `~/.codex/skills/`, `~/.threadkeeper/skills/`),
|
|
87
|
+
with lessons.md as a fallback for CLIs without a native skills
|
|
88
|
+
loader.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Quickstart
|
|
93
|
+
|
|
94
|
+
The shortest path — **PyPI + pipx** (recommended):
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
pipx install 'threadkeeper[semantic]' && thread-keeper-setup
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
`thread-keeper-setup` detects every CLI you have installed (Claude
|
|
101
|
+
Code / Claude Desktop / Codex CLI + desktop / Gemini / Copilot / VS
|
|
102
|
+
Code), registers the MCP server in each one's config, copies hooks to
|
|
103
|
+
`~/.threadkeeper/hooks/`, and writes a managed instructions block into
|
|
104
|
+
each CLI's per-user instructions file (`CLAUDE.md` / `AGENTS.md` /
|
|
105
|
+
`GEMINI.md` / `copilot-instructions.md` — Claude Desktop and VS Code
|
|
106
|
+
have no global instructions file, so that step is skipped for them).
|
|
107
|
+
|
|
108
|
+
Restart your CLI of choice. The SessionStart hook injects a brief on
|
|
109
|
+
first message; no manual `brief()` call required.
|
|
110
|
+
|
|
111
|
+
### Alternative installs
|
|
112
|
+
|
|
113
|
+
If you don't have `pipx` and don't want to install it:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
# uv (Rust-fast Python tool runner) — no clone, single binary on PATH
|
|
117
|
+
uv tool install 'threadkeeper[semantic]' && thread-keeper-setup
|
|
118
|
+
|
|
119
|
+
# Plain pip into a venv
|
|
120
|
+
python3 -m venv ~/.threadkeeper-venv
|
|
121
|
+
~/.threadkeeper-venv/bin/pip install 'threadkeeper[semantic]'
|
|
122
|
+
~/.threadkeeper-venv/bin/thread-keeper-setup
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
For development (editable install from a git checkout) or to track the
|
|
126
|
+
bleeding edge:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
# One-liner installer — clones to ~/thread-keeper, makes a venv,
|
|
130
|
+
# editable-installs, wires every detected CLI. Idempotent — re-run to
|
|
131
|
+
# update (it git-pulls + reinstalls).
|
|
132
|
+
curl -fsSL https://raw.githubusercontent.com/po4erk91/thread-keeper/main/install.sh | bash -s -- --semantic
|
|
133
|
+
|
|
134
|
+
# Or fully manual
|
|
135
|
+
git clone https://github.com/po4erk91/thread-keeper ~/thread-keeper
|
|
136
|
+
cd ~/thread-keeper && python3 -m venv .venv
|
|
137
|
+
.venv/bin/pip install -e '.[semantic]'
|
|
138
|
+
.venv/bin/thread-keeper-setup
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
To preview without writing anything:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
thread-keeper-setup --dry-run
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Multi-CLI integration
|
|
150
|
+
|
|
151
|
+
| CLI | MCP config | Instructions file | Hooks | Transcripts ingested |
|
|
152
|
+
|---|---|---|---|---|
|
|
153
|
+
| Claude Code | `~/.claude.json` `mcpServers` | `~/.claude/CLAUDE.md` | `~/.claude/settings.json` `hooks` | `~/.claude/projects/**/*.jsonl` |
|
|
154
|
+
| 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 app | none — chats live in Electron IndexedDB |
|
|
155
|
+
| Codex (CLI + desktop) | `~/.codex/config.toml` `[mcp_servers]` (shared between CLI and `Codex.app`) | `~/.codex/AGENTS.md` | not supported | `~/.codex/sessions/**/rollout-*.jsonl` |
|
|
156
|
+
| Gemini | `~/.gemini/settings.json` `mcpServers` | `~/.gemini/GEMINI.md` | `~/.gemini/settings.json` `hooks` | `~/.gemini/tmp/<user>/chats/session-*.jsonl` |
|
|
157
|
+
| Copilot | `~/.copilot/mcp-config.json` `mcpServers` | `~/.copilot/copilot-instructions.md` | `~/.copilot/hooks.json` | `~/.copilot/session-store.db` (sqlite) |
|
|
158
|
+
| 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 supported | none — extensions own their history |
|
|
159
|
+
|
|
160
|
+
Every CLI that produces parseable transcripts feeds the same
|
|
161
|
+
`dialog_messages` table with a `source` tag, so `dialog_search()` finds
|
|
162
|
+
matches regardless of where the conversation happened. Claude Desktop
|
|
163
|
+
and the VS Code adapter are the exceptions — MCP registration only;
|
|
164
|
+
their chats don't reach the table for now (Electron IndexedDB on the
|
|
165
|
+
Claude Desktop side; per-extension stores on the VS Code side).
|
|
166
|
+
|
|
167
|
+
VS Code's user-level `mcp.json` is the central host that **every
|
|
168
|
+
MCP-aware VS Code extension** consumes — GitHub Copilot Chat, the
|
|
169
|
+
Anthropic Claude IDE plugin, the OpenAI Codex IDE plugin, Continue,
|
|
170
|
+
Cline, … — so a single registration there reaches all of them at once.
|
|
171
|
+
|
|
172
|
+
Adding a new CLI = one file under `threadkeeper/adapters/` implementing
|
|
173
|
+
the `CLIAdapter` contract. See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Core systems
|
|
178
|
+
|
|
179
|
+
### Spawn — primary parallelism primitive
|
|
180
|
+
|
|
181
|
+
`spawn(prompt, slim=True, role=..., visible=False, ...)` launches a child
|
|
182
|
+
Claude session via a `claude -p` subprocess. By default `slim=True`: the
|
|
183
|
+
child loads only the thread-keeper MCP, no embeddings, no third-party
|
|
184
|
+
servers. ~500 MB RSS versus ~1.3 GB for a full child. Heuristic for the
|
|
185
|
+
parent: N≥2 modular independent units of ≥5 min each = spawn signal.
|
|
186
|
+
|
|
187
|
+
A daemon measures combined child RSS every 10 s; admission control
|
|
188
|
+
refuses a new spawn that would exceed `THREADKEEPER_SPAWN_BUDGET_MB`
|
|
189
|
+
(3 GB default). Slim children that need semantic search delegate to the
|
|
190
|
+
parent via `search_via_parent` — no per-child copy of sentence-transformers.
|
|
191
|
+
|
|
192
|
+
### Learning loops
|
|
193
|
+
|
|
194
|
+
Five loops turn raw agent dialog into a curated, multi-CLI-mirrored
|
|
195
|
+
skill library — autonomously, without requiring agents to call
|
|
196
|
+
`note()` / `verbatim_user()` / `close_thread()` on their own (audit
|
|
197
|
+
shows agents focused on their primary task rarely do).
|
|
198
|
+
|
|
199
|
+
**Pipeline at a glance:**
|
|
200
|
+
|
|
201
|
+
```
|
|
202
|
+
every CLI's transcripts
|
|
203
|
+
│
|
|
204
|
+
▼ (ingest, every 30s — always-on)
|
|
205
|
+
dialog_messages ◄──────────────────────────────────────┐
|
|
206
|
+
│ │
|
|
207
|
+
├────────► [1] auto_review on close_thread │
|
|
208
|
+
│ (agent triggers — rare) │
|
|
209
|
+
│ │ │
|
|
210
|
+
├────────► [2] shadow_review daemon │
|
|
211
|
+
│ (cron, every 15 min) │
|
|
212
|
+
│ │ │
|
|
213
|
+
├────────► [3] extract daemon │
|
|
214
|
+
│ (cron, every 10 min) │
|
|
215
|
+
│ │ │
|
|
216
|
+
│ extract_candidates │
|
|
217
|
+
│ │ │
|
|
218
|
+
│ ▼ │
|
|
219
|
+
│ [4] candidate_reviewer daemon │
|
|
220
|
+
│ (cron, every 1 h) ──────────────┤
|
|
221
|
+
│ │ │
|
|
222
|
+
▼ ▼ │
|
|
223
|
+
brief() SKILL.md + lessons.md ─► skill_usage │
|
|
224
|
+
│ │ │ │
|
|
225
|
+
│ ▼ ▼ │
|
|
226
|
+
│ (every detected CLI's │ │
|
|
227
|
+
│ skills/ directory) │ │
|
|
228
|
+
│ │ │ │
|
|
229
|
+
│ └──────► [5] Curator daemon ───┘
|
|
230
|
+
│ (cron, every 7d)
|
|
231
|
+
│ │
|
|
232
|
+
│ ▼
|
|
233
|
+
│ REPORT-<date>.md
|
|
234
|
+
▼
|
|
235
|
+
injected into every new session at SessionStart
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
**Each loop in one row:**
|
|
239
|
+
|
|
240
|
+
| # | Loop | Default tick | Reads | Writes |
|
|
241
|
+
|---|---|---|---|---|
|
|
242
|
+
| 1 | auto_review on close_thread | on `close_thread()` for rich threads | the thread's notes | SKILL.md, lessons.md |
|
|
243
|
+
| 2 | shadow_review daemon | every 15 min (env knob) | recent `dialog_messages` window | SKILL.md, lessons.md |
|
|
244
|
+
| 3 | extract daemon | every 10 min (env knob) | recent `dialog_messages` window | `extract_candidates` pending queue |
|
|
245
|
+
| 4 | candidate-reviewer daemon | every 1 h (env knob) | pending candidates queue | SKILL.md (create/patch) / notes / verbatim / reject |
|
|
246
|
+
| 5 | Curator daemon | every 7 days (env knob) | every existing lesson + recently-touched skill | REPORT-`<date>`.md (advisory) or direct PATCH/PRUNE/CONSOLIDATE |
|
|
247
|
+
|
|
248
|
+
All five write into the universal Skill format (`SKILL.md` under each
|
|
249
|
+
detected CLI's skills directory — `~/.claude/skills/`,
|
|
250
|
+
`~/.codex/skills/`, plus the canonical `~/.threadkeeper/skills/`
|
|
251
|
+
mirror), with `~/.threadkeeper/lessons.md` as a CLI-agnostic fallback
|
|
252
|
+
for clients without a native skills loader (Gemini, Copilot, bare
|
|
253
|
+
MCP).
|
|
254
|
+
|
|
255
|
+
#### 1. Auto-review on close_thread
|
|
256
|
+
|
|
257
|
+
When a closed thread is rich (≥5 notes, ≥2 insight/move),
|
|
258
|
+
`close_thread` spawns a slim child with `SKILL_REVIEW_PROMPT` + the
|
|
259
|
+
thread's notes. The prompt is rubric-form (Q1–Q5 yes/no) with explicit
|
|
260
|
+
positive examples for incident-vs-rule classification. The fork also
|
|
261
|
+
receives a "recently active skills" block so it prefers PATCHing
|
|
262
|
+
existing umbrellas over creating new ones (*active-update bias*).
|
|
263
|
+
Child appends a lesson via `lesson_append`, optionally mirrors to
|
|
264
|
+
`~/.claude/skills/<name>/SKILL.md`, then closes with
|
|
265
|
+
`mark_skill_materialized`. Opt in with `THREADKEEPER_AUTO_REVIEW=1`.
|
|
266
|
+
|
|
267
|
+
#### 2. Shadow-review daemon
|
|
268
|
+
|
|
269
|
+
Every `THREADKEEPER_SHADOW_REVIEW_INTERVAL_S` seconds (default off,
|
|
270
|
+
900 = 15 min recommended) scans the diff of `dialog_messages` since
|
|
271
|
+
the last cursor **across all CLIs at once**. The window filters
|
|
272
|
+
internal review-child sessions (no self-pollution) and strips adapter
|
|
273
|
+
`[tool_result]` / `[tool_call]` noise (the "clean context" rule). If
|
|
274
|
+
≥500 chars of meaningful signal remain, spawns a slim observer child
|
|
275
|
+
that decides on class-level learning. Idempotent through
|
|
276
|
+
`events.kind='shadow_review_pass'`.
|
|
277
|
+
|
|
278
|
+
#### 3. Extract daemon
|
|
279
|
+
|
|
280
|
+
Every `THREADKEEPER_EXTRACT_INTERVAL_S` seconds (default off, 600 =
|
|
281
|
+
10 min recommended) scans recent `dialog_messages` with heuristic
|
|
282
|
+
matchers: locale-aware "I want / next time / always" patterns,
|
|
283
|
+
headers + insight markers, bullet regularities, and paraphrase
|
|
284
|
+
clusters via cosine ≥ 0.80. Each match enqueues a row in
|
|
285
|
+
`extract_candidates.status='pending'`. Same self-pollution filter as
|
|
286
|
+
shadow_review (internal review-child sessions excluded) plus
|
|
287
|
+
message-level noise filter (compaction summaries, SKILL.md
|
|
288
|
+
injections, subagent role prompts, test-runner log dumps).
|
|
289
|
+
|
|
290
|
+
Where shadow extracts CLASS-LEVEL durable rules, extract harvests
|
|
291
|
+
PER-INCIDENT decision-shaped utterances. Heuristic, not LLM —
|
|
292
|
+
findings get refined by loop 4.
|
|
293
|
+
|
|
294
|
+
#### 4. Candidate-reviewer daemon
|
|
295
|
+
|
|
296
|
+
Every `THREADKEEPER_CANDIDATE_REVIEW_INTERVAL_S` seconds (default off,
|
|
297
|
+
3600 = 1 h recommended) consumes the pending queue extract built up.
|
|
298
|
+
Spawns a slim LLM child that decides per candidate or per coherent
|
|
299
|
+
cluster:
|
|
300
|
+
|
|
301
|
+
- **SKILL.create** — class-level rule; merge 2-5 related candidates
|
|
302
|
+
into one skill (active-update bias prefers PATCH over CREATE)
|
|
303
|
+
- **SKILL.patch** — refines a recently-active skill
|
|
304
|
+
- **SKILL.write_file** — adds `references/<topic>.md` under an
|
|
305
|
+
existing umbrella
|
|
306
|
+
- **NOTE** — per-incident decision (requires `thread_id`)
|
|
307
|
+
- **VERBATIM** — user quote worth preserving in `brief()`
|
|
308
|
+
- **REJECT** — false positive that slipped past extract's filters
|
|
309
|
+
|
|
310
|
+
Hard limits: max 2 new skills per pass, `[PROTECTED]` (pinned +
|
|
311
|
+
foreground-authored) skills off-limits. Closes the gap between
|
|
312
|
+
heuristic harvest and SKILL.md materialization — previously pending
|
|
313
|
+
candidates accumulated indefinitely waiting for an agent to call
|
|
314
|
+
`accept_candidate()` manually.
|
|
315
|
+
|
|
316
|
+
#### 5. Autonomous Curator
|
|
317
|
+
|
|
318
|
+
Every `THREADKEEPER_CURATOR_INTERVAL_S` seconds (default off, 604800
|
|
319
|
+
= 7 days recommended) spawns a slim child that reviews the EXISTING
|
|
320
|
+
`lessons.md` + `skill_usage` inventory and writes
|
|
321
|
+
`~/.threadkeeper/curator/REPORT-<isodate>.md` with KEEP / PATCH /
|
|
322
|
+
CONSOLIDATE / PRUNE recommendations. Pinned and foreground-authored
|
|
323
|
+
entries are marked `[PROTECTED]` in the inventory so the curator
|
|
324
|
+
never proposes destructive changes against them.
|
|
325
|
+
|
|
326
|
+
Phase 1 is advisory-only (REPORT only); flip
|
|
327
|
+
`THREADKEEPER_CURATOR_DESTRUCTIVE=1` once trust builds to let the
|
|
328
|
+
child apply its own recommendations directly.
|
|
329
|
+
|
|
330
|
+
#### Honest take
|
|
331
|
+
|
|
332
|
+
What works **without** agent cooperation (passive, opt-in via env):
|
|
333
|
+
|
|
334
|
+
- Loop 2 (shadow), 3 (extract), 4 (candidate-reviewer), 5 (curator) —
|
|
335
|
+
all run from the parent process, never require `note()` or
|
|
336
|
+
`close_thread()` from the agent
|
|
337
|
+
|
|
338
|
+
What depends on the agent **calling tools explicitly**:
|
|
339
|
+
|
|
340
|
+
- Loop 1 (auto-review on close_thread) — only fires if the agent
|
|
341
|
+
closes threads, which the audit shows agents focused on coding
|
|
342
|
+
tasks rarely do
|
|
343
|
+
- Manual `skill_record(outcome='wrong')` — strongest feedback signal
|
|
344
|
+
to the Curator, but agents need to remember to flag bad skills
|
|
345
|
+
|
|
346
|
+
The whole point of having five loops (not one) is graceful
|
|
347
|
+
degradation: even when agents don't actively contribute, loops 2-5
|
|
348
|
+
keep the library growing from passive observation of the dialog
|
|
349
|
+
stream.
|
|
350
|
+
|
|
351
|
+
### Dialectic user model
|
|
352
|
+
|
|
353
|
+
A model of you, accumulated as you use the agent. `dialectic_claim`,
|
|
354
|
+
`dialectic_evidence` (support / contradict),
|
|
355
|
+
`dialectic_synthesis`, `dialectic_supersede`. Honcho-inspired
|
|
356
|
+
**weighted, smoothed** ratio
|
|
357
|
+
`(Σw_support − Σw_contradict) / (Σw_support + Σw_contradict + 3)`
|
|
358
|
+
→ low / medium / high / disputed confidence.
|
|
359
|
+
Grouped by domain (style, values, workflow, ...) in `brief()`.
|
|
360
|
+
|
|
361
|
+
**Source-based evidence discount.** Each evidence row's effective weight
|
|
362
|
+
is `base_weight × discount(WRITE_ORIGIN)`. Foreground (direct user / human
|
|
363
|
+
signal) = 1.0. shadow_review / background_review / candidate_review /
|
|
364
|
+
curator review-forks = 0.5. Structural defence against self-confirmation
|
|
365
|
+
loops: a claim that surfaces in `brief()` and then gets "confirmed" by a
|
|
366
|
+
review-fork reading the same dialog can't ride that internal evidence
|
|
367
|
+
all the way to high confidence — internal evidence buys half as much.
|
|
368
|
+
|
|
369
|
+
**Discrete tier on each claim** — `hypothesis → observed → validated`
|
|
370
|
+
(plus `disputed`). Independent of the continuous confidence band; tier
|
|
371
|
+
is the **action-gating** signal:
|
|
372
|
+
|
|
373
|
+
- `validated` → agent applies by default (★ in brief)
|
|
374
|
+
- `observed` → agent references and may mention the assumption (· in brief)
|
|
375
|
+
- `hypothesis` → active probe; surfaces in a separate `currently_testing`
|
|
376
|
+
block so the agent watches the next user moves through that lens
|
|
377
|
+
|
|
378
|
+
Transitions are discrete events (`tier_promoted` / `tier_demoted` in the
|
|
379
|
+
`events` table) with timestamps for an auditable trail of when each
|
|
380
|
+
claim earned trust. Thresholds:
|
|
381
|
+
|
|
382
|
+
- `hypothesis → observed`: `w_support ≥ 2.0` (claim has real backing)
|
|
383
|
+
- `observed → validated`: `w_support ≥ 4.0` **and** no contradict in 14 days
|
|
384
|
+
- `validated → observed`: any recent contradict (demote on user pushback)
|
|
385
|
+
- any → `disputed`: `w_contradict > w_support`
|
|
386
|
+
- `disputed → hypothesis`: support overtakes contradict (recovery path)
|
|
387
|
+
|
|
388
|
+
### i18n bundle
|
|
389
|
+
|
|
390
|
+
All multilingual regex and prompt fragments live in
|
|
391
|
+
`threadkeeper/i18n.py` — the rest of the codebase stays English-only.
|
|
392
|
+
Currently ships ten locales: **English, Mandarin Chinese, Hindi,
|
|
393
|
+
Spanish, Portuguese, French, German, Arabic, Russian, Japanese**
|
|
394
|
+
(~82 % of the world's speakers).
|
|
395
|
+
|
|
396
|
+
Adding a new language is a two-file PR — see [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
397
|
+
|
|
398
|
+
---
|
|
399
|
+
|
|
400
|
+
## Configuration
|
|
401
|
+
|
|
402
|
+
The most-used env knobs (full list in `threadkeeper/config.py`):
|
|
403
|
+
|
|
404
|
+
| Knob | Default | Purpose |
|
|
405
|
+
|---|---|---|
|
|
406
|
+
| `THREADKEEPER_DB` | `~/.threadkeeper/db.sqlite` | SQLite file |
|
|
407
|
+
| `THREADKEEPER_AUTO_REVIEW` | "" (off) | auto-review on `close_thread` |
|
|
408
|
+
| `THREADKEEPER_SHADOW_REVIEW_INTERVAL_S` | 0 (off) | shadow daemon tick (s) |
|
|
409
|
+
| `THREADKEEPER_SHADOW_REVIEW_WINDOW_S` | 900 | sliding window for shadow scan (s) |
|
|
410
|
+
| `THREADKEEPER_EXTRACT_INTERVAL_S` | 0 (off) | extract daemon tick (s); 600 = 10 min recommended |
|
|
411
|
+
| `THREADKEEPER_EXTRACT_WINDOW_MIN` | 30 | sliding dialog window per extract pass (min) |
|
|
412
|
+
| `THREADKEEPER_CANDIDATE_REVIEW_INTERVAL_S` | 0 (off) | candidate-reviewer daemon tick (s); 3600 = 1h recommended |
|
|
413
|
+
| `THREADKEEPER_CANDIDATE_REVIEW_MIN` | 3 | min pending candidates before reviewer engages |
|
|
414
|
+
| `THREADKEEPER_CURATOR_INTERVAL_S` | 0 (off) | curator daemon tick (s); 604800 = 7d recommended |
|
|
415
|
+
| `THREADKEEPER_CURATOR_MIN_LESSONS` | 3 | min lessons before curator engages |
|
|
416
|
+
| `THREADKEEPER_CURATOR_DESTRUCTIVE` | "" (advisory) | when "1": curator child applies its own PATCH/PRUNE/CONSOLIDATE directly instead of writing advisory REPORT only |
|
|
417
|
+
| `THREADKEEPER_SPAWN_BUDGET_MB` | 3072 | combined child RSS cap (MB); 0 disables |
|
|
418
|
+
| `THREADKEEPER_INGEST_INTERVAL_S` | 3 | transcript ingest tick (s) |
|
|
419
|
+
| `THREADKEEPER_NO_EMBEDDINGS` | "" | force-disable sentence-transformers |
|
|
420
|
+
| `THREADKEEPER_SKILL_NUDGE_INTERVAL` | 10 | events between `skill_hint` nudges |
|
|
421
|
+
|
|
422
|
+
Persist them via `~/.claude/settings.json`'s `env` block (Claude Code) or
|
|
423
|
+
the equivalent env section in each CLI's config. Hot-config reload is
|
|
424
|
+
[tracked](https://github.com/po4erk91/thread-keeper/issues/2).
|
|
425
|
+
|
|
426
|
+
### Per-loop agent dispatch
|
|
427
|
+
|
|
428
|
+
By default every learning-loop spawn runs through the same CLI that
|
|
429
|
+
hosts thread-keeper — Opus-session ⇒ Opus spawn, Codex-session ⇒
|
|
430
|
+
Codex spawn, etc. Detection: process-tree walk at startup, cached for
|
|
431
|
+
the server lifetime. The MCP tool `spawn_status()` shows the live
|
|
432
|
+
resolution table.
|
|
433
|
+
|
|
434
|
+
Override per role via `~/.threadkeeper/spawn.toml`:
|
|
435
|
+
|
|
436
|
+
```toml
|
|
437
|
+
[default]
|
|
438
|
+
agent = "auto" # "auto" = use active CLI (default)
|
|
439
|
+
|
|
440
|
+
[loops]
|
|
441
|
+
# Force specific roles to specific CLIs regardless of active host
|
|
442
|
+
shadow_observer = "claude" # heaviest reasoning → keep on Claude
|
|
443
|
+
curator = "codex" # weekly audit → Codex is fine
|
|
444
|
+
candidate_reviewer = "auto" # follow active CLI
|
|
445
|
+
archivist = "claude" # close_thread auto-review
|
|
446
|
+
extract = "auto" # this one is local (no spawn)
|
|
447
|
+
|
|
448
|
+
[models]
|
|
449
|
+
# Optional per-CLI model pin — overrides each CLI's own default
|
|
450
|
+
claude = "opus"
|
|
451
|
+
codex = "gpt-5.4"
|
|
452
|
+
gemini = "gemini-2.5-pro"
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
Or via env (highest priority, overrides the TOML):
|
|
456
|
+
|
|
457
|
+
```bash
|
|
458
|
+
export THREADKEEPER_SPAWN_DEFAULT=codex # global default
|
|
459
|
+
export THREADKEEPER_SPAWN_LOOP_CURATOR=gemini # per-role
|
|
460
|
+
export THREADKEEPER_SPAWN_MODEL_CLAUDE=opus # per-CLI model
|
|
461
|
+
export THREADKEEPER_ACTIVE_CLI=claude # force detection
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
Adapters without headless support (Claude Desktop, VS Code) can't be
|
|
465
|
+
spawn targets — `spawn_status()` reports them as "no adapter" and any
|
|
466
|
+
override pointing at them falls back to the next priority level.
|
|
467
|
+
|
|
468
|
+
---
|
|
469
|
+
|
|
470
|
+
## Hygiene tools
|
|
471
|
+
|
|
472
|
+
Two tools keep the memory tidy — both default to `dry_run=True`, run
|
|
473
|
+
them with `dry_run=False` to apply:
|
|
474
|
+
|
|
475
|
+
- **`consolidate()`** — dedup near-identical notes (intra-thread cosine
|
|
476
|
+
≥ 0.95), deduplicate verbatim quotes, demote untouched-active threads
|
|
477
|
+
to `idle` after 30 days, release orphaned thread claims.
|
|
478
|
+
- **`validate_threads()`** — heuristic triage of active threads with
|
|
479
|
+
four categories (first match wins per thread):
|
|
480
|
+
- `no_notes_old` — active with zero notes ≥ 7 days → close as abandoned.
|
|
481
|
+
- `shipped` — last note matches a shipped-marker regex (EN+RU:
|
|
482
|
+
shipped/fixed/works/passed/done/merged/закрыто/готово/сделано/…)
|
|
483
|
+
and has settled ≥ 3 days → close with the last move as outcome.
|
|
484
|
+
- `dropped_open_q` — last note is an `open_q` left unfollowed
|
|
485
|
+
≥ 14 days → close as dropped.
|
|
486
|
+
- `stale_idle` — any active not touched in ≥ 30 days → demote to
|
|
487
|
+
`idle` (not closed — revives on next `note()`).
|
|
488
|
+
|
|
489
|
+
Idle threads are never touched. Tunable via `no_notes_days`,
|
|
490
|
+
`shipped_settle_days`, `drop_open_q_days`, `stale_days`, and
|
|
491
|
+
`shipped_markers` (comma-separated extra tokens).
|
|
492
|
+
|
|
493
|
+
---
|
|
494
|
+
|
|
495
|
+
## Storage
|
|
496
|
+
|
|
497
|
+
`~/.threadkeeper/db.sqlite` (overridable via `THREADKEEPER_DB`). WAL
|
|
498
|
+
mode for multi-writer concurrency. Optional `notes_vec` / `dialog_vec`
|
|
499
|
+
HNSW indexes through `sqlite-vec` for sub-linear semantic search;
|
|
500
|
+
fallback to Python-side cosine when the extension is missing.
|
|
501
|
+
|
|
502
|
+
One file. Backup = `cp`. Wipe memory = `rm`.
|
|
503
|
+
|
|
504
|
+
Hooks and small runtime artifacts: `~/.threadkeeper/hooks/`.
|
|
505
|
+
|
|
506
|
+
---
|
|
507
|
+
|
|
508
|
+
## Verifying ingest across CLIs
|
|
509
|
+
|
|
510
|
+
```bash
|
|
511
|
+
python scripts/tk_verify_ingest.py
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
Walks every installed CLI adapter, parses recent transcripts in an
|
|
515
|
+
isolated tempdir DB, reports per-source message counts and any silent
|
|
516
|
+
parse failures. Read-only with respect to live state.
|
|
517
|
+
|
|
518
|
+
---
|
|
519
|
+
|
|
520
|
+
## Tests
|
|
521
|
+
|
|
522
|
+
```bash
|
|
523
|
+
pip install -e '.[semantic,dev]'
|
|
524
|
+
python -m pytest
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
495 tests passing on Python 3.11 / 3.12 / 3.13 (1 skipped). CI runs
|
|
528
|
+
the suite on every push and PR.
|
|
529
|
+
|
|
530
|
+
---
|
|
531
|
+
|
|
532
|
+
## Project layout
|
|
533
|
+
|
|
534
|
+
```
|
|
535
|
+
threadkeeper/
|
|
536
|
+
├── server.py # MCP entry: python -m threadkeeper.server
|
|
537
|
+
├── _setup.py # `thread-keeper-setup` installer
|
|
538
|
+
├── config.py # env-driven defaults
|
|
539
|
+
├── db.py # SQLite schema + sqlite-vec loader
|
|
540
|
+
├── identity.py # session, self-cid, daemon launchers
|
|
541
|
+
├── ingest.py # adapter-driven transcript ingest
|
|
542
|
+
├── brief.py # render_brief / render_context
|
|
543
|
+
├── shadow_review.py # autonomous learning observer
|
|
544
|
+
├── i18n.py # 10 locales of regex + prompt bundles
|
|
545
|
+
├── adapters/ # one file per supported CLI
|
|
546
|
+
│ ├── claude_code.py
|
|
547
|
+
│ ├── claude_desktop.py
|
|
548
|
+
│ ├── codex.py
|
|
549
|
+
│ ├── gemini.py
|
|
550
|
+
│ ├── copilot.py
|
|
551
|
+
│ └── vscode.py
|
|
552
|
+
└── tools/ # @mcp.tool entries — 89 of them
|
|
553
|
+
├── threads.py
|
|
554
|
+
├── peers.py
|
|
555
|
+
├── spawn.py
|
|
556
|
+
├── skills.py
|
|
557
|
+
├── dialectic.py
|
|
558
|
+
├── validate.py
|
|
559
|
+
└── ...
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
Detailed map in [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).
|
|
563
|
+
Open work in [docs/ROADMAP.md](docs/ROADMAP.md) and the
|
|
564
|
+
[Issues tab](https://github.com/po4erk91/thread-keeper/issues).
|
|
565
|
+
|
|
566
|
+
---
|
|
567
|
+
|
|
568
|
+
## Contributing
|
|
569
|
+
|
|
570
|
+
PRs welcome — see [CONTRIBUTING.md](CONTRIBUTING.md) for the project
|
|
571
|
+
map, test workflow, and recipes for adding a new CLI adapter or a new
|
|
572
|
+
locale. Look for the `good-first-issue` label.
|
|
573
|
+
|
|
574
|
+
---
|
|
575
|
+
|
|
576
|
+
## License
|
|
577
|
+
|
|
578
|
+
MIT — see [LICENSE](LICENSE).
|