threadkeeper 0.9.0__tar.gz → 0.9.2__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.
Files changed (148) hide show
  1. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/PKG-INFO +19 -14
  2. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/README.md +18 -13
  3. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/pyproject.toml +10 -1
  4. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_candidate_reviewer.py +36 -0
  5. threadkeeper-0.9.2/tests/test_menubar_app.py +69 -0
  6. threadkeeper-0.9.2/threadkeeper/assets/macos-agent-status/Info.plist +25 -0
  7. threadkeeper-0.9.2/threadkeeper/assets/macos-agent-status/README.md +65 -0
  8. threadkeeper-0.9.2/threadkeeper/assets/macos-agent-status/ThreadKeeperAgentStatus.swift +482 -0
  9. threadkeeper-0.9.2/threadkeeper/assets/macos-agent-status/build.sh +27 -0
  10. threadkeeper-0.9.2/threadkeeper/assets/macos-agent-status/install.sh +58 -0
  11. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/candidate_reviewer.py +117 -43
  12. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/menubar_app.py +28 -2
  13. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper.egg-info/PKG-INFO +19 -14
  14. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper.egg-info/SOURCES.txt +6 -0
  15. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/LICENSE +0 -0
  16. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/setup.cfg +0 -0
  17. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_adapters.py +0 -0
  18. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_agent_status.py +0 -0
  19. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_brief_footprint.py +0 -0
  20. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_brief_sections.py +0 -0
  21. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_config_settings.py +0 -0
  22. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_core_memory.py +0 -0
  23. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_curator.py +0 -0
  24. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_dashboard.py +0 -0
  25. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_delegated_search.py +0 -0
  26. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_dialectic.py +0 -0
  27. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_dialectic_feed_tools.py +0 -0
  28. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_dialectic_miner.py +0 -0
  29. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_dialectic_observation_resolve.py +0 -0
  30. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_dialectic_recompute.py +0 -0
  31. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_dialectic_tier.py +0 -0
  32. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_dialectic_validator.py +0 -0
  33. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_error_paths.py +0 -0
  34. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_evolve_applier.py +0 -0
  35. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_evolve_apply_2.py +0 -0
  36. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_evolve_apply_3.py +0 -0
  37. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_evolve_daemon.py +0 -0
  38. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_extract_daemon.py +0 -0
  39. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_extract_dedup.py +0 -0
  40. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_i18n_multilang.py +0 -0
  41. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_identity.py +0 -0
  42. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_ingest_status.py +0 -0
  43. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_lessons.py +0 -0
  44. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_memory_guard.py +0 -0
  45. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_missed_spawns.py +0 -0
  46. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_nudges.py +0 -0
  47. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_onnx_embeddings.py +0 -0
  48. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_panel.py +0 -0
  49. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_probe_daemon.py +0 -0
  50. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_process_health.py +0 -0
  51. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_search_fts_punctuation.py +0 -0
  52. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_shadow_review.py +0 -0
  53. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_skill_hint.py +0 -0
  54. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_skill_passive_tier.py +0 -0
  55. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_skill_tier.py +0 -0
  56. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_skill_use_parser.py +0 -0
  57. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_skill_watcher.py +0 -0
  58. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_skills.py +0 -0
  59. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_spawn_budget.py +0 -0
  60. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_spawn_codex_stdin.py +0 -0
  61. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_spawn_config.py +0 -0
  62. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_spawn_hint.py +0 -0
  63. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_spawn_reap.py +0 -0
  64. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_spawn_slim.py +0 -0
  65. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_spawn_wrap.py +0 -0
  66. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_thread_janitor.py +0 -0
  67. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_threads.py +0 -0
  68. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_tools_smoke.py +0 -0
  69. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_validate_threads.py +0 -0
  70. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/tests/test_vec_search.py +0 -0
  71. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/__init__.py +0 -0
  72. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/_mcp.py +0 -0
  73. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/_setup.py +0 -0
  74. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/_spawn_wrap.py +0 -0
  75. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/adapters/__init__.py +0 -0
  76. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/adapters/_hook_helpers.py +0 -0
  77. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/adapters/base.py +0 -0
  78. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/adapters/claude_code.py +0 -0
  79. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/adapters/claude_desktop.py +0 -0
  80. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/adapters/codex.py +0 -0
  81. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/adapters/copilot.py +0 -0
  82. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/adapters/gemini.py +0 -0
  83. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/adapters/vscode.py +0 -0
  84. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/agent_status.py +0 -0
  85. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/brief.py +0 -0
  86. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/config.py +0 -0
  87. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/curator.py +0 -0
  88. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/db.py +0 -0
  89. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/dialectic_miner.py +0 -0
  90. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/dialectic_validator.py +0 -0
  91. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/embeddings.py +0 -0
  92. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/evolve_applier.py +0 -0
  93. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/evolve_daemon.py +0 -0
  94. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/extract_daemon.py +0 -0
  95. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/helpers.py +0 -0
  96. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/i18n.py +0 -0
  97. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/identity.py +0 -0
  98. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/ingest.py +0 -0
  99. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/lessons.py +0 -0
  100. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/memory_guard.py +0 -0
  101. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/migrate_embeddings.py +0 -0
  102. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/nudges.py +0 -0
  103. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/probe_daemon.py +0 -0
  104. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/process_health.py +0 -0
  105. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/review_prompts.py +0 -0
  106. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/search_proxy.py +0 -0
  107. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/server.py +0 -0
  108. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/shadow_review.py +0 -0
  109. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/skill_watcher.py +0 -0
  110. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/spawn_budget.py +0 -0
  111. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/spawn_config.py +0 -0
  112. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/thread_janitor.py +0 -0
  113. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/tools/__init__.py +0 -0
  114. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/tools/agent_status.py +0 -0
  115. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/tools/candidate_reviewer.py +0 -0
  116. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/tools/concepts.py +0 -0
  117. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/tools/consolidate.py +0 -0
  118. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/tools/core_memory.py +0 -0
  119. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/tools/correlation.py +0 -0
  120. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/tools/curator.py +0 -0
  121. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/tools/dashboard.py +0 -0
  122. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/tools/dialectic.py +0 -0
  123. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/tools/dialectic_feed.py +0 -0
  124. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/tools/dialog.py +0 -0
  125. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/tools/distill.py +0 -0
  126. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/tools/evolve_applier.py +0 -0
  127. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/tools/extract.py +0 -0
  128. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/tools/graph.py +0 -0
  129. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/tools/invariants.py +0 -0
  130. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/tools/lessons.py +0 -0
  131. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/tools/memory_guard.py +0 -0
  132. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/tools/missed_spawns.py +0 -0
  133. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/tools/panel.py +0 -0
  134. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/tools/peers.py +0 -0
  135. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/tools/pickup.py +0 -0
  136. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/tools/probes.py +0 -0
  137. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/tools/process_health.py +0 -0
  138. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/tools/session.py +0 -0
  139. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/tools/shadow_review.py +0 -0
  140. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/tools/skills.py +0 -0
  141. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/tools/spawn.py +0 -0
  142. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/tools/style.py +0 -0
  143. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/tools/threads.py +0 -0
  144. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper/tools/validate.py +0 -0
  145. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper.egg-info/dependency_links.txt +0 -0
  146. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper.egg-info/entry_points.txt +0 -0
  147. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper.egg-info/requires.txt +0 -0
  148. {threadkeeper-0.9.0 → threadkeeper-0.9.2}/threadkeeper.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: threadkeeper
