brainlayer 1.5.2__tar.gz → 1.5.3__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 (203) hide show
  1. {brainlayer-1.5.2 → brainlayer-1.5.3}/PKG-INFO +3 -2
  2. {brainlayer-1.5.2 → brainlayer-1.5.3}/pyproject.toml +3 -2
  3. {brainlayer-1.5.2 → brainlayer-1.5.3}/scripts/hotlane_brainbar_daemon.py +310 -33
  4. {brainlayer-1.5.2 → brainlayer-1.5.3}/scripts/launchd/backup-daily.sh +5 -1
  5. brainlayer-1.5.3/scripts/launchd/com.brainlayer.t3-ingest.plist +53 -0
  6. {brainlayer-1.5.2 → brainlayer-1.5.3}/scripts/launchd/com.brainlayer.wal-checkpoint.plist +1 -2
  7. {brainlayer-1.5.2 → brainlayer-1.5.3}/scripts/launchd/install.sh +7 -2
  8. {brainlayer-1.5.2 → brainlayer-1.5.3}/scripts/launchd/throughput-watchdog.py +225 -39
  9. {brainlayer-1.5.2 → brainlayer-1.5.3}/server.json +2 -2
  10. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/__init__.py +1 -1
  11. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/agent_provenance.py +13 -0
  12. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/backup_daily.py +286 -54
  13. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/brainbar_hybrid_helper.py +1 -1
  14. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/cli/__init__.py +85 -11
  15. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/deploy_drift.py +81 -24
  16. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/doctor.py +37 -8
  17. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/drain.py +161 -51
  18. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/health_check.py +40 -21
  19. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/index_new.py +31 -25
  20. brainlayer-1.5.3/src/brainlayer/ingest/__init__.py +27 -0
  21. brainlayer-1.5.3/src/brainlayer/ingest/t3.py +404 -0
  22. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/launchd_primitive.py +31 -6
  23. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/maintenance.py +108 -0
  24. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/mcp/__init__.py +153 -62
  25. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/mcp/_shared.py +1 -1
  26. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/mcp/palette.py +1 -1
  27. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/mcp/store_handler.py +9 -0
  28. brainlayer-1.5.3/src/brainlayer/paths.py +55 -0
  29. brainlayer-1.5.3/src/brainlayer/pause.py +46 -0
  30. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/queue_io.py +6 -2
  31. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/runtime_store.py +2 -0
  32. brainlayer-1.5.3/src/brainlayer/setup.py +278 -0
  33. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/store.py +14 -0
  34. brainlayer-1.5.3/src/brainlayer/t3_provenance.py +97 -0
  35. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/vector_store.py +46 -12
  36. brainlayer-1.5.3/src/brainlayer/wal_checkpoint.py +175 -0
  37. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/watcher.py +504 -127
  38. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/watcher_bridge.py +127 -14
  39. brainlayer-1.5.2/src/brainlayer/ingest/__init__.py +0 -1
  40. brainlayer-1.5.2/src/brainlayer/paths.py +0 -28
  41. brainlayer-1.5.2/src/brainlayer/setup.py +0 -114
  42. brainlayer-1.5.2/src/brainlayer/wal_checkpoint.py +0 -72
  43. {brainlayer-1.5.2 → brainlayer-1.5.3}/.gitignore +0 -0
  44. {brainlayer-1.5.2 → brainlayer-1.5.3}/LICENSE +0 -0
  45. {brainlayer-1.5.2 → brainlayer-1.5.3}/README.md +0 -0
  46. {brainlayer-1.5.2 → brainlayer-1.5.3}/scripts/launchd/brainlayer-env-run.sh +0 -0
  47. {brainlayer-1.5.2 → brainlayer-1.5.3}/scripts/launchd/brainlayer.env.example +0 -0
  48. {brainlayer-1.5.2 → brainlayer-1.5.3}/scripts/launchd/com.brainlayer.backup-daily.plist +0 -0
  49. {brainlayer-1.5.2 → brainlayer-1.5.3}/scripts/launchd/com.brainlayer.decay.plist +0 -0
  50. {brainlayer-1.5.2 → brainlayer-1.5.3}/scripts/launchd/com.brainlayer.drain.plist +0 -0
  51. {brainlayer-1.5.2 → brainlayer-1.5.3}/scripts/launchd/com.brainlayer.enrichment.plist +0 -0
  52. {brainlayer-1.5.2 → brainlayer-1.5.3}/scripts/launchd/com.brainlayer.health-check.plist +0 -0
  53. {brainlayer-1.5.2 → brainlayer-1.5.3}/scripts/launchd/com.brainlayer.hotlane-brainbar.plist +0 -0
  54. {brainlayer-1.5.2 → brainlayer-1.5.3}/scripts/launchd/com.brainlayer.index.plist +0 -0
  55. {brainlayer-1.5.2 → brainlayer-1.5.3}/scripts/launchd/com.brainlayer.jsonl-backup.plist +0 -0
  56. {brainlayer-1.5.2 → brainlayer-1.5.3}/scripts/launchd/com.brainlayer.maintenance-nightly.plist +0 -0
  57. {brainlayer-1.5.2 → brainlayer-1.5.3}/scripts/launchd/com.brainlayer.maintenance-weekly.plist +0 -0
  58. {brainlayer-1.5.2 → brainlayer-1.5.3}/scripts/launchd/com.brainlayer.p0-counter.plist +0 -0
  59. {brainlayer-1.5.2 → brainlayer-1.5.3}/scripts/launchd/com.brainlayer.repair-fts.plist +0 -0
  60. {brainlayer-1.5.2 → brainlayer-1.5.3}/scripts/launchd/com.brainlayer.throughput-watchdog.plist +0 -0
  61. {brainlayer-1.5.2 → brainlayer-1.5.3}/scripts/launchd/com.brainlayer.tier0-watchdog.plist +0 -0
  62. {brainlayer-1.5.2 → brainlayer-1.5.3}/scripts/launchd/com.brainlayer.watch.plist +0 -0
  63. {brainlayer-1.5.2 → brainlayer-1.5.3}/scripts/launchd/jsonl-backup.sh +0 -0
  64. {brainlayer-1.5.2 → brainlayer-1.5.3}/scripts/tier0-watchdog.sh +0 -0
  65. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/_helpers.py +0 -0
  66. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/agent_profiles.py +0 -0
  67. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/alarm.py +0 -0
  68. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/bitemporal.py +0 -0
  69. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/calibrate.py +0 -0
  70. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/chunk_origin.py +0 -0
  71. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/chunk_origin_backfill.py +0 -0
  72. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/classify.py +0 -0
  73. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/claude_paths.py +0 -0
  74. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/cli/wizard.py +0 -0
  75. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/cli_new.py +0 -0
  76. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/cloud_backfill.py +0 -0
  77. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/clustering.py +0 -0
  78. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/config.py +0 -0
  79. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/content_class.py +0 -0
  80. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/correction_judge.py +0 -0
  81. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/data/agents_registry.yaml +0 -0
  82. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/data/sandbox_seeds/skill-eval-baseline.json +0 -0
  83. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/db_shrink.py +0 -0
  84. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/decay.py +0 -0
  85. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/decay_backfill.py +0 -0
  86. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/decay_job.py +0 -0
  87. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/dedupe.py +0 -0
  88. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/drain_liveness.py +0 -0
  89. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/embeddings.py +0 -0
  90. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/engine.py +0 -0
  91. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/enrichment_controller.py +0 -0
  92. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/eval/__init__.py +0 -0
  93. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/eval/abcde_enrich_runner.py +0 -0
  94. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/eval/abcde_variants.py +0 -0
  95. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/eval/abcde_variants.yaml +0 -0
  96. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/eval/benchmark.py +0 -0
  97. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/eval/enrichment_gold.py +0 -0
  98. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/eval/enrichment_graders.py +0 -0
  99. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/eval/enrichment_judge.py +0 -0
  100. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/eval/enrichment_llm_judge.py +0 -0
  101. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/eval/enrichment_quality_benchmark.py +0 -0
  102. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/eval/experiment_store.py +0 -0
  103. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/eval/fixtures/README.md +0 -0
  104. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/eval/labeling/enrichment-labeler.html +0 -0
  105. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/eval/phoenix_gate/__init__.py +0 -0
  106. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/eval/phoenix_gate/__main__.py +0 -0
  107. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/eval/phoenix_gate/baseline_store.py +0 -0
  108. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/eval/phoenix_gate/cli.py +0 -0
  109. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/eval/phoenix_gate/models.py +0 -0
  110. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/eval/phoenix_gate/phoenix_client.py +0 -0
  111. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/eval/phoenix_gate/regression_gate.py +0 -0
  112. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/eval/phoenix_gate/triggers.py +0 -0
  113. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/fallback_replay.py +0 -0
  114. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/git_learning.py +0 -0
  115. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/hooks/__init__.py +0 -0
  116. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/hooks/indexer.py +0 -0
  117. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/ingest/codex.py +0 -0
  118. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/ingest_denylist.py +0 -0
  119. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/ingest_guard.py +0 -0
  120. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/isolation_proof.py +0 -0
  121. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/jsonl_backup.py +0 -0
  122. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/kg/__init__.py +0 -0
  123. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/kg_cleanup.py +0 -0
  124. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/kg_judge.py +0 -0
  125. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/kg_promotion.py +0 -0
  126. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/kg_repo.py +0 -0
  127. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/kg_review_session.py +0 -0
  128. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/kg_session_finish.py +0 -0
  129. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/kg_session_harvest.py +0 -0
  130. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/lexical_defense.py +0 -0
  131. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/lexical_defense_dictionary.json +0 -0
  132. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/mcp/_format.py +0 -0
  133. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/mcp/enrich_handler.py +0 -0
  134. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/mcp/entity_handler.py +0 -0
  135. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/mcp/search_handler.py +0 -0
  136. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/mcp/tags_handler.py +0 -0
  137. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/mcp_stdio_bridge.py +0 -0
  138. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/memory_types.py +0 -0
  139. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/migrate.py +0 -0
  140. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/p0_longitudinal_count.py +0 -0
  141. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/parent_death.py +0 -0
  142. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/phonetic.py +0 -0
  143. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/pipeline/__init__.py +0 -0
  144. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/pipeline/agent_enrichment.py +0 -0
  145. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/pipeline/analyze_communication.py +0 -0
  146. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/pipeline/batch_extraction.py +0 -0
  147. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/pipeline/brain_graph.py +0 -0
  148. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/pipeline/chat_tags.py +0 -0
  149. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/pipeline/chunk.py +0 -0
  150. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/pipeline/classify.py +0 -0
  151. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/pipeline/cluster_sampling.py +0 -0
  152. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/pipeline/code_intelligence.py +0 -0
  153. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/pipeline/correction_detection.py +0 -0
  154. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/pipeline/correction_mining.py +0 -0
  155. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/pipeline/digest.py +0 -0
  156. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/pipeline/enrichment.py +0 -0
  157. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/pipeline/enrichment_tiers.py +0 -0
  158. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/pipeline/entity_extraction.py +0 -0
  159. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/pipeline/entity_resolution.py +0 -0
  160. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/pipeline/extract.py +0 -0
  161. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/pipeline/extract_claude_desktop.py +0 -0
  162. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/pipeline/extract_corrections.py +0 -0
  163. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/pipeline/extract_markdown.py +0 -0
  164. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/pipeline/extract_whatsapp.py +0 -0
  165. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/pipeline/frustration_mining.py +0 -0
  166. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/pipeline/git_overlay.py +0 -0
  167. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/pipeline/kg_extraction.py +0 -0
  168. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/pipeline/kg_extraction_groq.py +0 -0
  169. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/pipeline/longitudinal_analyzer.py +0 -0
  170. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/pipeline/obsidian_export.py +0 -0
  171. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/pipeline/operation_grouping.py +0 -0
  172. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/pipeline/plan_linking.py +0 -0
  173. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/pipeline/rate_limiter.py +0 -0
  174. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/pipeline/sanitize.py +0 -0
  175. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/pipeline/secret_scrub.py +0 -0
  176. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/pipeline/semantic_style.py +0 -0
  177. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/pipeline/sentiment.py +0 -0
  178. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/pipeline/session_enrichment.py +0 -0
  179. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/pipeline/style_embed.py +0 -0
  180. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/pipeline/style_index.py +0 -0
  181. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/pipeline/tag_entity_promotion.py +0 -0
  182. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/pipeline/temporal_chains.py +0 -0
  183. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/pipeline/time_batcher.py +0 -0
  184. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/pipeline/unified_timeline.py +0 -0
  185. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/pipeline/write_queue.py +0 -0
  186. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/provenance.py +0 -0
  187. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/provenance_autosupersede.py +0 -0
  188. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/provenance_integration.py +0 -0
  189. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/queue_merge.py +0 -0
  190. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/reembed_backfill.py +0 -0
  191. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/sandbox_db.py +0 -0
  192. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/scoping.py +0 -0
  193. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/search_fanout.py +0 -0
  194. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/search_profile.py +0 -0
  195. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/search_repo.py +0 -0
  196. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/session_repo.py +0 -0
  197. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/storage.py +0 -0
  198. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/system_prompt_guard.py +0 -0
  199. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/tag_normalization.py +0 -0
  200. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/taxonomy.json +0 -0
  201. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/telemetry.py +0 -0
  202. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/types.py +0 -0
  203. {brainlayer-1.5.2 → brainlayer-1.5.3}/src/brainlayer/writer_telemetry.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: brainlayer
