brainlayer 1.5.0__tar.gz → 1.5.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 (195) hide show
  1. {brainlayer-1.5.0 → brainlayer-1.5.2}/PKG-INFO +1 -1
  2. {brainlayer-1.5.0 → brainlayer-1.5.2}/pyproject.toml +3 -1
  3. brainlayer-1.5.2/scripts/hotlane_brainbar_daemon.py +611 -0
  4. {brainlayer-1.5.0 → brainlayer-1.5.2}/scripts/launchd/com.brainlayer.hotlane-brainbar.plist +1 -1
  5. {brainlayer-1.5.0 → brainlayer-1.5.2}/scripts/launchd/install.sh +225 -5
  6. {brainlayer-1.5.0 → brainlayer-1.5.2}/server.json +2 -2
  7. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/__init__.py +1 -1
  8. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/setup.py +4 -2
  9. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/watcher.py +210 -43
  10. {brainlayer-1.5.0 → brainlayer-1.5.2}/.gitignore +0 -0
  11. {brainlayer-1.5.0 → brainlayer-1.5.2}/LICENSE +0 -0
  12. {brainlayer-1.5.0 → brainlayer-1.5.2}/README.md +0 -0
  13. {brainlayer-1.5.0 → brainlayer-1.5.2}/scripts/launchd/backup-daily.sh +0 -0
  14. {brainlayer-1.5.0 → brainlayer-1.5.2}/scripts/launchd/brainlayer-env-run.sh +0 -0
  15. {brainlayer-1.5.0 → brainlayer-1.5.2}/scripts/launchd/brainlayer.env.example +0 -0
  16. {brainlayer-1.5.0 → brainlayer-1.5.2}/scripts/launchd/com.brainlayer.backup-daily.plist +0 -0
  17. {brainlayer-1.5.0 → brainlayer-1.5.2}/scripts/launchd/com.brainlayer.decay.plist +0 -0
  18. {brainlayer-1.5.0 → brainlayer-1.5.2}/scripts/launchd/com.brainlayer.drain.plist +0 -0
  19. {brainlayer-1.5.0 → brainlayer-1.5.2}/scripts/launchd/com.brainlayer.enrichment.plist +0 -0
  20. {brainlayer-1.5.0 → brainlayer-1.5.2}/scripts/launchd/com.brainlayer.health-check.plist +0 -0
  21. {brainlayer-1.5.0 → brainlayer-1.5.2}/scripts/launchd/com.brainlayer.index.plist +0 -0
  22. {brainlayer-1.5.0 → brainlayer-1.5.2}/scripts/launchd/com.brainlayer.jsonl-backup.plist +0 -0
  23. {brainlayer-1.5.0 → brainlayer-1.5.2}/scripts/launchd/com.brainlayer.maintenance-nightly.plist +0 -0
  24. {brainlayer-1.5.0 → brainlayer-1.5.2}/scripts/launchd/com.brainlayer.maintenance-weekly.plist +0 -0
  25. {brainlayer-1.5.0 → brainlayer-1.5.2}/scripts/launchd/com.brainlayer.p0-counter.plist +0 -0
  26. {brainlayer-1.5.0 → brainlayer-1.5.2}/scripts/launchd/com.brainlayer.repair-fts.plist +0 -0
  27. {brainlayer-1.5.0 → brainlayer-1.5.2}/scripts/launchd/com.brainlayer.throughput-watchdog.plist +0 -0
  28. {brainlayer-1.5.0 → brainlayer-1.5.2}/scripts/launchd/com.brainlayer.tier0-watchdog.plist +0 -0
  29. {brainlayer-1.5.0 → brainlayer-1.5.2}/scripts/launchd/com.brainlayer.wal-checkpoint.plist +0 -0
  30. {brainlayer-1.5.0 → brainlayer-1.5.2}/scripts/launchd/com.brainlayer.watch.plist +0 -0
  31. {brainlayer-1.5.0 → brainlayer-1.5.2}/scripts/launchd/jsonl-backup.sh +0 -0
  32. {brainlayer-1.5.0 → brainlayer-1.5.2}/scripts/launchd/throughput-watchdog.py +0 -0
  33. {brainlayer-1.5.0 → brainlayer-1.5.2}/scripts/tier0-watchdog.sh +0 -0
  34. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/_helpers.py +0 -0
  35. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/agent_profiles.py +0 -0
  36. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/agent_provenance.py +0 -0
  37. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/alarm.py +0 -0
  38. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/backup_daily.py +0 -0
  39. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/bitemporal.py +0 -0
  40. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/brainbar_hybrid_helper.py +0 -0
  41. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/calibrate.py +0 -0
  42. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/chunk_origin.py +0 -0
  43. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/chunk_origin_backfill.py +0 -0
  44. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/classify.py +0 -0
  45. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/claude_paths.py +0 -0
  46. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/cli/__init__.py +0 -0
  47. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/cli/wizard.py +0 -0
  48. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/cli_new.py +0 -0
  49. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/cloud_backfill.py +0 -0
  50. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/clustering.py +0 -0
  51. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/config.py +0 -0
  52. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/content_class.py +0 -0
  53. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/correction_judge.py +0 -0
  54. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/data/agents_registry.yaml +0 -0
  55. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/data/sandbox_seeds/skill-eval-baseline.json +0 -0
  56. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/db_shrink.py +0 -0
  57. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/decay.py +0 -0
  58. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/decay_backfill.py +0 -0
  59. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/decay_job.py +0 -0
  60. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/dedupe.py +0 -0
  61. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/deploy_drift.py +0 -0
  62. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/doctor.py +0 -0
  63. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/drain.py +0 -0
  64. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/drain_liveness.py +0 -0
  65. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/embeddings.py +0 -0
  66. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/engine.py +0 -0
  67. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/enrichment_controller.py +0 -0
  68. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/eval/__init__.py +0 -0
  69. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/eval/abcde_enrich_runner.py +0 -0
  70. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/eval/abcde_variants.py +0 -0
  71. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/eval/abcde_variants.yaml +0 -0
  72. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/eval/benchmark.py +0 -0
  73. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/eval/enrichment_gold.py +0 -0
  74. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/eval/enrichment_graders.py +0 -0
  75. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/eval/enrichment_judge.py +0 -0
  76. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/eval/enrichment_llm_judge.py +0 -0
  77. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/eval/enrichment_quality_benchmark.py +0 -0
  78. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/eval/experiment_store.py +0 -0
  79. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/eval/fixtures/README.md +0 -0
  80. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/eval/labeling/enrichment-labeler.html +0 -0
  81. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/eval/phoenix_gate/__init__.py +0 -0
  82. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/eval/phoenix_gate/__main__.py +0 -0
  83. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/eval/phoenix_gate/baseline_store.py +0 -0
  84. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/eval/phoenix_gate/cli.py +0 -0
  85. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/eval/phoenix_gate/models.py +0 -0
  86. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/eval/phoenix_gate/phoenix_client.py +0 -0
  87. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/eval/phoenix_gate/regression_gate.py +0 -0
  88. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/eval/phoenix_gate/triggers.py +0 -0
  89. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/fallback_replay.py +0 -0
  90. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/git_learning.py +0 -0
  91. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/health_check.py +0 -0
  92. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/hooks/__init__.py +0 -0
  93. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/hooks/indexer.py +0 -0
  94. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/index_new.py +0 -0
  95. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/ingest/__init__.py +0 -0
  96. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/ingest/codex.py +0 -0
  97. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/ingest_denylist.py +0 -0
  98. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/ingest_guard.py +0 -0
  99. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/isolation_proof.py +0 -0
  100. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/jsonl_backup.py +0 -0
  101. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/kg/__init__.py +0 -0
  102. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/kg_cleanup.py +0 -0
  103. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/kg_judge.py +0 -0
  104. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/kg_promotion.py +0 -0
  105. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/kg_repo.py +0 -0
  106. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/kg_review_session.py +0 -0
  107. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/kg_session_finish.py +0 -0
  108. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/kg_session_harvest.py +0 -0
  109. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/launchd_primitive.py +0 -0
  110. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/lexical_defense.py +0 -0
  111. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/lexical_defense_dictionary.json +0 -0
  112. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/maintenance.py +0 -0
  113. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/mcp/__init__.py +0 -0
  114. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/mcp/_format.py +0 -0
  115. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/mcp/_shared.py +0 -0
  116. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/mcp/enrich_handler.py +0 -0
  117. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/mcp/entity_handler.py +0 -0
  118. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/mcp/palette.py +0 -0
  119. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/mcp/search_handler.py +0 -0
  120. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/mcp/store_handler.py +0 -0
  121. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/mcp/tags_handler.py +0 -0
  122. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/mcp_stdio_bridge.py +0 -0
  123. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/memory_types.py +0 -0
  124. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/migrate.py +0 -0
  125. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/p0_longitudinal_count.py +0 -0
  126. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/parent_death.py +0 -0
  127. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/paths.py +0 -0
  128. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/phonetic.py +0 -0
  129. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/pipeline/__init__.py +0 -0
  130. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/pipeline/agent_enrichment.py +0 -0
  131. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/pipeline/analyze_communication.py +0 -0
  132. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/pipeline/batch_extraction.py +0 -0
  133. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/pipeline/brain_graph.py +0 -0
  134. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/pipeline/chat_tags.py +0 -0
  135. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/pipeline/chunk.py +0 -0
  136. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/pipeline/classify.py +0 -0
  137. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/pipeline/cluster_sampling.py +0 -0
  138. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/pipeline/code_intelligence.py +0 -0
  139. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/pipeline/correction_detection.py +0 -0
  140. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/pipeline/correction_mining.py +0 -0
  141. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/pipeline/digest.py +0 -0
  142. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/pipeline/enrichment.py +0 -0
  143. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/pipeline/enrichment_tiers.py +0 -0
  144. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/pipeline/entity_extraction.py +0 -0
  145. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/pipeline/entity_resolution.py +0 -0
  146. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/pipeline/extract.py +0 -0
  147. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/pipeline/extract_claude_desktop.py +0 -0
  148. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/pipeline/extract_corrections.py +0 -0
  149. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/pipeline/extract_markdown.py +0 -0
  150. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/pipeline/extract_whatsapp.py +0 -0
  151. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/pipeline/frustration_mining.py +0 -0
  152. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/pipeline/git_overlay.py +0 -0
  153. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/pipeline/kg_extraction.py +0 -0
  154. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/pipeline/kg_extraction_groq.py +0 -0
  155. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/pipeline/longitudinal_analyzer.py +0 -0
  156. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/pipeline/obsidian_export.py +0 -0
  157. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/pipeline/operation_grouping.py +0 -0
  158. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/pipeline/plan_linking.py +0 -0
  159. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/pipeline/rate_limiter.py +0 -0
  160. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/pipeline/sanitize.py +0 -0
  161. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/pipeline/secret_scrub.py +0 -0
  162. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/pipeline/semantic_style.py +0 -0
  163. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/pipeline/sentiment.py +0 -0
  164. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/pipeline/session_enrichment.py +0 -0
  165. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/pipeline/style_embed.py +0 -0
  166. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/pipeline/style_index.py +0 -0
  167. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/pipeline/tag_entity_promotion.py +0 -0
  168. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/pipeline/temporal_chains.py +0 -0
  169. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/pipeline/time_batcher.py +0 -0
  170. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/pipeline/unified_timeline.py +0 -0
  171. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/pipeline/write_queue.py +0 -0
  172. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/provenance.py +0 -0
  173. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/provenance_autosupersede.py +0 -0
  174. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/provenance_integration.py +0 -0
  175. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/queue_io.py +0 -0
  176. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/queue_merge.py +0 -0
  177. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/reembed_backfill.py +0 -0
  178. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/runtime_store.py +0 -0
  179. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/sandbox_db.py +0 -0
  180. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/scoping.py +0 -0
  181. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/search_fanout.py +0 -0
  182. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/search_profile.py +0 -0
  183. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/search_repo.py +0 -0
  184. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/session_repo.py +0 -0
  185. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/storage.py +0 -0
  186. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/store.py +0 -0
  187. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/system_prompt_guard.py +0 -0
  188. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/tag_normalization.py +0 -0
  189. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/taxonomy.json +0 -0
  190. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/telemetry.py +0 -0
  191. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/types.py +0 -0
  192. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/vector_store.py +0 -0
  193. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/wal_checkpoint.py +0 -0
  194. {brainlayer-1.5.0 → brainlayer-1.5.2}/src/brainlayer/watcher_bridge.py +0 -0
  195. {brainlayer-1.5.0 → brainlayer-1.5.2}/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.0