3
- Version: 0.9.0
3
+ Version: 0.9.2
4
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
5
  Author: thread-keeper contributors
6
6
  License: MIT
@@ -211,18 +211,20 @@ tk-agent-status --json
211
211
  `apps/macos-agent-status/` contains a small macOS menu-bar app that polls this
212
212
  command every 5 seconds and shows every autonomous learning loop: enabled/off,
213
213
  running/idle/ready, last pass, backlog, and active child RSS when that loop has
214
- spawned a worker. Active loops are sorted first (`running`, then `ready`), so
215
- background work stays at the top of the panel. The app also requests macOS
216
- notification permission and sends a notification when a newly completed
217
- autonomous child task produces a useful result in `recent_results`; the first
218
- poll only marks existing results as seen, so old completions do not spam
219
- notifications. Probe backlog is due objective probes only, not every registered
220
- probe, so a healthy cooldown shows `0 due probes` instead of looking stuck. On
221
- macOS, `python -m threadkeeper.server` automatically installs and launches it
222
- on MCP startup. Set `THREADKEEPER_MENUBAR_AUTO_LAUNCH=0` to disable that
223
- behavior.
224
-
225
- Manual fallback:
214
+ spawned a worker. PyPI wheels and sdists also bundle the same Swift source under
215
+ `threadkeeper/assets/macos-agent-status/`, so a normal `pipx`/`uv tool` install
216
+ does not need a git checkout for the widget to build. Active loops are sorted
217
+ first (`running`, then `ready`), so background work stays at the top of the
218
+ panel. The app also requests macOS notification permission and sends a
219
+ notification when a newly completed autonomous child task produces a useful
220
+ result in `recent_results`; the first poll only marks existing results as seen,
221
+ so old completions do not spam notifications. Probe backlog is due objective
222
+ probes only, not every registered probe, so a healthy cooldown shows `0 due
223
+ probes` instead of looking stuck. On macOS, `python -m threadkeeper.server`
224
+ automatically installs and launches it on MCP startup. Set
225
+ `THREADKEEPER_MENUBAR_AUTO_LAUNCH=0` to disable that behavior.
226
+
227
+ Manual fallback from a source checkout:
226
228
 