3
- Version: 1.5.2
3
+ Version: 1.5.3
4
4
  Summary: Persistent memory MCP server for AI agents — 13 tools, semantic search, knowledge graph, on-device SQLite
5
5
  Project-URL: Homepage, https://brainlayer.etanheyman.com
6
6
  Project-URL: Repository, https://github.com/EtanHey/brainlayer
@@ -23,7 +23,8 @@ Requires-Dist: abydos>=0.5.0
23
23
  Requires-Dist: apsw>=3.45.0
24
24
  Requires-Dist: google-api-python-client>=2.0.0
25
25
  Requires-Dist: google-auth>=2.0.0
26
- Requires-Dist: mcp>=1.0.0
26
+ Requires-Dist: jsonschema>=4.20.0
27
+ Requires-Dist: mcp<3.0.0,>=2.0.0
27
28
  Requires-Dist: numpy<3.0,>=1.22
28
29
  Requires-Dist: orjson>=3.9.0
29
30
  Requires-Dist: pydantic>=2.0.0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "brainlayer"
3
- version = "1.5.2"
3
+ version = "1.5.3"
4
4
  description = "Persistent memory MCP server for AI agents — 13 tools, semantic search, knowledge graph, on-device SQLite"
5
5
  license = {text = "Apache-2.0"}