3
+ Version: 1.5.2
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "brainlayer"
3
- version = "1.5.0"
3
+ version = "1.5.2"
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"
@@ -131,12 +131,14 @@ exclude = [
131
131
 
132
132
  [tool.hatch.build.targets.wheel.force-include]
133
133
  "scripts/launchd" = "brainlayer/launchd"
134
+ "scripts/hotlane_brainbar_daemon.py" = "brainlayer/launchd/hotlane_brainbar_daemon.py"
134
135
  "scripts/tier0-watchdog.sh" = "brainlayer/launchd/tier0-watchdog.sh"
135
136
 
136
137
  [tool.hatch.build.targets.sdist]
137
138
  only-include = [
138
139
  "src/brainlayer",
139
140
  "scripts/launchd",
141
+ "scripts/hotlane_brainbar_daemon.py",
140
142
  "scripts/tier0-watchdog.sh",
141
143
  "README.md",
142
144
  "LICENSE",
@@ -0,0 +1,611 @@
1
+ #!/usr/bin/env python3
2
+ """Hot-embed recent BrainBar MCP writes and interleave realtime enrichment.
3
+
4
+ BrainBar owns the live MCP socket and stores ``brain_store`` rows directly in
5
+ SQLite. This adapter owns the single BrainLayer writer slot and must therefore
6
+ perform all write-side background work that needs steady progress: hot embedding,
7
+ optional embedding backlog, and enrichment backlog.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import argparse
13
+ import inspect
14
+ import logging
15
+ import os
16
+ import signal
17
+ import time
18
+ from pathlib import Path
19
+ from typing import Callable, NamedTuple
20
+
21
+ import apsw
22
+ import sqlite_vec
23
+
24
+ from brainlayer._helpers import serialize_f32
25
+ from brainlayer.embeddings import get_embedding_model
26
+ from brainlayer.enrichment_controller import (
27
+ DEFAULT_ENRICH_SUPERVISOR_SINCE_HOURS,
28
+ _result_hit_daily_cap,
29
+ enrich_realtime,
30
+ )
31
+ from brainlayer.paths import get_db_path
32
+ from brainlayer.store import embed_hot_chunk, embed_pending_chunks
33
+ from brainlayer.vector_store import VectorStore
34
+ from brainlayer.writer_telemetry import start_writer_span
35
+
36
+ LOGGER = logging.getLogger("brainlayer.hotlane_brainbar")
37
+ STOP = False
38
+ DEFAULT_HOTLANE_ENRICH_LIMIT = 5
39
+ DEFAULT_BACKLOG_BATCH = 4
40
+ MAX_BACKLOG_BATCH = 16
41
+ DEFAULT_HOTLANE_WRITE_BUSY_TIMEOUT_MS = 1000
42
+ MAX_APSW_BUSY_TIMEOUT_MS = 2_147_483_647
43
+ VECTOR_WRITE_YIELD_SECONDS = 0.005
44
+ _sleep = time.sleep
45
+
46
+
47
+ class CycleResult(NamedTuple):
48
+ embedded: int = 0
49
+ enrich_attempted: int = 0
50
+ enriched: int = 0
51
+ enrich_skipped: int = 0
52
+ enrich_failed: int = 0
53
+ enrich_daily_cap_reached: bool = False
54
+
55
+
56
+ class EmbedCandidate(NamedTuple):
57
+ chunk_id: str
58
+ content: str
59
+
60
+
61
+ class EmbeddedVector(NamedTuple):
62
+ chunk_id: str
63
+ content: str
64
+ embedding: list[float]
65
+
66
+
67
+ def _stop(_signum: int, _frame: object) -> None:
68
+ global STOP
69
+ STOP = True
70
+
71
+
72
+ def _candidate_chunk_ids(store: VectorStore, *, limit: int) -> list[str]:
73
+ rows = store.conn.cursor().execute(
74
+ """
75
+ SELECT c.id
76
+ FROM chunks c
77
+ LEFT JOIN chunk_vectors_rowids r ON r.id = c.id
78
+ WHERE r.id IS NULL
79
+ AND c.source_file = 'brainbar-store'
80
+ AND c.source = 'mcp'
81
+ AND c.content IS NOT NULL
82
+ AND c.content != ''
83
+ AND c.archived_at IS NULL
84
+ AND c.superseded_by IS NULL
85
+ AND c.aggregated_into IS NULL
86
+ AND COALESCE(c.archived, 0) = 0
87
+ AND COALESCE(c.status, 'active') = 'active'
88
+ ORDER BY c.created_at DESC
89
+ LIMIT ?
90
+ """,
91
+ (limit,),
92
+ )
93
+ return [str(row[0]) for row in rows]
94
+
95
+
96
+ def _candidate_chunk_rows(store: VectorStore, *, limit: int) -> list[EmbedCandidate]:
97
+ 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,),
116
+ )
117
+ return [EmbedCandidate(str(row[0]), str(row[1])) for row in rows]
118
+
119
+
120
+ def _pending_chunk_rows(store: VectorStore, *, limit: int) -> list[EmbedCandidate]:
121
+ rows = store.conn.cursor().execute(
122
+ """
123
+ SELECT c.id, c.content
124
+ FROM chunks c
125
+ LEFT JOIN chunk_vectors_rowids r ON c.id = r.id
126
+ WHERE r.id IS NULL
127
+ AND c.content IS NOT NULL
128
+ AND c.content != ''
129
+ AND c.archived_at IS NULL
130
+ AND c.superseded_by IS NULL
131
+ AND c.aggregated_into IS NULL
132
+ AND COALESCE(c.archived, 0) = 0
133
+ AND COALESCE(c.status, 'active') = 'active'
134
+ ORDER BY c.created_at ASC
135
+ LIMIT ?
136
+ """,
137
+ (limit,),
138
+ )
139
+ return [EmbedCandidate(str(row[0]), str(row[1])) for row in rows]
140
+
141
+
142
+ def _embed_candidates(
143
+ candidates: list[EmbedCandidate],
144
+ *,
145
+ embed_fn: Callable[[str], list[float]],
146
+ embed_batch_fn: Callable[[list[str]], list[list[float]]] | None = None,
147
+ ) -> list[EmbeddedVector]:
148
+ if not candidates:
149
+ return []
150
+
151
+ if embed_batch_fn is not None and len(candidates) > 1:
152
+ try:
153
+ embeddings = embed_batch_fn([candidate.content for candidate in candidates])
154
+ if len(embeddings) != len(candidates):
155
+ raise ValueError(f"batch embedder returned {len(embeddings)} embeddings for {len(candidates)} chunks")
156
+ return [
157
+ EmbeddedVector(candidate.chunk_id, candidate.content, embedding)
158
+ for candidate, embedding in zip(candidates, embeddings)
159
+ ]
160
+ except Exception as exc:
161
+ LOGGER.warning("Failed to embed hotlane batch: %s", exc)
162
+
163
+ embedded: list[EmbeddedVector] = []
164
+ for candidate in candidates:
165
+ try:
166
+ embedded.append(EmbeddedVector(candidate.chunk_id, candidate.content, embed_fn(candidate.content)))
167
+ except Exception as exc:
168
+ LOGGER.warning("Failed to embed chunk %s: %s", candidate.chunk_id, exc)
169
+ return embedded
170
+
171
+
172
+ def _hotlane_write_busy_timeout_ms() -> int:
173
+ raw_value = os.environ.get("BRAINLAYER_HOTLANE_WRITE_BUSY_TIMEOUT_MS")
174
+ if raw_value is None:
175
+ return DEFAULT_HOTLANE_WRITE_BUSY_TIMEOUT_MS
176
+ try:
177
+ return max(1, min(int(raw_value), MAX_APSW_BUSY_TIMEOUT_MS))
178
+ except ValueError:
179
+ LOGGER.warning(
180
+ "Invalid BRAINLAYER_HOTLANE_WRITE_BUSY_TIMEOUT_MS=%r; using %dms",
181
+ raw_value,
182
+ DEFAULT_HOTLANE_WRITE_BUSY_TIMEOUT_MS,
183
+ )
184
+ return DEFAULT_HOTLANE_WRITE_BUSY_TIMEOUT_MS
185
+
186
+
187
+ def _open_hotlane_write_connection(db_path: Path) -> apsw.Connection:
188
+ conn = apsw.Connection(str(db_path), flags=apsw.SQLITE_OPEN_READWRITE)
189
+ conn.setbusytimeout(_hotlane_write_busy_timeout_ms())
190
+ conn.enableloadextension(True)
191
+ conn.loadextension(sqlite_vec.loadable_path())
192
+ conn.enableloadextension(False)
193
+ return conn
194
+
195
+
196
+ def _table_exists(conn: apsw.Connection, table_name: str) -> bool:
197
+ return bool(
198
+ conn.cursor()
199
+ .execute(
200
+ "SELECT 1 FROM sqlite_master WHERE type IN ('table', 'view') AND name = ?",
201
+ (table_name,),
202
+ )
203
+ .fetchone()
204
+ )
205
+
206
+
207
+ def _upsert_chunk_vector_raw(
208
+ cursor,
209
+ chunk_id: str,
210
+ embedding: list[float],
211
+ *,
212
+ binary_index_available: bool,
213
+ ) -> None:
214
+ embedding_bytes = serialize_f32(embedding)
215
+ cursor.execute("DELETE FROM chunk_vectors WHERE chunk_id = ?", (chunk_id,))
216
+ cursor.execute(
217
+ """
218
+ INSERT INTO chunk_vectors (chunk_id, embedding)
219
+ VALUES (?, ?)
220
+ """,
221
+ (chunk_id, embedding_bytes),
222
+ )
223
+ if binary_index_available:
224
+ cursor.execute("DELETE FROM chunk_vectors_binary WHERE chunk_id = ?", (chunk_id,))
225
+ cursor.execute(
226
+ """
227
+ INSERT INTO chunk_vectors_binary (chunk_id, embedding)
228
+ VALUES (?, vec_quantize_binary(?))
229
+ """,
230
+ (chunk_id, embedding_bytes),
231
+ )
232
+
233
+
234
+ def _write_embedded_vectors(store_or_path: VectorStore | Path | str, vectors: list[EmbeddedVector]) -> int:
235
+ if not vectors:
236
+ return 0
237
+
238
+ conn: apsw.Connection | None = None
239
+ db_path: Path | None
240
+ if hasattr(store_or_path, "conn"):
241
+ store = store_or_path
242
+ cursor = store.conn.cursor()
243
+ db_path = getattr(store, "db_path", None)
244
+ upsert_chunk_vector = store._upsert_chunk_vector
245
+ else:
246
+ db_path = Path(store_or_path)
247
+ conn = _open_hotlane_write_connection(db_path)
248
+ cursor = conn.cursor()
249
+ binary_index_available = _table_exists(conn, "chunk_vectors_binary")
250
+
251
+ def upsert_chunk_vector(cursor, chunk_id, embedding):
252
+ return _upsert_chunk_vector_raw(
253
+ cursor,
254
+ chunk_id,
255
+ embedding,
256
+ binary_index_available=binary_index_available,
257
+ )
258
+
259
+ count = 0
260
+ try:
261
+ for vector_index, (chunk_id, content, embedding) in enumerate(vectors):
262
+ transaction_started = False
263
+ telemetry_span = start_writer_span(
264
+ cursor.connection if hasattr(cursor, "connection") else (conn or store.conn),
265
+ db_path=db_path,
266
+ producer="hotlane",
267
+ lane="hotlane",
268
+ operation="write_vectors",
269
+ rows_planned=1,
270
+ )
271
+ try:
272
+ cursor.execute("BEGIN IMMEDIATE")
273
+ transaction_started = True
274
+ still_eligible = cursor.execute(
275
+ """
276
+ SELECT 1
277
+ FROM chunks c
278
+ LEFT JOIN chunk_vectors_rowids r ON c.id = r.id
279
+ WHERE c.id = ?
280
+ AND c.content = ?
281
+ AND r.id IS NULL
282
+ AND c.content IS NOT NULL
283
+ AND c.content != ''
284
+ AND c.archived_at IS NULL
285
+ AND c.superseded_by IS NULL
286
+ AND c.aggregated_into IS NULL
287
+ AND COALESCE(c.archived, 0) = 0
288
+ AND COALESCE(c.status, 'active') = 'active'
289
+ """,
290
+ (chunk_id, content),
291
+ ).fetchone()
292
+ rows_touched = 0
293
+ if still_eligible:
294
+ upsert_chunk_vector(cursor, chunk_id, embedding)
295
+ rows_touched = 1
296
+ cursor.execute("COMMIT")
297
+ transaction_started = False
298
+ count += rows_touched
299
+ telemetry_span.finish("commit", rows_touched=rows_touched)
300
+ if vector_index < len(vectors) - 1:
301
+ _sleep(VECTOR_WRITE_YIELD_SECONDS)
302
+ except Exception as exc:
303
+ if transaction_started:
304
+ cursor.execute("ROLLBACK")
305
+ telemetry_span.finish("rollback", error=f"{type(exc).__name__}: {exc}")
306
+ raise
307
+ finally:
308
+ if conn is not None:
309
+ conn.close()
310
+ if count:
311
+ from brainlayer.search_repo import clear_hybrid_search_cache
312
+
313
+ clear_hybrid_search_cache(db_path)
314
+ return count
315
+
316
+
317
+ def _open_store(
318
+ vector_store_cls: Callable[..., VectorStore],
319
+ db_path: Path,
320
+ *,
321
+ readonly: bool,
322
+ ) -> VectorStore:
323
+ if not _callable_accepts_keyword(vector_store_cls, "readonly"):
324
+ return vector_store_cls(db_path)
325
+ try:
326
+ return vector_store_cls(db_path, readonly=readonly)
327
+ except TypeError as exc:
328
+ if not _type_error_rejects_keyword(exc, "readonly"):
329
+ raise
330
+ return vector_store_cls(db_path)
331
+
332
+
333
+ def _callable_accepts_keyword(func: Callable[..., object], keyword: str) -> bool:
334
+ try:
335
+ signature = inspect.signature(func)
336
+ except (TypeError, ValueError):
337
+ return True
338
+ return keyword in signature.parameters or any(
339
+ parameter.kind == inspect.Parameter.VAR_KEYWORD for parameter in signature.parameters.values()
340
+ )
341
+
342
+
343
+ def _type_error_rejects_keyword(exc: TypeError, keyword: str) -> bool:
344
+ message = str(exc)
345
+ return keyword in message and (
346
+ "unexpected keyword argument" in message
347
+ or "got an unexpected keyword" in message
348
+ or "invalid keyword argument" in message
349
+ )
350
+
351
+
352
+ def _run_split_cycle(
353
+ *,
354
+ db_path: Path,
355
+ vector_store_cls: Callable[..., VectorStore],
356
+ embed_fn: Callable[[str], list[float]],
357
+ recent_limit: int,
358
+ backlog_batch: int,
359
+ enrich_limit: int,
360
+ enrich_since_hours: int,
361
+ embed_batch_fn: Callable[[list[str]], list[list[float]]] | None = None,
362
+ enrich_fn: Callable[..., object] = enrich_realtime,
363
+ candidate_rows_fn: Callable[..., list[EmbedCandidate]] = _candidate_chunk_rows,
364
+ pending_rows_fn: Callable[..., list[EmbedCandidate]] = _pending_chunk_rows,
365
+ write_vectors_fn: Callable[..., int] = _write_embedded_vectors,
366
+ ) -> CycleResult:
367
+ embedded = 0
368
+ hot_rows: list[EmbedCandidate] = []
369
+ pending_rows: list[EmbedCandidate] = []
370
+
371
+ if recent_limit > 0 or backlog_batch > 0:
372
+ if not db_path.exists():
373
+ bootstrap_store = _open_store(vector_store_cls, db_path, readonly=False)
374
+ bootstrap_store.close()
375
+ read_store = _open_store(vector_store_cls, db_path, readonly=True)
376
+ try:
377
+ if recent_limit > 0:
378
+ hot_rows = candidate_rows_fn(read_store, limit=recent_limit)
379
+ if backlog_batch > 0:
380
+ pending_rows = pending_rows_fn(read_store, limit=backlog_batch)
381
+ finally:
382
+ read_store.close()
383
+
384
+ seen_hot_ids = {candidate.chunk_id for candidate in hot_rows}
385
+ pending_rows = [candidate for candidate in pending_rows if candidate.chunk_id not in seen_hot_ids]
386
+ vectors = _embed_candidates(hot_rows, embed_fn=embed_fn, embed_batch_fn=embed_batch_fn)
387
+ vectors.extend(_embed_candidates(pending_rows, embed_fn=embed_fn, embed_batch_fn=embed_batch_fn))
388
+ if vectors:
389
+ embedded += write_vectors_fn(db_path, vectors)
390
+
391
+ if enrich_limit <= 0:
392
+ return CycleResult(embedded=embedded)
393
+
394
+ enrich_store = _open_store(vector_store_cls, db_path, readonly=False)
395
+ try:
396
+ enrich_result = enrich_fn(store=enrich_store, limit=enrich_limit, since_hours=enrich_since_hours)
397
+ finally:
398
+ enrich_store.close()
399
+ return CycleResult(
400
+ embedded=embedded,
401
+ enrich_attempted=int(getattr(enrich_result, "attempted", 0) or 0),
402
+ enriched=int(getattr(enrich_result, "enriched", 0) or 0),
403
+ enrich_skipped=int(getattr(enrich_result, "skipped", 0) or 0),
404
+ enrich_failed=int(getattr(enrich_result, "failed", 0) or 0),
405
+ enrich_daily_cap_reached=hasattr(enrich_result, "errors") and _result_hit_daily_cap(enrich_result),
406
+ )
407
+
408
+
409
+ def _default_queue_dir() -> Path:
410
+ import os
411
+
412
+ return Path(os.environ.get("BRAINLAYER_QUEUE_DIR", str(Path.home() / ".brainlayer" / "queue"))).expanduser()
413
+
414
+
415
+ def _queue_depth(queue_dir: Path) -> int:
416
+ try:
417
+ return sum(1 for _path in queue_dir.glob("*.jsonl"))
418
+ except OSError:
419
+ return 0
420
+
421
+
422
+ def _high_priority_queue_depth(queue_dir: Path) -> int:
423
+ try:
424
+ return sum(1 for path in queue_dir.glob("*.jsonl") if not path.name.startswith("enrichment-"))
425
+ except OSError:
426
+ return 0
427
+
428
+
429
+ def run_cycle(
430
+ *,
431
+ store: VectorStore,
432
+ embed_fn: Callable[[str], list[float]],
433
+ recent_limit: int,
434
+ backlog_batch: int,
435
+ enrich_limit: int,
436
+ enrich_since_hours: int,
437
+ candidate_chunk_ids_fn: Callable[..., list[str]] = _candidate_chunk_ids,
438
+ hot_embed_fn: Callable[..., bool] = embed_hot_chunk,
439
+ pending_embed_fn: Callable[..., int] = embed_pending_chunks,
440
+ embed_batch_fn: Callable[[list[str]], list[list[float]]] | None = None,
441
+ enrich_fn: Callable[..., object] = enrich_realtime,
442
+ ) -> CycleResult:
443
+ embedded = 0
444
+ for chunk_id in candidate_chunk_ids_fn(store, limit=recent_limit):
445
+ if hot_embed_fn(store=store, embed_fn=embed_fn, chunk_id=chunk_id):
446
+ embedded += 1
447
+ break
448
+
449
+ if backlog_batch > 0:
450
+ embedded += pending_embed_fn(
451
+ store=store,
452
+ embed_fn=embed_fn,
453
+ batch_size=backlog_batch,
454
+ embed_batch_fn=embed_batch_fn,
455
+ )
456
+
457
+ if enrich_limit <= 0:
458
+ return CycleResult(embedded=embedded)
459
+
460
+ enrich_result = enrich_fn(store, limit=enrich_limit, since_hours=enrich_since_hours)
461
+ return CycleResult(
462
+ embedded=embedded,
463
+ enrich_attempted=int(getattr(enrich_result, "attempted", 0) or 0),
464
+ enriched=int(getattr(enrich_result, "enriched", 0) or 0),
465
+ enrich_skipped=int(getattr(enrich_result, "skipped", 0) or 0),
466
+ enrich_failed=int(getattr(enrich_result, "failed", 0) or 0),
467
+ enrich_daily_cap_reached=hasattr(enrich_result, "errors") and _result_hit_daily_cap(enrich_result),
468
+ )
469
+
470
+
471
+ def run(
472
+ *,
473
+ db_path: Path,
474
+ interval: float,
475
+ recent_limit: int,
476
+ backlog_interval: float,
477
+ backlog_batch: int,
478
+ enrich_interval: float,
479
+ enrich_limit: int,
480
+ enrich_since_hours: int,
481
+ vector_store_cls: Callable[[Path], VectorStore] = VectorStore,
482
+ model_factory: Callable[[], object] = get_embedding_model,
483
+ cycle_fn: Callable[..., CycleResult] = run_cycle,
484
+ time_fn: Callable[[], float] = time.monotonic,
485
+ sleep_fn: Callable[[float], None] = time.sleep,
486
+ max_cycles: int | None = None,
487
+ queue_dir: Path | None = None,
488
+ queue_depth_fn: Callable[[Path], int] = _queue_depth,
489
+ high_priority_queue_depth_fn: Callable[[Path], int] = _high_priority_queue_depth,
490
+ ) -> None:
491
+ model = model_factory()
492
+ embed_batch_fn = getattr(model, "embed_texts", None)
493
+ if embed_batch_fn is not None:
494
+
495
+ def embed_fn(text: str) -> list[float]:
496
+ embeddings = embed_batch_fn([text])
497
+ if len(embeddings) != 1:
498
+ raise RuntimeError(f"single text embedder returned {len(embeddings)} embeddings")
499
+ return embeddings[0]
500
+
501
+ else:
502
+ embed_fn = model.embed_query
503
+ queue_dir_was_explicit = queue_dir is not None
504
+ queue_dir = queue_dir or _default_queue_dir()
505
+ last_backlog = time_fn() - backlog_interval
506
+ last_enrich = 0.0
507
+ enrich_disabled = False
508
+ cycles = 0
509
+ backlog_batch = min(max(backlog_batch, 0), MAX_BACKLOG_BATCH)
510
+ LOGGER.info("hotlane adapter started db=%s", db_path)
511
+ while not STOP:
512
+ if max_cycles is not None and cycles >= max_cycles:
513
+ break
514
+ try:
515
+ now = time_fn()
516
+ queue_has_backlog = queue_depth_fn(queue_dir) > 0
517
+ 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:
520
+ 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")
523
+ cycles += 1
524
+ sleep_fn(interval)
525
+ 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
+ )
535
+ if cycle_backlog_batch > 0:
536
+ last_backlog = now
537
+ if cycle_enrich_limit > 0:
538
+ last_enrich = now
539
+ if cycle_fn is run_cycle:
540
+ result = _run_split_cycle(
541
+ db_path=db_path,
542
+ vector_store_cls=vector_store_cls,
543
+ embed_fn=embed_fn,
544
+ recent_limit=recent_limit,
545
+ backlog_batch=cycle_backlog_batch,
546
+ embed_batch_fn=embed_batch_fn,
547
+ enrich_limit=cycle_enrich_limit,
548
+ enrich_since_hours=enrich_since_hours,
549
+ )
550
+ else:
551
+ store = vector_store_cls(db_path)
552
+ try:
553
+ result = cycle_fn(
554
+ store=store,
555
+ embed_fn=embed_fn,
556
+ recent_limit=recent_limit,
557
+ backlog_batch=cycle_backlog_batch,
558
+ embed_batch_fn=embed_batch_fn,
559
+ enrich_limit=cycle_enrich_limit,
560
+ enrich_since_hours=enrich_since_hours,
561
+ )
562
+ finally:
563
+ store.close()
564
+ if result.enrich_daily_cap_reached:
565
+ enrich_disabled = True
566
+ LOGGER.warning("enrichment daily cap reached; disabling hotlane enrichment until restart")
567
+ if result.embedded or result.enrich_attempted:
568
+ LOGGER.info(
569
+ "embedded=%d enrich_attempted=%d enriched=%d skipped=%d failed=%d",
570
+ result.embedded,
571
+ result.enrich_attempted,
572
+ result.enriched,
573
+ result.enrich_skipped,
574
+ result.enrich_failed,
575
+ )
576
+ except Exception:
577
+ LOGGER.exception("hotlane adapter cycle failed")
578
+ sleep_fn(min(interval * 2, 5.0))
579
+ cycles += 1
580
+ sleep_fn(interval)
581
+
582
+
583
+ def main() -> None:
584
+ parser = argparse.ArgumentParser(description=__doc__)
585
+ parser.add_argument("--db", type=Path, default=get_db_path())
586
+ parser.add_argument("--interval", type=float, default=1.0)
587
+ parser.add_argument("--recent-limit", type=int, default=5)
588
+ parser.add_argument("--backlog-interval", type=float, default=10.0)
589
+ parser.add_argument("--backlog-batch", type=int, default=DEFAULT_BACKLOG_BATCH)
590
+ parser.add_argument("--enrich-interval", type=float, default=10.0)
591
+ parser.add_argument("--enrich-limit", type=int, default=DEFAULT_HOTLANE_ENRICH_LIMIT)
592
+ parser.add_argument("--enrich-since-hours", type=int, default=DEFAULT_ENRICH_SUPERVISOR_SINCE_HOURS)
593
+ args = parser.parse_args()
594
+
595
+ logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
596
+ signal.signal(signal.SIGTERM, _stop)
597
+ signal.signal(signal.SIGINT, _stop)
598
+ run(
599
+ db_path=args.db,
600
+ interval=max(args.interval, 0.25),
601
+ recent_limit=max(args.recent_limit, 1),
602
+ backlog_interval=max(args.backlog_interval, 1.0),
603
+ backlog_batch=min(max(args.backlog_batch, 0), MAX_BACKLOG_BATCH),
604
+ enrich_interval=max(args.enrich_interval, 1.0),
605
+ enrich_limit=max(args.enrich_limit, 0),
606
+ enrich_since_hours=max(args.enrich_since_hours, 0),
607
+ )
608
+
609
+
610
+ if __name__ == "__main__":
611
+ main()
@@ -8,7 +8,7 @@
8
8
  <array>
9
9
  <string>__BRAINLAYER_ENV_RUN__</string>
10
10
  <string>__BRAINLAYER_PYTHON__</string>
11
- <string>__BRAINLAYER_DIR__/scripts/hotlane_brainbar_daemon.py</string>
11
+ <string>__HOTLANE_BRAINBAR_DAEMON__</string>
12
12
  <string>--interval</string>
13
13
  <string>1.0</string>
14
14
  <string>--recent-limit</string>