227
229
  ```sh
228
230
  cd apps/macos-agent-status
@@ -365,7 +367,10 @@ Hard limits: max 2 new skills per pass, `[PROTECTED]` (pinned +
365
367
  foreground-authored) skills off-limits. Closes the gap between
366
368
  heuristic harvest and SKILL.md materialization — previously pending
367
369
  candidates accumulated indefinitely waiting for an agent to call
368
- `accept_candidate()` manually.
370
+ `accept_candidate()` manually. The loop is machine-wide single-flight:
371
+ while one reviewer child is running, other foreground servers/ticks report
372
+ `candidate_review_running` instead of spawning another child for the same
373
+ queue.
369
374
 
370
375
  #### 5. Autonomous Curator
371
376
 
@@ -170,18 +170,20 @@ tk-agent-status --json
170
170
  `apps/macos-agent-status/` contains a small macOS menu-bar app that polls this
171
171
  command every 5 seconds and shows every autonomous learning loop: enabled/off,
172
172
  running/idle/ready, last pass, backlog, and active child RSS when that loop has
173
- spawned a worker. Active loops are sorted first (`running`, then `ready`), so
174
- background work stays at the top of the panel. The app also requests macOS
175
- notification permission and sends a notification when a newly completed
176
- autonomous child task produces a useful result in `recent_results`; the first
177
- poll only marks existing results as seen, so old completions do not spam
178
- notifications. Probe backlog is due objective probes only, not every registered
179
- probe, so a healthy cooldown shows `0 due probes` instead of looking stuck. On
180
- macOS, `python -m threadkeeper.server` automatically installs and launches it
181
- on MCP startup. Set `THREADKEEPER_MENUBAR_AUTO_LAUNCH=0` to disable that
182
- behavior.
183
-
184
- Manual fallback:
173
+ spawned a worker. PyPI wheels and sdists also bundle the same Swift source under
174
+ `threadkeeper/assets/macos-agent-status/`, so a normal `pipx`/`uv tool` install
175
+ does not need a git checkout for the widget to build. Active loops are sorted
176
+ first (`running`, then `ready`), so background work stays at the top of the
177
+ panel. The app also requests macOS notification permission and sends a
178
+ notification when a newly completed autonomous child task produces a useful
179
+ result in `recent_results`; the first poll only marks existing results as seen,
180
+ so old completions do not spam notifications. Probe backlog is due objective
181
+ probes only, not every registered probe, so a healthy cooldown shows `0 due
182
+ probes` instead of looking stuck. On macOS, `python -m threadkeeper.server`
183
+ automatically installs and launches it on MCP startup. Set
184
+ `THREADKEEPER_MENUBAR_AUTO_LAUNCH=0` to disable that behavior.
185
+
186
+ Manual fallback from a source checkout:
185
187
 
186
188
  ```sh