6
6
  readme = "README.md"
@@ -16,7 +16,8 @@ dependencies = [
16
16
  "sentence-transformers>=2.2.0",
17
17
 
18
18
  # MCP server
19
- "mcp>=1.0.0",
19
+ "mcp>=2.0.0,<3.0.0",
20
+ "jsonschema>=4.20.0", # MCP v2 low-level handlers require explicit schema validation
20
21
 
21
22
  # CLI
22
23
  "typer>=0.9.0",
@@ -15,6 +15,7 @@ import logging
15
15
  import os
16
16
  import signal
17
17
  import time
18
+ from collections import OrderedDict
18
19
  from pathlib import Path
19
20
  from typing import Callable, NamedTuple
20
21
 
@@ -41,8 +42,88 @@ MAX_BACKLOG_BATCH = 16
41
42
  DEFAULT_HOTLANE_WRITE_BUSY_TIMEOUT_MS = 1000
42
43
  MAX_APSW_BUSY_TIMEOUT_MS = 2_147_483_647
43
44
  VECTOR_WRITE_YIELD_SECONDS = 0.005
45
+ HOT_CANDIDATE_SCAN_LIMIT = 256
46
+ HOT_CANDIDATE_HEAD_LIMIT = HOT_CANDIDATE_SCAN_LIMIT // 2
47
+ MAX_HOT_CANDIDATE_RETRIES = 256
44
48
  _sleep = time.sleep
45
49
 
50
+ HOT_CANDIDATE_SCAN_SQL = """
51
+ SELECT
52
+ c.id,
53
+ c.content,
54
+ c.source_file,
55
+ c.source,
56
+ c.archived_at,
57
+ c.superseded_by,
58
+ c.aggregated_into,
59
+ COALESCE(c.archived, 0),
60
+ COALESCE(c.status, 'active'),
61
+ r.id
62
+ FROM chunks c
63
+ LEFT JOIN chunk_vectors_rowids r ON r.id = c.id
64
+ ORDER BY c.created_at DESC
65
+ LIMIT ?
66
+ """
67
+
68
+ HOT_CANDIDATE_ROWID_SCAN_SQL = """
69
+ SELECT
70
+ c.rowid,
71
+ c.id,
72
+ c.content,
73
+ c.source_file,
74
+ c.source,
75
+ c.archived_at,
76
+ c.superseded_by,
77
+ c.aggregated_into,
78
+ COALESCE(c.archived, 0),
79
+ COALESCE(c.status, 'active'),
80
+ r.id
81
+ FROM chunks c
82
+ LEFT JOIN chunk_vectors_rowids r ON r.id = c.id
83
+ ORDER BY c.rowid DESC
84
+ LIMIT ?
85
+ """
86
+
87
+ HOT_CANDIDATE_ROWID_PAGE_SQL = """
88
+ SELECT
89
+ c.rowid,
90
+ c.id,
91
+ c.content,
92
+ c.source_file,
93
+ c.source,
94
+ c.archived_at,
95
+ c.superseded_by,
96
+ c.aggregated_into,
97
+ COALESCE(c.archived, 0),
98
+ COALESCE(c.status, 'active'),
99
+ r.id
100
+ FROM chunks c
101
+ LEFT JOIN chunk_vectors_rowids r ON r.id = c.id
102
+ WHERE c.rowid < ?
103
+ ORDER BY c.rowid DESC
104
+ LIMIT ?
105
+ """
106
+
107
+ HOT_CANDIDATE_ROWID_FORWARD_SQL = """
108
+ SELECT
109
+ c.rowid,
110
+ c.id,
111
+ c.content,
112
+ c.source_file,
113
+ c.source,
114
+ c.archived_at,
115
+ c.superseded_by,
116
+ c.aggregated_into,
117
+ COALESCE(c.archived, 0),
118
+ COALESCE(c.status, 'active'),
119
+ r.id
120
+ FROM chunks c
121
+ LEFT JOIN chunk_vectors_rowids r ON r.id = c.id
122
+ WHERE c.rowid > ?
123
+ ORDER BY c.rowid ASC
124
+ LIMIT ?
125
+ """
126
+
46
127
 
47
128
  class CycleResult(NamedTuple):
48
129
  embedded: int = 0
@@ -64,6 +145,183 @@ class EmbeddedVector(NamedTuple):
64
145
  embedding: list[float]
65
146
 
66
147
 
148
+ def _candidates_from_scanned_rows(
149
+ rows: list[tuple], *, limit: int, exclude_ids: set[str] | None = None
150
+ ) -> tuple[list[EmbedCandidate], int | None, int | None, bool]:
151
+ if limit <= 0:
152
+ return [], None, None, False
153
+ candidates: list[EmbedCandidate] = []
154
+ exclude_ids = exclude_ids or set()
155
+ first_candidate_rowid: int | None = None
156
+ last_inspected_rowid: int | None = None
157
+ for index, row in enumerate(rows):
158
+ (
159
+ rowid,
160
+ chunk_id,
161
+ content,
162
+ source_file,
163
+ source,
164
+ archived_at,
165
+ superseded_by,
166
+ aggregated_into,
167
+ archived,
168
+ status,
169
+ vector_id,
170
+ ) = row
171
+ last_inspected_rowid = int(rowid)
172
+ eligible = (
173
+ vector_id is None
174
+ and source_file == "brainbar-store"
175
+ and source == "mcp"
176
+ and content
177
+ and archived_at is None
178
+ and superseded_by is None
179
+ and aggregated_into is None
180
+ and not archived
181
+ and status == "active"
182
+ )
183
+ if eligible:
184
+ if first_candidate_rowid is None:
185
+ first_candidate_rowid = int(rowid)
186
+ if str(chunk_id) in exclude_ids:
187
+ continue
188
+ candidates.append(EmbedCandidate(str(chunk_id), str(content)))
189
+ if len(candidates) >= limit:
190
+ return candidates, first_candidate_rowid, last_inspected_rowid, index == len(rows) - 1
191
+ return candidates, first_candidate_rowid, last_inspected_rowid, True
192
+
193
+
194
+ class HotCandidateScanner:
195
+ """Scan bounded rowid lanes while retaining returned candidates for retry."""
196
+
197
+ def __init__(self) -> None:
198
+ self._before_rowid: int | None = None
199
+ self._newest_seen_rowid: int | None = None
200
+ self._retries: OrderedDict[str, EmbedCandidate] = OrderedDict()
201
+ self._retry_turn = True
202
+ self._retry_buffer_full_logged = False
203
+
204
+ def _refresh_retries(self, cursor: apsw.Cursor) -> None:
205
+ if not self._retries:
206
+ return
207
+ placeholders = ",".join("?" for _ in self._retries)
208
+ rows = cursor.execute(
209
+ f"""
210
+ SELECT c.id, c.content
211
+ FROM chunks c
212
+ LEFT JOIN chunk_vectors_rowids r ON r.id = c.id
213
+ WHERE c.id IN ({placeholders})
214
+ AND r.id IS NULL
215
+ AND c.source_file = 'brainbar-store'
216
+ AND c.source = 'mcp'
217
+ AND c.content IS NOT NULL
218
+ AND c.content != ''
219
+ AND c.archived_at IS NULL
220
+ AND c.superseded_by IS NULL
221
+ AND c.aggregated_into IS NULL
222
+ AND COALESCE(c.archived, 0) = 0
223
+ AND COALESCE(c.status, 'active') = 'active'
224
+ """,
225
+ tuple(self._retries),
226
+ )
227
+ active = {str(chunk_id): EmbedCandidate(str(chunk_id), str(content)) for chunk_id, content in rows}
228
+ for chunk_id in list(self._retries):
229
+ if chunk_id not in active:
230
+ del self._retries[chunk_id]
231
+ else:
232
+ self._retries[chunk_id] = active[chunk_id]
233
+ if len(self._retries) < MAX_HOT_CANDIDATE_RETRIES:
234
+ self._retry_buffer_full_logged = False
235
+
236
+ def _take_retries(self, *, limit: int) -> list[EmbedCandidate]:
237
+ selected: list[EmbedCandidate] = []
238
+ for _ in range(min(limit, len(self._retries))):
239
+ chunk_id, candidate = self._retries.popitem(last=False)
240
+ self._retries[chunk_id] = candidate
241
+ selected.append(candidate)
242
+ return selected
243
+
244
+ def _retain(self, candidates: list[EmbedCandidate]) -> None:
245
+ for candidate in candidates:
246
+ if candidate.chunk_id in self._retries:
247
+ continue
248
+ if len(self._retries) >= MAX_HOT_CANDIDATE_RETRIES:
249
+ if not self._retry_buffer_full_logged:
250
+ LOGGER.warning("hot candidate retry buffer full; pausing new retry retention")
251
+ self._retry_buffer_full_logged = True
252
+ break
253
+ self._retries[candidate.chunk_id] = candidate
254
+
255
+ def __call__(self, store: VectorStore, *, limit: int) -> list[EmbedCandidate]:
256
+ cursor = store.conn.cursor()
257
+ self._refresh_retries(cursor)
258
+ if limit <= 0:
259
+ return []
260
+ if limit == 1 and self._retries:
261
+ retry_limit = 1 if self._retry_turn else 0
262
+ self._retry_turn = not self._retry_turn
263
+ else:
264
+ retry_limit = min(len(self._retries), max(1, limit // 2))
265
+ candidates = self._take_retries(limit=retry_limit)
266
+ retention_slots = MAX_HOT_CANDIDATE_RETRIES - len(self._retries)
267
+ scan_limit = min(limit - len(candidates), retention_slots)
268
+ if scan_limit <= 0:
269
+ return candidates
270
+ retry_ids = set(self._retries)
271
+ head_rows = list(cursor.execute(HOT_CANDIDATE_ROWID_SCAN_SQL, (HOT_CANDIDATE_HEAD_LIMIT,)))
272
+ head_candidates, _, last_inspected_rowid, _ = _candidates_from_scanned_rows(
273
+ head_rows,
274
+ limit=scan_limit,
275
+ exclude_ids=retry_ids,
276
+ )
277
+ candidates.extend(head_candidates)
278
+ retention_slots -= len(head_candidates)
279
+ if not head_rows:
280
+ self._retain(candidates)
281
+ return candidates
282
+ if self._newest_seen_rowid is None:
283
+ self._newest_seen_rowid = last_inspected_rowid
284
+ if self._before_rowid is None and last_inspected_rowid is not None:
285
+ self._before_rowid = last_inspected_rowid
286
+ if len(candidates) >= limit or retention_slots <= 0:
287
+ self._retain(candidates)
288
+ return candidates
289
+
290
+ page_limit = HOT_CANDIDATE_SCAN_LIMIT - HOT_CANDIDATE_HEAD_LIMIT
291
+ forward_rows = list(
292
+ cursor.execute(
293
+ HOT_CANDIDATE_ROWID_FORWARD_SQL,
294
+ (self._newest_seen_rowid, page_limit),
295
+ )
296
+ )
297
+ if forward_rows:
298
+ forward_candidates, _, last_inspected_rowid, _ = _candidates_from_scanned_rows(
299
+ forward_rows,
300
+ limit=min(limit - len(candidates), retention_slots),
301
+ exclude_ids=retry_ids | {candidate.chunk_id for candidate in candidates},
302
+ )
303
+ candidates.extend(forward_candidates)
304
+ retention_slots -= len(forward_candidates)
305
+ self._newest_seen_rowid = last_inspected_rowid
306
+ if len(candidates) >= limit or retention_slots <= 0 or self._before_rowid is None:
307
+ self._retain(candidates)
308
+ return candidates[:limit]
309
+
310
+ page_rows = list(cursor.execute(HOT_CANDIDATE_ROWID_PAGE_SQL, (self._before_rowid, page_limit)))
311
+ if page_rows:
312
+ page_candidates, _, last_inspected_rowid, fully_scanned = _candidates_from_scanned_rows(
313
+ page_rows,
314
+ limit=min(limit - len(candidates), retention_slots),
315
+ exclude_ids=retry_ids | {candidate.chunk_id for candidate in candidates},
316
+ )
317
+ candidates.extend(page_candidates)
318
+ self._before_rowid = last_inspected_rowid
319
+ if fully_scanned and len(page_rows) < page_limit:
320
+ self._before_rowid = None
321
+ self._retain(candidates)
322
+ return candidates[:limit]
323
+
324
+
67
325
  def _stop(_signum: int, _frame: object) -> None:
68
326
  global STOP
69
327
  STOP = True
@@ -95,26 +353,38 @@ def _candidate_chunk_ids(store: VectorStore, *, limit: int) -> list[str]:
95
353
 
96
354
  def _candidate_chunk_rows(store: VectorStore, *, limit: int) -> list[EmbedCandidate]:
97
355
  rows = store.conn.cursor().execute(
98
- """
99
- SELECT c.id, c.content
100
- FROM chunks c
101
- LEFT JOIN chunk_vectors_rowids r ON r.id = c.id
102
- WHERE r.id IS NULL
103
- AND c.source_file = 'brainbar-store'
104
- AND c.source = 'mcp'
105
- AND c.content IS NOT NULL
106
- AND c.content != ''
107
- AND c.archived_at IS NULL
108
- AND c.superseded_by IS NULL
109
- AND c.aggregated_into IS NULL
110
- AND COALESCE(c.archived, 0) = 0
111
- AND COALESCE(c.status, 'active') = 'active'
112
- ORDER BY c.created_at DESC
113
- LIMIT ?
114
- """,
115
- (limit,),
356
+ HOT_CANDIDATE_SCAN_SQL,
357
+ (HOT_CANDIDATE_SCAN_LIMIT,),
116
358
  )
117
- return [EmbedCandidate(str(row[0]), str(row[1])) for row in rows]
359
+ candidates: list[EmbedCandidate] = []
360
+ for row in rows:
361
+ (
362
+ chunk_id,
363
+ content,
364
+ source_file,
365
+ source,
366
+ archived_at,
367
+ superseded_by,
368
+ aggregated_into,
369
+ archived,
370
+ status,
371
+ vector_id,
372
+ ) = row
373
+ if (
374
+ vector_id is None
375
+ and source_file == "brainbar-store"
376
+ and source == "mcp"
377
+ and content
378
+ and archived_at is None
379
+ and superseded_by is None
380
+ and aggregated_into is None
381
+ and not archived
382
+ and status == "active"
383
+ ):
384
+ candidates.append(EmbedCandidate(str(chunk_id), str(content)))
385
+ if len(candidates) >= limit:
386
+ break
387
+ return candidates
118
388
 
119
389
 
120
390
  def _pending_chunk_rows(store: VectorStore, *, limit: int) -> list[EmbedCandidate]:
@@ -419,9 +689,13 @@ def _queue_depth(queue_dir: Path) -> int:
419
689
  return 0
420
690
 
421
691
 
692
+ def _is_enrichment_queue_file(path: Path) -> bool:
693
+ return path.name.startswith("enrichment-") or path.name.startswith("queue-enrichment")
694
+
695
+
422
696
  def _high_priority_queue_depth(queue_dir: Path) -> int:
423
697
  try:
424
- return sum(1 for path in queue_dir.glob("*.jsonl") if not path.name.startswith("enrichment-"))
698
+ return sum(1 for path in queue_dir.glob("*.jsonl") if not _is_enrichment_queue_file(path))
425
699
  except OSError:
426
700
  return 0
427
701
 
@@ -505,6 +779,8 @@ def run(
505
779
  last_backlog = time_fn() - backlog_interval
506
780
  last_enrich = 0.0
507
781
  enrich_disabled = False
782
+ queue_backpressure_active = False
783
+ hot_candidate_scanner = HotCandidateScanner()
508
784
  cycles = 0
509
785
  backlog_batch = min(max(backlog_batch, 0), MAX_BACKLOG_BATCH)
510
786
  LOGGER.info("hotlane adapter started db=%s", db_path)
@@ -515,23 +791,23 @@ def run(
515
791
  now = time_fn()
516
792
  queue_has_backlog = queue_depth_fn(queue_dir) > 0
517
793
  queue_has_high_priority_backlog = queue_has_backlog and high_priority_queue_depth_fn(queue_dir) > 0
518
- if queue_has_backlog:
519
- if queue_has_high_priority_backlog:
794
+ if queue_has_high_priority_backlog:
795
+ if not queue_backpressure_active:
520
796
  LOGGER.info("durable high-priority queue has backlog; yielding all hotlane writer work")
521
- else:
522
- LOGGER.info("durable queue has backlog; yielding all hotlane writer work")
797
+ queue_backpressure_active = True
523
798
  cycles += 1
524
799
  sleep_fn(interval)
525
800
  continue
526
- else:
527
- cycle_backlog_batch = (
528
- backlog_batch if backlog_batch > 0 and now - last_backlog >= backlog_interval else 0
529
- )
530
- cycle_enrich_limit = (
531
- enrich_limit
532
- if not enrich_disabled and enrich_limit > 0 and now - last_enrich >= enrich_interval
533
- else 0
534
- )
801
+ queue_backpressure_active = False
802
+ cycle_backlog_batch = backlog_batch if backlog_batch > 0 and now - last_backlog >= backlog_interval else 0
803
+ cycle_enrich_limit = (
804
+ enrich_limit
805
+ if not queue_has_backlog
806
+ and not enrich_disabled
807
+ and enrich_limit > 0
808
+ and now - last_enrich >= enrich_interval
809
+ else 0
810
+ )
535
811
  if cycle_backlog_batch > 0:
536
812
  last_backlog = now
537
813
  if cycle_enrich_limit > 0:
@@ -546,6 +822,7 @@ def run(
546
822
  embed_batch_fn=embed_batch_fn,
547
823
  enrich_limit=cycle_enrich_limit,
548
824
  enrich_since_hours=enrich_since_hours,
825
+ candidate_rows_fn=hot_candidate_scanner,
549
826
  )
550
827
  else:
551
828
  store = vector_store_cls(db_path)
@@ -3,8 +3,12 @@ set -euo pipefail
3
3
 
4
4
  export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:$HOME/.local/bin"
5
5
  export PYTHONUNBUFFERED=1
6
- : "${BRAINLAYER_BACKUP_TIMEOUT_SECONDS:=300}"
6
+ : "${BRAINLAYER_BACKUP_CLIENT_TIMEOUT_SECONDS:=0}"
7
+ export BRAINLAYER_BACKUP_CLIENT_TIMEOUT_SECONDS
8
+ : "${BRAINLAYER_BACKUP_TIMEOUT_SECONDS:=7200}"
7
9
  export BRAINLAYER_BACKUP_TIMEOUT_SECONDS
10
+ : "${BRAINLAYER_BACKUP_ATTEMPT_MAX_AGE_SECONDS:=86400}"
11
+ export BRAINLAYER_BACKUP_ATTEMPT_MAX_AGE_SECONDS
8
12
  : "${BRAINLAYER_BACKUP_LOG_PROVENANCE:=real}"
9
13
  export BRAINLAYER_BACKUP_LOG_PROVENANCE
10
14
  BRAINLAYER_DIR="${BRAINLAYER_DIR:-__BRAINLAYER_DIR_VALUE__}"
@@ -0,0 +1,53 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>Label</key>
6
+ <string>com.brainlayer.t3-ingest</string>
7
+ <key>ProgramArguments</key>
8
+ <array>
9
+ <string>__BRAINLAYER_ENV_RUN__</string>
10
+ <string>__BRAINLAYER_BIN__</string>
11
+ <string>ingest-t3</string>
12
+ </array>
13
+ <key>StartCalendarInterval</key>
14
+ <dict>
15
+ <key>Hour</key>
16
+ <integer>3</integer>
17
+ <key>Minute</key>
18
+ <integer>45</integer>
19
+ </dict>
20
+ <key>RunAtLoad</key>
21
+ <true/>
22
+ <key>StandardOutPath</key>
23
+ <string>__HOME__/Library/Logs/brainlayer/t3-ingest.out.log</string>
24
+ <key>StandardErrorPath</key>
25
+ <string>__HOME__/Library/Logs/brainlayer/t3-ingest.err.log</string>
26
+ <key>EnvironmentVariables</key>
27
+ <dict>
28
+ <key>PATH</key>
29
+ <string>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:__HOME__/.local/bin</string>
30
+ <key>PYTHONUNBUFFERED</key>
31
+ <string>1</string>
32
+ <key>BRAINLAYER_REPO_ROOT</key>
33
+ <string>__BRAINLAYER_DIR__</string>
34
+ <key>BRAINLAYER_ENV_FILE</key>
35
+ <string>__BRAINLAYER_ENV_FILE__</string>
36
+ <key>BRAINLAYER_LAUNCHD_SERVICE</key>
37
+ <string>t3-ingest</string>
38
+ </dict>
39
+ <key>Nice</key>
40
+ <integer>10</integer>
41
+ <key>ProcessType</key>
42
+ <string>Background</string>
43
+ <key>ExitTimeOut</key>
44
+ <integer>1800</integer>
45
+ <key>LowPriorityIO</key>
46
+ <true/>
47
+ <key>SoftResourceLimits</key>
48
+ <dict>
49
+ <key>NumberOfFiles</key>
50
+ <integer>4096</integer>
51
+ </dict>
52
+ </dict>
53
+ </plist>
@@ -11,11 +11,10 @@
11
11
  <string>wal-checkpoint</string>
12
12
  <string>--mode</string>
13
13
  <string>TRUNCATE</string>
14
+ <string>--retry-busy</string>
14
15
  </array>
15
16
  <key>StartCalendarInterval</key>
16
17
  <dict>
17
- <key>Weekday</key>
18
- <integer>0</integer>
19
18
  <key>Hour</key>
20
19
  <integer>4</integer>
21
20
  <key>Minute</key>
@@ -20,6 +20,7 @@
20
20
  # ./scripts/launchd/install.sh throughput-watchdog # Install watcher throughput watchdog only
21
21
  # ./scripts/launchd/install.sh hotlane # Install BrainBar hotlane embed/enrich daemon only
22
22
  # ./scripts/launchd/install.sh p0-counter # Install daily P0 longitudinal counter only
23
+ # ./scripts/launchd/install.sh t3-ingest # Install T3 thread ingestion only
23
24
  # ./scripts/launchd/install.sh remove # Unload and remove all
24
25
  set -euo pipefail
25
26
 
@@ -661,13 +662,16 @@ case "${1:-all}" in
661
662
  p0-counter)
662
663
  install_plist p0-counter
663
664
  ;;
665
+ t3-ingest)
666
+ install_plist t3-ingest
667
+ ;;
664
668
  all)
665
669
  install_env_runner
666
670
  verify_config_file
667
671
  verify_gemini_env_file
668
672
  failures=0
669
673
  enrichment_ok=0
670
- if ! install_many index drain watch hotlane-brainbar; then
674
+ if ! install_many index t3-ingest drain watch hotlane-brainbar; then
671
675
  failures=1
672
676
  fi
673
677
  if install_plist enrichment; then
@@ -707,6 +711,7 @@ case "${1:-all}" in
707
711
  ;;
708
712
  remove)
709
713
  remove_plist index
714
+ remove_plist t3-ingest 2>/dev/null || true
710
715
  remove_plist enrich 2>/dev/null || true
711
716
  remove_plist enrichment 2>/dev/null || true
712
717
  remove_plist watch 2>/dev/null || true
@@ -730,7 +735,7 @@ case "${1:-all}" in
730
735
  rm -f "$HOTLANE_BRAINBAR_DST"
731
736
  ;;
732
737
  *)
733
- echo "Usage: $0 [index|watch|enrich|enrichment|decay|drain|hotlane|repair-fts|load [name]|unload [name]|checkpoint|backup|jsonl-backup|maintenance|maintenance-nightly|maintenance-weekly|health-check|tier0-watchdog|throughput-watchdog|p0-counter|all|remove]"
738
+ echo "Usage: $0 [index|t3-ingest|watch|enrich|enrichment|decay|drain|hotlane|repair-fts|load [name]|unload [name]|checkpoint|backup|jsonl-backup|maintenance|maintenance-nightly|maintenance-weekly|health-check|tier0-watchdog|throughput-watchdog|p0-counter|all|remove]"
734
739
  exit 1
735
740
  ;;
736
741
  esac