187
189
  cd apps/macos-agent-status
@@ -324,7 +326,10 @@ Hard limits: max 2 new skills per pass, `[PROTECTED]` (pinned +
324
326
  foreground-authored) skills off-limits. Closes the gap between
325
327
  heuristic harvest and SKILL.md materialization — previously pending
326
328
  candidates accumulated indefinitely waiting for an agent to call
327
- `accept_candidate()` manually.
329
+ `accept_candidate()` manually. The loop is machine-wide single-flight:
330
+ while one reviewer child is running, other foreground servers/ticks report
331
+ `candidate_review_running` instead of spawning another child for the same
332
+ queue.
328
333
 
329
334
  #### 5. Autonomous Curator
330
335
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "threadkeeper"
7
- version = "0.9.0"
7
+ version = "0.9.2"
8
8
  description = "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."
9
9
  requires-python = ">=3.11"
10
10
  authors = [{ name = "thread-keeper contributors" }]
@@ -78,6 +78,15 @@ tk-agent-status = "threadkeeper.agent_status:main"
78
78
  include = ["threadkeeper*"]
79
79
  exclude = ["tests*", "scripts*"]
80
80
 
81
+ [tool.setuptools.package-data]
82
+ threadkeeper = [
83
+ "assets/macos-agent-status/Info.plist",
84
+ "assets/macos-agent-status/README.md",
85
+ "assets/macos-agent-status/ThreadKeeperAgentStatus.swift",
86
+ "assets/macos-agent-status/build.sh",
87
+ "assets/macos-agent-status/install.sh",
88
+ ]
89
+
81
90
  [tool.pytest.ini_options]
82
91
  testpaths = ["tests"]
83
92
  addopts = "-q --strict-markers"
@@ -7,6 +7,7 @@ fork.
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
+ import os
10
11
  import sys
11
12
  import time
12
13
  from pathlib import Path
@@ -205,6 +206,41 @@ def test_run_spawns_when_threshold_met(tmp_path, monkeypatch):
205
206
  assert "Bash" not in allowed
206
207
 
207
208
 
209
+ def test_single_flight_when_reviewer_child_running(tmp_path, monkeypatch):
210
+ """Candidate review consumes one global queue; don't spawn duplicates."""
211
+ pkg = _bootstrap(tmp_path, monkeypatch, min_n="3")
212
+ conn = pkg["db"].get_db()
213
+ for i in range(4):
214
+ _seed_pending(conn, "verbatim", f"candidate {i}", age_s=60 + i)
215
+ conn.execute(
216
+ "INSERT INTO tasks "
217
+ "(id, pid, parent_cid, spawned_cid, cwd, prompt, started_at) "
218
+ "VALUES ('tk_running_review', ?, 'p', 'c', '/x', ?, ?)",
219
+ (
220
+ os.getpid(),
221
+ "You are a CANDIDATE REVIEWER for thread-keeper's extract queue.",
222
+ int(time.time()) - 30,
223
+ ),
224
+ )
225
+ conn.commit()
226
+
227
+ import threadkeeper.tools.spawn as spawn_mod
228
+
229
+ def fail_spawn(**kwargs): # pragma: no cover - should not be called
230
+ raise AssertionError("spawn should not run while reviewer is active")
231
+
232
+ monkeypatch.setattr(spawn_mod, "spawn", fail_spawn)
233
+
234
+ out = pkg["candidate_reviewer"].run_review_pass(force=True)
235
+
236
+ assert out == "candidate_review_running n=1 (single-flight)"
237
+ row = conn.execute(
238
+ "SELECT summary FROM events WHERE kind='candidate_review_pass' "
239
+ "ORDER BY id DESC LIMIT 1"
240
+ ).fetchone()
241
+ assert "candidate_review_running n=1" in row["summary"]
242
+
243
+
208
244
  # ──────────────────────────────────────────────────────────────────────
209
245
  # Daemon lifecycle
210
246
  # ──────────────────────────────────────────────────────────────────────
@@ -0,0 +1,69 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from types import SimpleNamespace
5
+
6
+
7
+ def test_menubar_packaged_assets_are_available(fresh_mp):
8
+ import threadkeeper.menubar_app as menubar_app
9
+
10
+ src = menubar_app._package_source_dir()
11
+
12
+ assert src.name == "macos-agent-status"
13
+ assert (src / "ThreadKeeperAgentStatus.swift").exists()
14
+ assert (src / "Info.plist").exists()
15
+ assert (src / "build.sh").exists()
16
+
17
+
18
+ def test_menubar_source_falls_back_to_packaged_assets(fresh_mp, tmp_path, monkeypatch):
19
+ import threadkeeper.menubar_app as menubar_app
20
+
21
+ monkeypatch.setattr(menubar_app, "_dev_source_dir", lambda: tmp_path / "missing")
22
+
23
+ assert menubar_app._source_dir() == menubar_app._package_source_dir()
24
+
25
+
26
+ def test_install_app_builds_from_task_log_scratch_without_executable_bit(
27
+ fresh_mp,
28
+ tmp_path,
29
+ monkeypatch,
30
+ ):
31
+ import threadkeeper.menubar_app as menubar_app
32
+
33
+ src = tmp_path / "source"
34
+ src.mkdir()
35
+ (src / "ThreadKeeperAgentStatus.swift").write_text("// swift\n", encoding="utf-8")
36
+ (src / "Info.plist").write_text("<plist></plist>\n", encoding="utf-8")
37
+ build = src / "build.sh"
38
+ build.write_text("#!/usr/bin/env bash\n", encoding="utf-8")
39
+ build.chmod(0o644)
40
+
41
+ task_logs = tmp_path / "tasks"
42
+ monkeypatch.setattr(menubar_app, "TASK_LOG_DIR", task_logs)
43
+ monkeypatch.setattr(menubar_app, "_app_is_current", lambda src, app: False)
44
+ calls = []
45
+
46
+ def fake_run(args, timeout=60, cwd=None):
47
+ calls.append((args, timeout, Path(cwd)))
48
+ app_dir = Path(cwd) / "build" / menubar_app.APP_BUNDLE
49
+ bin_dir = app_dir / "Contents" / "MacOS"
50
+ bin_dir.mkdir(parents=True)
51
+ (app_dir / "Contents" / "Info.plist").write_text(
52
+ "<plist></plist>\n",
53
+ encoding="utf-8",
54
+ )
55
+ (bin_dir / menubar_app.APP_NAME).write_text("binary\n", encoding="utf-8")
56
+ return SimpleNamespace(returncode=0, stdout=f"{app_dir}\n")
57
+
58
+ monkeypatch.setattr(menubar_app, "_run", fake_run)
59
+
60
+ installed = tmp_path / "Applications" / menubar_app.APP_BUNDLE
61
+ assert menubar_app._install_app(src, installed) is True
62
+
63
+ assert calls
64
+ assert calls[0][0][0] == "/bin/bash"
65
+ assert calls[0][0][1] == str(task_logs / "menubar-build" / "source" / "build.sh")
66
+ assert calls[0][2] == task_logs / "menubar-build" / "source"
67
+ assert (installed / "Contents" / "Info.plist").exists()
68
+ assert (installed / "Contents" / "MacOS" / menubar_app.APP_NAME).exists()
69
+ assert not (src / "build").exists()
@@ -0,0 +1,25 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
3
+ "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
4
+ <plist version="1.0">
5
+ <dict>
6
+ <key>CFBundleExecutable</key>
7
+ <string>ThreadKeeperAgentStatus</string>
8
+ <key>CFBundleIdentifier</key>
9
+ <string>local.threadkeeper.agent-status</string>
10
+ <key>CFBundleName</key>
11
+ <string>ThreadKeeperAgentStatus</string>
12
+ <key>CFBundlePackageType</key>
13
+ <string>APPL</string>
14
+ <key>CFBundleShortVersionString</key>
15
+ <string>0.1.0</string>
16
+ <key>CFBundleVersion</key>
17
+ <string>1</string>
18
+ <key>LSMinimumSystemVersion</key>
19
+ <string>13.0</string>
20
+ <key>LSUIElement</key>
21
+ <true/>
22
+ <key>NSHighResolutionCapable</key>
23
+ <true/>
24
+ </dict>
25
+ </plist>
@@ -0,0 +1,65 @@
1
+ # ThreadKeeper Agent Status
2
+
3
+ Small macOS menu-bar app for live thread-keeper autonomous learning loop status.
4
+
5
+ It polls `tk-agent-status --json` every 5 seconds and shows:
6
+
7
+ - running/enabled loop count in the menu bar,
8
+ - every autonomous learning loop,
9
+ - a stable role description for what each loop/agent is responsible for,
10
+ - running / idle / ready / off state,
11
+ - active loops first (`running`, then `ready`),
12
+ - last pass summary,
13
+ - backlog count,
14
+ - active spawned-child RSS when a loop has a worker running,
15
+ - macOS notifications for newly completed autonomous child tasks that produced
16
+ a useful result.
17
+
18
+ The first poll primes the seen-result list, so the app does not notify for old
19
+ completed tasks that existed before it started.
20
+
21
+ ## Automatic startup
22
+
23
+ On macOS, `python -m threadkeeper.server` installs and launches this app
24
+ automatically when the MCP server starts. The startup hook is idempotent: it
25
+ rebuilds only when the installed app is missing or older than the source,
26
+ registers the LaunchAgent, and opens the app if it is not already running.
27
+
28
+ Disable automatic startup with:
29
+
30
+ ```sh
31
+ THREADKEEPER_MENUBAR_AUTO_LAUNCH=0
32
+ ```
33
+
34
+ ## Build
35
+
36
+ ```sh
37
+ ./build.sh
38
+ open build/ThreadKeeperAgentStatus.app
39
+ ```
40
+
41
+ ## Install at login
42
+
43
+ ```sh
44
+ ./install.sh
45
+ ```
46
+
47
+ The app is installed to `~/Applications/ThreadKeeperAgentStatus.app` and a
48
+ LaunchAgent is registered at
49
+ `~/Library/LaunchAgents/local.threadkeeper.agent-status.plist`.
50
+
51
+ If `tk-agent-status` is not already installed on PATH, `install.sh` creates a
52
+ small fallback wrapper at `~/.local/bin/tk-agent-status` that runs the local
53
+ repo module through `.venv/bin/python`.
54
+
55
+ ## Command lookup
56
+
57
+ The app looks for `tk-agent-status` in:
58
+
59
+ - `/opt/homebrew/bin/tk-agent-status`
60
+ - `/usr/local/bin/tk-agent-status`
61
+ - `~/.local/bin/tk-agent-status`
62
+ - `PATH` via `/usr/bin/env`
63
+
64
+ Set `THREADKEEPER_AGENT_STATUS_COMMAND` when launching the app if the command
65
+ lives somewhere else.