brainiac-cli 0.20.44__tar.gz → 0.20.46__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 (360) hide show
  1. {brainiac_cli-0.20.44/src/brainiac_cli.egg-info → brainiac_cli-0.20.46}/PKG-INFO +7 -5
  2. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/pyproject.toml +24 -4
  3. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/scripts/brain-brief.sh +15 -0
  4. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_version.py +1 -1
  5. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/audit_drift.py +2 -1
  6. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/core/_maintenance_state.py +2 -1
  7. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/embed.py +7 -4
  8. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/embed_onnx.py +8 -4
  9. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/exceptions_page.py +3 -2
  10. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/exceptions_verify.py +3 -2
  11. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/folds/intake.py +5 -5
  12. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/graph_ops.py +3 -2
  13. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index/_search.py +15 -10
  14. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/deliverables.py +2 -1
  15. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/handlers/concealment_pdf.py +4 -1
  16. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/pipeline_files.py +8 -3
  17. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/interview.py +2 -1
  18. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/invariant_coverage.py +2 -1
  19. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/maintenance_notify.py +2 -1
  20. brainiac_cli-0.20.46/src/brain/model_slot.py +110 -0
  21. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/overlay_generated.py +2 -1
  22. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/provision.py +2 -2
  23. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/remediation_exceptions_store.py +2 -1
  24. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/remediation_state.py +2 -1
  25. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/rerank.py +7 -4
  26. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/rerank_onnx.py +28 -4
  27. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/session_hook_settings.py +2 -1
  28. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/update_channels.py +18 -12
  29. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/vmstaging.py +22 -6
  30. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/workspaces.py +2 -1
  31. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46/src/brainiac_cli.egg-info}/PKG-INFO +7 -5
  32. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brainiac_cli.egg-info/SOURCES.txt +1 -0
  33. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brainiac_cli.egg-info/requires.txt +12 -2
  34. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/LICENSE +0 -0
  35. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/MANIFEST.in +0 -0
  36. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/README.md +0 -0
  37. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/setup.cfg +0 -0
  38. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/__init__.py +0 -0
  39. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/__main__.py +0 -0
  40. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/AGENTS.md +0 -0
  41. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/assets/graph-explorer-template.html +0 -0
  42. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/overlay/template/brand/brand-guide.md +0 -0
  43. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/overlay/template/keywords/glossary.md +0 -0
  44. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/overlay/template/people/roster.md +0 -0
  45. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/overlay/template/voice/voice-profile.md +0 -0
  46. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/routines/manifest.json +0 -0
  47. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/scripts/brain-brief-mac.plist +0 -0
  48. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/scripts/brain-synthesis-mac.plist +0 -0
  49. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/scripts/brain-synthesis.sh +0 -0
  50. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/scripts/brain_synthesis_codex.py +0 -0
  51. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/scripts/brainiac-alerts.sh +0 -0
  52. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/scripts/brainiac-egress-guard.sh +0 -0
  53. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/scripts/install-brief-mac.sh +0 -0
  54. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/scripts/install-brief-windows.ps1 +0 -0
  55. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/scripts/register_tasks.py +0 -0
  56. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/scripts/vm-boundary-probe.sh +0 -0
  57. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/scripts/vm-selftest.sh +0 -0
  58. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/templates/company.md +0 -0
  59. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/templates/concept.md +0 -0
  60. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/templates/daily.md +0 -0
  61. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/templates/decision.md +0 -0
  62. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/templates/deliverable.md +0 -0
  63. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/templates/meeting.md +0 -0
  64. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/templates/person.md +0 -0
  65. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/templates/project.md +0 -0
  66. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/templates/state-moc.md +0 -0
  67. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_optional.py +0 -0
  68. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/alerts.py +0 -0
  69. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/alerts_render.py +0 -0
  70. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/alerts_staging.py +0 -0
  71. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/anchor.py +0 -0
  72. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/atomic_temp.py +0 -0
  73. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/attestation.py +0 -0
  74. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/audit.py +0 -0
  75. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/audit_chain.py +0 -0
  76. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/autolink.py +0 -0
  77. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/backup.py +0 -0
  78. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/brief.py +0 -0
  79. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/brief_render.py +0 -0
  80. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/capture.py +0 -0
  81. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/chunk.py +0 -0
  82. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/classification.py +0 -0
  83. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli.py +0 -0
  84. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/__init__.py +0 -0
  85. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/common_parser.py +0 -0
  86. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/connect.py +0 -0
  87. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/connect_parser.py +0 -0
  88. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/eval.py +0 -0
  89. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/eval_parser.py +0 -0
  90. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/index_storage.py +0 -0
  91. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/index_storage_parser.py +0 -0
  92. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/ingest_storage.py +0 -0
  93. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/ingest_storage_parser.py +0 -0
  94. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/lifecycle.py +0 -0
  95. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/lifecycle_parser.py +0 -0
  96. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/maintenance.py +0 -0
  97. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/maintenance_parser.py +0 -0
  98. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/navigation.py +0 -0
  99. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/navigation_parser.py +0 -0
  100. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/owner_workflow.py +0 -0
  101. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/owner_workflow_parser.py +0 -0
  102. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/presentation.py +0 -0
  103. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/presentation_parser.py +0 -0
  104. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/retrieval.py +0 -0
  105. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/retrieval_parser.py +0 -0
  106. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/update.py +0 -0
  107. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/update_parser.py +0 -0
  108. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/versioning.py +0 -0
  109. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/versioning_parser.py +0 -0
  110. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_help.py +0 -0
  111. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_read_record.py +0 -0
  112. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_render.py +0 -0
  113. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/config.py +0 -0
  114. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/config_hostpaths.py +0 -0
  115. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/connect.py +0 -0
  116. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/context.py +0 -0
  117. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/conventions.py +0 -0
  118. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/conventions_scan.py +0 -0
  119. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/core/__init__.py +0 -0
  120. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/core/_audit.py +0 -0
  121. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/core/_briefing.py +0 -0
  122. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/core/_capture.py +0 -0
  123. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/core/_capture_staging.py +0 -0
  124. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/core/_durability.py +0 -0
  125. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/core/_health.py +0 -0
  126. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/core/_maintenance.py +0 -0
  127. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/core/_retrieval.py +0 -0
  128. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/core/_shared.py +0 -0
  129. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/core/_supersede_batch.py +0 -0
  130. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/core/_supersession.py +0 -0
  131. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/core/_supersession_journal.py +0 -0
  132. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cowork_leak_artifact.py +0 -0
  133. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cowork_leak_databases.py +0 -0
  134. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cowork_leak_notes.py +0 -0
  135. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cowork_leak_scan.py +0 -0
  136. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cowork_snapshot_guard.py +0 -0
  137. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cowork_staging.py +0 -0
  138. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/dbretry.py +0 -0
  139. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/deliverables_absorb.py +0 -0
  140. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/deliverables_apply.py +0 -0
  141. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/deliverables_ledger.py +0 -0
  142. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/deliverables_materialize.py +0 -0
  143. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/deliverables_previous.py +0 -0
  144. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/deliverables_readme.py +0 -0
  145. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/deliverables_shelf.py +0 -0
  146. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/deliverables_sync.py +0 -0
  147. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/doctor.py +0 -0
  148. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/doctor_checks.py +0 -0
  149. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/doctor_context.py +0 -0
  150. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/doctor_desktop.py +0 -0
  151. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/doctor_health.py +0 -0
  152. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/doctor_mount_leak.py +0 -0
  153. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/doctor_plugins.py +0 -0
  154. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/doctor_staging.py +0 -0
  155. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/doctor_vendor.py +0 -0
  156. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/doctor_vm.py +0 -0
  157. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/doctor_wiring.py +0 -0
  158. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/draft_drain.py +0 -0
  159. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/draft_update.py +0 -0
  160. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/egress.py +0 -0
  161. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/egress_terms.py +0 -0
  162. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/embed_backends.py +0 -0
  163. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/embed_catalog.py +0 -0
  164. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/embed_qwen.py +0 -0
  165. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/encryption.py +0 -0
  166. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/exceptions_cli.py +0 -0
  167. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/exceptions_render.py +0 -0
  168. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/exceptions_tray.py +0 -0
  169. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/folds/__init__.py +0 -0
  170. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/folds/context.py +0 -0
  171. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/folds/daily.py +0 -0
  172. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/folds/deliverables.py +0 -0
  173. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/folds/golden.py +0 -0
  174. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/folds/graph.py +0 -0
  175. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/folds/invariants.py +0 -0
  176. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/folds/orchestrator.py +0 -0
  177. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/folds/organization.py +0 -0
  178. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/folds/preflight.py +0 -0
  179. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/folds/publish.py +0 -0
  180. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/folds/remediation.py +0 -0
  181. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/folds/remediation_bookkeeping.py +0 -0
  182. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/folds/reporting.py +0 -0
  183. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/folds/retention.py +0 -0
  184. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/folds/watchdogs.py +0 -0
  185. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/folds/weekly.py +0 -0
  186. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/frontmatter.py +0 -0
  187. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/golden_ops.py +0 -0
  188. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/golden_probe.py +0 -0
  189. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/golden_probe_cases.py +0 -0
  190. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/graph.py +0 -0
  191. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/graph_hygiene.py +0 -0
  192. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/graphify.py +0 -0
  193. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/graphify_edges.py +0 -0
  194. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/graphreport.py +0 -0
  195. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/graphreport_sections.py +0 -0
  196. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/graphreport_sections_2.py +0 -0
  197. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/guard_settings.py +0 -0
  198. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/healthreport.py +0 -0
  199. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/healthreport_sections.py +0 -0
  200. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/healthreport_unsigned.py +0 -0
  201. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/inbox.py +0 -0
  202. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index/__init__.py +0 -0
  203. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index/_connection.py +0 -0
  204. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index/_families.py +0 -0
  205. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index/_graph.py +0 -0
  206. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index/_identity.py +0 -0
  207. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index/_lifecycle.py +0 -0
  208. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index/_models.py +0 -0
  209. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index/_planning.py +0 -0
  210. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index/_records.py +0 -0
  211. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index/_retirement.py +0 -0
  212. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index/_schema.py +0 -0
  213. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index/_settings.py +0 -0
  214. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index/_shared.py +0 -0
  215. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index/_tools.py +0 -0
  216. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index/_zones.py +0 -0
  217. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index_stages/__init__.py +0 -0
  218. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index_stages/connection.py +0 -0
  219. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index_stages/diagnostics.py +0 -0
  220. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index_stages/duplicate_families.py +0 -0
  221. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index_stages/near_duplicates.py +0 -0
  222. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index_stages/rebuild.py +0 -0
  223. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index_stages/reranking.py +0 -0
  224. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index_stages/search.py +0 -0
  225. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index_stages/search_finalization.py +0 -0
  226. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index_stages/search_identity.py +0 -0
  227. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index_stages/search_ranking.py +0 -0
  228. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index_stages/sync.py +0 -0
  229. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/__init__.py +0 -0
  230. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/handlers/__init__.py +0 -0
  231. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/handlers/base.py +0 -0
  232. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/handlers/concealment.py +0 -0
  233. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/handlers/concealment_gate.py +0 -0
  234. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/handlers/concealment_html.py +0 -0
  235. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/handlers/concealment_ledger.py +0 -0
  236. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/handlers/concealment_office.py +0 -0
  237. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/handlers/docx.py +0 -0
  238. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/handlers/email.py +0 -0
  239. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/handlers/html.py +0 -0
  240. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/handlers/image.py +0 -0
  241. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/handlers/ole.py +0 -0
  242. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/handlers/pdf.py +0 -0
  243. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/handlers/pptx.py +0 -0
  244. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/handlers/tables.py +0 -0
  245. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/handlers/text.py +0 -0
  246. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/handlers/xlsx.py +0 -0
  247. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/handlers/zip.py +0 -0
  248. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/pipeline.py +0 -0
  249. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/pipeline_duplicates.py +0 -0
  250. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/pipeline_injection.py +0 -0
  251. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/pipeline_nested.py +0 -0
  252. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/pipeline_run.py +0 -0
  253. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/pipeline_stages.py +0 -0
  254. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/tierguard.py +0 -0
  255. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/tierguard_verdict.py +0 -0
  256. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/transcript.py +0 -0
  257. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/init.py +0 -0
  258. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/init_import.py +0 -0
  259. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/init_samples.py +0 -0
  260. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/init_seed.py +0 -0
  261. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/init_steps.py +0 -0
  262. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/injection_fold.py +0 -0
  263. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/injection_scan.py +0 -0
  264. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/interview_apply.py +0 -0
  265. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/interview_detect.py +0 -0
  266. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/invariant_crosstier.py +0 -0
  267. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/invariant_deliverables.py +0 -0
  268. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/invariant_floors.py +0 -0
  269. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/invariant_ingest_guard.py +0 -0
  270. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/invariant_lane_health.py +0 -0
  271. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/invariant_reachability.py +0 -0
  272. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/invariant_shared.py +0 -0
  273. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/invariant_unsigned_notes.py +0 -0
  274. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/invariants.py +0 -0
  275. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/invariants_metrics.py +0 -0
  276. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/language.py +0 -0
  277. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/link_targets.py +0 -0
  278. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/lock.py +0 -0
  279. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/maintenance.py +0 -0
  280. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/maintenance_escalation.py +0 -0
  281. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/maintenance_folds.py +0 -0
  282. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/maintenance_folds_2.py +0 -0
  283. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/maintenance_folds_3.py +0 -0
  284. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/maintenance_folds_4.py +0 -0
  285. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/maintenance_golden.py +0 -0
  286. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/maintenance_graphfolds.py +0 -0
  287. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/maintenance_healthlog.py +0 -0
  288. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/maintenance_hotmd.py +0 -0
  289. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/maintenance_invariant_fields.py +0 -0
  290. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/maintenance_outcomes.py +0 -0
  291. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/maintenance_para.py +0 -0
  292. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/maintenance_recommendations.py +0 -0
  293. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/maintenance_retention.py +0 -0
  294. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/maintenance_sweep.py +0 -0
  295. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/maintenance_watchdog.py +0 -0
  296. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/mcp_adapter.py +0 -0
  297. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/mcp_capture_verbs.py +0 -0
  298. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/mcp_housekeeping_verbs.py +0 -0
  299. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/mcp_mediation.py +0 -0
  300. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/mcp_supersede_verbs.py +0 -0
  301. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/mcp_tools/__init__.py +0 -0
  302. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/mcp_tools/capture.py +0 -0
  303. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/mcp_tools/core_read.py +0 -0
  304. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/mcp_tools/housekeeping.py +0 -0
  305. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/mcp_tools/retrieval.py +0 -0
  306. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/mcp_tools/supersession.py +0 -0
  307. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/mcp_verbs.py +0 -0
  308. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/multihop.py +0 -0
  309. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/notes.py +0 -0
  310. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/originals_resolve.py +0 -0
  311. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/overlay.py +0 -0
  312. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/overlay_core.py +0 -0
  313. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/overlay_keywords.py +0 -0
  314. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/overlay_rules.py +0 -0
  315. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/pathkey.py +0 -0
  316. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/progress.py +0 -0
  317. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/projection.py +0 -0
  318. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/provenance.py +0 -0
  319. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/provision_mcp.py +0 -0
  320. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/provision_stage.py +0 -0
  321. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/provision_wire.py +0 -0
  322. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/querylog.py +0 -0
  323. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/querylog_digest.py +0 -0
  324. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/querylog_replay.py +0 -0
  325. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/querylog_status.py +0 -0
  326. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/read_log.py +0 -0
  327. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/reexec.py +0 -0
  328. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/remediation.py +0 -0
  329. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/remediation_answers.py +0 -0
  330. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/remediation_branches.py +0 -0
  331. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/remediation_converged.py +0 -0
  332. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/remediation_exceptions.py +0 -0
  333. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/remediation_extract_retry.py +0 -0
  334. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/remediation_folds.py +0 -0
  335. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/remediation_questions.py +0 -0
  336. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/remediation_routing.py +0 -0
  337. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/remediation_schema.py +0 -0
  338. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/retrieval_ops.py +0 -0
  339. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/retro.py +0 -0
  340. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/session_hook.py +0 -0
  341. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/session_hook_spec.py +0 -0
  342. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/snapshot.py +0 -0
  343. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/status_ops.py +0 -0
  344. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/supersede_declared.py +0 -0
  345. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/sweepdirs.py +0 -0
  346. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/update.py +0 -0
  347. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/update_channels_2.py +0 -0
  348. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/update_cos_guard.py +0 -0
  349. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/update_model.py +0 -0
  350. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/update_ops.py +0 -0
  351. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/update_plugins.py +0 -0
  352. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/update_state.py +0 -0
  353. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/update_venv.py +0 -0
  354. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/vectors.py +0 -0
  355. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/versionlink.py +0 -0
  356. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/versionlink_stages.py +0 -0
  357. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/vm_ceiling.py +0 -0
  358. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brainiac_cli.egg-info/dependency_links.txt +0 -0
  359. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brainiac_cli.egg-info/entry_points.txt +0 -0
  360. {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brainiac_cli.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: brainiac-cli
3
- Version: 0.20.44
3
+ Version: 0.20.46
4
4
  Summary: Brainiac — local any-LLM second-brain core engine + brain CLI (Markdown truth, derived SQLite index).
5
5
  License-Expression: Apache-2.0
6
6
  Project-URL: Homepage, https://github.com/Autopsias/brainiac
@@ -19,11 +19,12 @@ Requires-Dist: cryptography>=49
19
19
  Requires-Dist: PyYAML>=6
20
20
  Requires-Dist: regex>=2023.0.0
21
21
  Requires-Dist: keyring>=24; sys_platform == "win32"
22
- Requires-Dist: pypdf>=6.15.0
22
+ Requires-Dist: pypdf>=6.21.2
23
23
  Requires-Dist: python-docx>=1.1
24
24
  Requires-Dist: python-pptx>=0.6.23
25
25
  Requires-Dist: openpyxl>=3.1
26
- Requires-Dist: Pillow>=12.3.0
26
+ Requires-Dist: Pillow>=13.0.0; python_version >= "3.11"
27
+ Requires-Dist: Pillow>=12.3.0; python_version < "3.11"
27
28
  Provides-Extra: vec
28
29
  Requires-Dist: sqlite-vec>=0.1.6; extra == "vec"
29
30
  Provides-Extra: audit
@@ -43,11 +44,12 @@ Requires-Dist: PyYAML>=6; extra == "corporate"
43
44
  Provides-Extra: yaml
44
45
  Requires-Dist: PyYAML>=6; extra == "yaml"
45
46
  Provides-Extra: ingest
46
- Requires-Dist: pypdf>=6.15.0; extra == "ingest"
47
+ Requires-Dist: pypdf>=6.21.2; extra == "ingest"
47
48
  Requires-Dist: python-docx>=1.1; extra == "ingest"
48
49
  Requires-Dist: python-pptx>=0.6.23; extra == "ingest"
49
50
  Requires-Dist: openpyxl>=3.1; extra == "ingest"
50
- Requires-Dist: Pillow>=12.3.0; extra == "ingest"
51
+ Requires-Dist: Pillow>=13.0.0; python_version >= "3.11" and extra == "ingest"
52
+ Requires-Dist: Pillow>=12.3.0; python_version < "3.11" and extra == "ingest"
51
53
  Provides-Extra: dev
52
54
  Requires-Dist: pytest>=7; extra == "dev"
53
55
  Requires-Dist: build>=1; extra == "dev"
@@ -8,7 +8,7 @@ build-backend = "setuptools.build_meta"
8
8
  # check. The IMPORT package stays `brain` and the console script stays
9
9
  # `brain` — only the distribution/project name changes.
10
10
  name = "brainiac-cli"
11
- version = "0.20.44"
11
+ version = "0.20.46"
12
12
  description = "Brainiac — local any-LLM second-brain core engine + brain CLI (Markdown truth, derived SQLite index)."
13
13
  readme = "README.md"
14
14
  license = "Apache-2.0"
@@ -42,7 +42,7 @@ dependencies = [
42
42
  # because requirements.lock is CI/audit-only -- a plain
43
43
  # `pip install brainiac-cli` reads THIS constraint, and >=4.0 let the
44
44
  # vulnerable 6.14.2 survive an upgrade to 0.20.5 on a real host.
45
- "pypdf>=6.15.0",
45
+ "pypdf>=6.21.2",
46
46
  "python-docx>=1.1",
47
47
  "python-pptx>=0.6.23",
48
48
  "openpyxl>=3.1",
@@ -50,7 +50,10 @@ dependencies = [
50
50
  # binary is present — the pytesseract binding itself stays a fully
51
51
  # OPTIONAL, NOT-a-pip-dependency probe: OCR degrades gracefully absent
52
52
  # it, matching the ADR's "system binaries are never pip deps" rule).
53
- "Pillow>=12.3.0",
53
+ # >=13.0.0 fixes 37 advisories (one a FlashPix heap overflow); 13 needs
54
+ # Python 3.11+, and no fixed 12.x exists, so 3.10 keeps the old floor.
55
+ "Pillow>=13.0.0; python_version >= '3.11'",
56
+ "Pillow>=12.3.0; python_version < '3.11'",
54
57
  ]
55
58
 
56
59
  [project.optional-dependencies]
@@ -94,7 +97,7 @@ yaml = ["PyYAML>=6"]
94
97
  # message hints valid even though a default install already has these. Absent,
95
98
  # `brain ingest` degrades per-handler (reports which extensions are
96
99
  # unavailable) rather than crashing.
97
- ingest = ["pypdf>=6.15.0", "python-docx>=1.1", "python-pptx>=0.6.23", "openpyxl>=3.1", "Pillow>=12.3.0"]
100
+ ingest = ["pypdf>=6.21.2", "python-docx>=1.1", "python-pptx>=0.6.23", "openpyxl>=3.1", "Pillow>=13.0.0; python_version >= '3.11'", "Pillow>=12.3.0; python_version < '3.11'"]
98
101
  # pytest-xdist/-timeout back the documented parallel suite run (AGENTS.md §8,
99
102
  # "Running the test suite"): ~15 min sequential -> ~6 min at `-n 8`. Both are
100
103
  # pytest-dev org, actively maintained. Declared here so a fresh checkout can
@@ -296,3 +299,20 @@ python-platform = "linux"
296
299
  # `_assets` is the generated mirror of tools/ + scripts/ inside src/brain/.
297
300
  include = ["src", "tools", "eval", "scripts", "packaging"]
298
301
  exclude = ["**/_plans", "**/_archive", "**/_evidence", "**/_assets"]
302
+
303
+ [tool.uv]
304
+ # Wait 7 days before resolving a newly published version (a new release can be
305
+ # malicious or broken). shortcut: pypdf 6.21.2, filelock 4.2.4 and certifi
306
+ # 2026.10.23 are security fixes under 7 days old on 2026-10-24, so they are
307
+ # exempt; remove the exemptions after 2026-10-31.
308
+ exclude-newer = "7 days"
309
+ exclude-newer-package = { pypdf = false, filelock = false, certifi = false }
310
+ # fastembed (the optional `embed` extra, not installed by default) caps
311
+ # pillow<13 in every release up to 0.9.0, but pillow 13.0.0 is the first
312
+ # release fixing 37 advisories (one a FlashPix heap overflow). fastembed uses
313
+ # pillow only for IMAGE embeddings; this repo embeds text only.
314
+ # shortcut: drop this once a fastembed release allows pillow>=13.
315
+ override-dependencies = [
316
+ "pillow>=13.0.0; python_version >= '3.11'",
317
+ "pillow>=12.3.0; python_version < '3.11'",
318
+ ]
@@ -61,6 +61,21 @@ if [ -z "$BRAIN_BIN" ]; then
61
61
  exit 1
62
62
  fi
63
63
 
64
+ # YIELD TO THE NIGHTLY MAIL RUN (2026-10-19). Both do model work, and the
65
+ # machine runs at most $BRAIN_MODEL_SLOTS (2) model processes at once. On
66
+ # run374 an hourly maintain ran 02:06-03:04 across the run's grounding window
67
+ # and 58 of 72 sender lookups timed out (0 of 122 the night before). The night
68
+ # runs its own broker after its child exits, so skipping an hour loses
69
+ # nothing: the next hourly run catches up. A hung night also stops this job;
70
+ # the WAT-01 stale-feed alert names it after 2 days.
71
+ # ponytail: process-name match, a pid lock if the night ever runs under
72
+ # another name.
73
+ YIELD_TO="${BRAIN_BRIEF_YIELD_TO:-tools/cos_nightly.sh}"
74
+ if pgrep -f "$YIELD_TO" >/dev/null 2>&1; then
75
+ echo "=== brain-daily-brief / brain-nightly $(date -u +%Y-%m-%dT%H:%M:%SZ) === SKIP: the nightly mail run is running ($YIELD_TO); next hour catches up" >> "$LOG"
76
+ exit 0
77
+ fi
78
+
64
79
  # The RUN VALIDATOR needs the mutation toolchain, and the wheel does not carry
65
80
  # it (2026-08-22). `brain.cos_runverify.check_plan_binding` imports
66
81
  # `cos_mutate.load_frozen_plan` to re-hash a run's frozen plan — one definition
@@ -9,4 +9,4 @@ pip-installed host, importlib.metadata stays primary; brain/__init__.py reads
9
9
  this file only as the fallback before 0.0.0+unknown.
10
10
  """
11
11
 
12
- __version__ = "0.20.44"
12
+ __version__ = "0.20.46"
@@ -6,6 +6,7 @@ import os
6
6
  from datetime import date
7
7
  from pathlib import Path
8
8
  from typing import TYPE_CHECKING
9
+ from .atomic_temp import atomic_temp_path
9
10
 
10
11
  if TYPE_CHECKING: # annotation-only; a runtime import here would be a cycle
11
12
  from .audit_chain import AuditChain
@@ -32,7 +33,7 @@ def _write_dispositions(path: Path, payload: dict) -> None:
32
33
  turn every explained finding back into an unexplained one. `os.replace` is
33
34
  atomic within a filesystem, so a reader sees the old file or the new one.
34
35
  """
35
- tmp = path.with_name(path.name + ".tmp")
36
+ tmp = atomic_temp_path(path)
36
37
  tmp.write_text(json.dumps(payload, indent=2), encoding="utf-8")
37
38
  os.replace(tmp, path)
38
39
 
@@ -1,5 +1,6 @@
1
1
  """Maintenance state methods for BrainCore."""
2
2
  from __future__ import annotations
3
+ from ..atomic_temp import atomic_temp_path
3
4
 
4
5
  from ._shared import (
5
6
  Any,
@@ -130,7 +131,7 @@ class _MaintenanceStateMixin:
130
131
 
131
132
  path = config.maintain_state_path(self.vault)
132
133
  path.parent.mkdir(parents=True, exist_ok=True)
133
- tmp = path.with_suffix(path.suffix + ".tmp")
134
+ tmp = atomic_temp_path(path)
134
135
  tmp.write_text(_json.dumps(state, indent=2, sort_keys=True), encoding="utf-8")
135
136
  _os.replace(tmp, path)
136
137
  def _note_graphify_build_outcome(
@@ -50,18 +50,21 @@ ARCTIC_FULL_DIM = 768
50
50
  MRL_DIM = 256
51
51
 
52
52
 
53
+ # Measured 2026-10-19: 4 threads match the all-cores wall time at ~40% of the CPU.
54
+ _DEFAULT_THREADS = 4
55
+
56
+
53
57
  def _ort_threads() -> int | None:
54
58
  """Intra-op thread count for the ONNX session (S11 speed fix).
55
59
 
56
- Default: all physical cores (Apple Silicon has no SMT, so logical ==
57
- physical — on the M4 Pro that is 12). Saturating the batch dimension
58
- across cores is the biggest ORT knob for bulk embedding throughput.
60
+ Default: 4 threads (capped at the core count). More threads spin without
61
+ cutting wall time; raise it for bulk work.
59
62
  Override via ``$BRAIN_EMBED_THREADS``."""
60
63
  raw = os.environ.get("BRAIN_EMBED_THREADS")
61
64
  if raw and raw.strip().isdigit():
62
65
  return int(raw)
63
66
  try:
64
- return os.cpu_count() or None
67
+ return min(_DEFAULT_THREADS, os.cpu_count() or _DEFAULT_THREADS)
65
68
  except Exception:
66
69
  return None
67
70
 
@@ -8,6 +8,8 @@ import sys as sys
8
8
  from dataclasses import dataclass
9
9
  from typing import Sequence
10
10
 
11
+ from .model_slot import model_slot
12
+
11
13
  # --- Direct-ONNX models (DIST-01: eliminates fastembed) ---------------------
12
14
  # Each model owns its pooling, prefixes, dimension and exact file. Keeping the
13
15
  # contract here prevents a future model swap from inheriting another model's
@@ -255,9 +257,10 @@ class OnnxEmbedder:
255
257
  t = _ort_threads()
256
258
  if t:
257
259
  so.intra_op_num_threads = t
258
- self._sess = ort.InferenceSession(
259
- onnx_path, sess_options=so, providers=_ort_providers()
260
- )
260
+ with model_slot(): # loading peaks well above the weights' size
261
+ self._sess = ort.InferenceSession(
262
+ onnx_path, sess_options=so, providers=_ort_providers()
263
+ )
261
264
  self._in_names = [i.name for i in self._sess.get_inputs()]
262
265
  self._tok = Tokenizer.from_file(os.path.join(base, "tokenizer.json"))
263
266
  # Truncate to the engine's calibrated chunk window BEFORE padding.
@@ -387,7 +390,8 @@ class OnnxEmbedder:
387
390
  feed[nm] = am
388
391
  elif "token_type" in low:
389
392
  feed[nm] = np.zeros_like(ii)
390
- hidden = self._sess.run(None, feed)[0] # [batch, seq, dim] float32
393
+ with model_slot():
394
+ hidden = self._sess.run(None, feed)[0] # [batch, seq, dim] float32
391
395
  if self._pooling == "cls":
392
396
  pooled = hidden[:, 0, :]
393
397
  else:
@@ -60,6 +60,7 @@ from . import maintenance as _maintenance
60
60
  from . import maintenance_notify as _notify
61
61
  from . import remediation as _rem
62
62
  from . import remediation_exceptions as _rex
63
+ from .atomic_temp import atomic_temp_path
63
64
  from .exceptions_render import render_page # re-exported: the page's one
64
65
  # renderer, split out on 2026-08-22 when this file hit the 500-line bound.
65
66
 
@@ -287,7 +288,7 @@ def maybe_ping(vault: Path, keys: list[str], full_path: Path) -> str | None:
287
288
  if not str(out or "").startswith("skipped"): # a SKIP defers, never spends
288
289
  try:
289
290
  state_path.parent.mkdir(parents=True, exist_ok=True)
290
- tmp = state_path.with_name(state_path.name + ".tmp")
291
+ tmp = atomic_temp_path(state_path)
291
292
  tmp.write_text(json.dumps({"hash": current_hash}), encoding="utf-8")
292
293
  os.replace(tmp, state_path)
293
294
  except OSError:
@@ -364,6 +365,6 @@ def generate(core: Any, today: datetime.date | None = None) -> dict[str, Any]:
364
365
 
365
366
 
366
367
  def _atomic_write(path: Path, text: str) -> None:
367
- tmp = path.with_name(path.name + ".tmp")
368
+ tmp = atomic_temp_path(path)
368
369
  tmp.write_text(text, encoding="utf-8")
369
370
  os.replace(tmp, path)
@@ -78,6 +78,7 @@ from pathlib import Path
78
78
  from typing import Any
79
79
 
80
80
  from . import config as _config
81
+ from .atomic_temp import atomic_temp_path
81
82
 
82
83
  PINNED_FILENAME = "pinned-verify.json"
83
84
  MOUNT_HTML_FILENAME = "exceptions.html"
@@ -372,7 +373,7 @@ def stage_pin(vault: Path, workspace: Path | None = None) -> dict[str, Any]:
372
373
  data = {"vault_id": vid, "public_key_pem": pem}
373
374
  path = staging_root(vault, workspace) / PINNED_FILENAME
374
375
  path.parent.mkdir(parents=True, exist_ok=True)
375
- tmp = path.with_name(path.name + ".tmp")
376
+ tmp = atomic_temp_path(path)
376
377
  tmp.write_text(json.dumps(data, indent=1, sort_keys=True), encoding="utf-8")
377
378
  os.replace(tmp, path)
378
379
  return data
@@ -413,6 +414,6 @@ def publish_summary(vault: Path, workspace: Path | None = None) -> None:
413
414
  if not src.exists():
414
415
  continue
415
416
  dest = dest_dir / filename
416
- tmp = dest.with_name(dest.name + ".tmp")
417
+ tmp = atomic_temp_path(dest)
417
418
  tmp.write_bytes(src.read_bytes())
418
419
  os.replace(tmp, dest)
@@ -240,12 +240,12 @@ class IntakeFoldsMixin:
240
240
  from .. import cos # noqa: PLC0415
241
241
 
242
242
  ops = cos.run_ops_dir(self.vault)
243
- ledgers = sorted(ops.glob("_cos_ingestion_ledger_*.jsonl"),
243
+ ledgers = sorted(ops.glob(cos.LEDGER_GLOB),
244
244
  key=self._ledger_order)
245
245
  if not ledgers:
246
246
  return
247
247
  latest = ledgers[-1]
248
- run_id = latest.stem[len("_cos_ingestion_ledger_"):]
248
+ run_id = cos.ledger_run_id(latest)
249
249
  rows, seen = [], set()
250
250
  for line in latest.read_text(encoding="utf-8").splitlines():
251
251
  if not line.strip():
@@ -268,7 +268,7 @@ class IntakeFoldsMixin:
268
268
  """Sort key for `_cos_ingestion_ledger_<date>-run<N>.jsonl`: the date
269
269
  as text, then the run number as an INT. A missing or unparseable run
270
270
  number sorts first, which keeps a malformed name from winning."""
271
- stem = path.stem[len("_cos_ingestion_ledger_"):]
271
+ stem = path.stem[len("_cos_ingestion_ledger_"):] # no COS import: this runs unguarded
272
272
  date, _, tail = stem.partition("-run")
273
273
  return (date, int(tail) if tail.isdigit() else -1)
274
274
 
@@ -355,12 +355,12 @@ class IntakeFoldsMixin:
355
355
  from .. import cos # noqa: PLC0415
356
356
 
357
357
  ops = cos.run_ops_dir(self.vault)
358
- ledgers = sorted(ops.glob("_cos_ingestion_ledger_*.jsonl"),
358
+ ledgers = sorted(ops.glob(cos.LEDGER_GLOB),
359
359
  key=self._ledger_order)
360
360
  if not ledgers:
361
361
  return
362
362
  latest = ledgers[-1]
363
- run_id = latest.stem[len("_cos_ingestion_ledger_"):]
363
+ run_id = cos.ledger_run_id(latest)
364
364
  stuck, seen = {}, set()
365
365
  for line in latest.read_text(encoding="utf-8").splitlines():
366
366
  if not line.strip():
@@ -14,6 +14,7 @@ from . import classification, config, egress
14
14
  from . import graphify as graphify_model
15
15
  from .graph import build_graph
16
16
  from .progress import progress_note
17
+ from .atomic_temp import atomic_temp_path
17
18
 
18
19
 
19
20
  @dataclass(frozen=True)
@@ -186,7 +187,7 @@ def _publish_graph_artifact(
186
187
  day: dt.date,
187
188
  ) -> None:
188
189
  """Atomically publish validated graph and manifest files."""
189
- temporary_graph = paths.graph.with_suffix(paths.graph.suffix + ".tmp")
190
+ temporary_graph = atomic_temp_path(paths.graph)
190
191
  temporary_graph.write_text(
191
192
  json.dumps(artifact, indent=2, sort_keys=True), encoding="utf-8"
192
193
  )
@@ -197,7 +198,7 @@ def _publish_graph_artifact(
197
198
  "built_at": day.isoformat(),
198
199
  "notes": manifest,
199
200
  }
200
- temporary_manifest = paths.manifest.with_suffix(paths.manifest.suffix + ".tmp")
201
+ temporary_manifest = atomic_temp_path(paths.manifest)
201
202
  temporary_manifest.write_text(
202
203
  json.dumps(new_state, indent=2, sort_keys=True), encoding="utf-8"
203
204
  )
@@ -2,6 +2,7 @@
2
2
  from __future__ import annotations
3
3
 
4
4
  from ._shared import * # noqa: F401,F403
5
+ from ..model_slot import model_slot
5
6
 
6
7
 
7
8
  class _SearchMixin:
@@ -150,11 +151,14 @@ class _SearchMixin:
150
151
 
151
152
  Retired versions (``is_latest_version: false``) are hidden unless
152
153
  ``include_retired`` — see ``search_ranking.generate_candidates``."""
153
- return self._hybrid_search_impl(
154
- query, k=k, rrf_k=rrf_k, candidate_factor=candidate_factor,
155
- rerank=rerank, reranker=reranker, rerank_top=rerank_top,
156
- rerank_gate=rerank_gate, trace=None, include_retired=include_retired,
157
- )
154
+ # The slot is taken HERE, before the reranker's 30 s budget starts, so
155
+ # a queued search waits instead of silently losing its rerank.
156
+ with model_slot():
157
+ return self._hybrid_search_impl(
158
+ query, k=k, rrf_k=rrf_k, candidate_factor=candidate_factor,
159
+ rerank=rerank, reranker=reranker, rerank_top=rerank_top,
160
+ rerank_gate=rerank_gate, trace=None, include_retired=include_retired,
161
+ )
158
162
 
159
163
  def hybrid_search_with_trace(
160
164
  self,
@@ -187,11 +191,12 @@ class _SearchMixin:
187
191
  candidate_limit=n,
188
192
  result_limit=max(0, k),
189
193
  )
190
- hits = self._hybrid_search_impl(
191
- query, k=k, rrf_k=rrf_k, candidate_factor=candidate_factor,
192
- rerank=rerank, reranker=reranker, rerank_top=rerank_top,
193
- rerank_gate=rerank_gate, trace=trace, include_retired=include_retired,
194
- )
194
+ with model_slot(): # see hybrid_search
195
+ hits = self._hybrid_search_impl(
196
+ query, k=k, rrf_k=rrf_k, candidate_factor=candidate_factor,
197
+ rerank=rerank, reranker=reranker, rerank_top=rerank_top,
198
+ rerank_gate=rerank_gate, trace=trace, include_retired=include_retired,
199
+ )
195
200
  return hits, trace
196
201
 
197
202
  def _hybrid_search_impl(
@@ -49,6 +49,7 @@ from typing import TYPE_CHECKING, Any
49
49
 
50
50
  from .. import classification as CLS
51
51
  from .. import supersede_declared as SUP
52
+ from ..atomic_temp import atomic_temp_path
52
53
 
53
54
  if TYPE_CHECKING: # the stage record; a runtime import here would be a cycle
54
55
  from .pipeline_stages import ClaimRecord
@@ -423,7 +424,7 @@ def _store(vault: Any, pending: dict[str, dict[str, Any]]) -> None:
423
424
  path = journal_path(vault)
424
425
  path.parent.mkdir(parents=True, exist_ok=True)
425
426
  config.secure_file_permissions(path.parent, 0o700)
426
- tmp = path.with_name(path.name + ".tmp")
427
+ tmp = atomic_temp_path(path)
427
428
  tmp.write_text(json.dumps(pending, indent=2, sort_keys=True), encoding="utf-8")
428
429
  config.secure_file_permissions(tmp, 0o600)
429
430
  tmp.replace(path)
@@ -62,7 +62,10 @@ def page_fonts(page: Any) -> dict[str, Any]:
62
62
  """
63
63
  fonts: dict[str, Any] = {}
64
64
  try:
65
- from pypdf._font import Font
65
+ try:
66
+ from pypdf.generic._font import Font # pypdf >= 6.21
67
+ except ImportError: # coverage-audit: older pypdf keeps Font here
68
+ from pypdf._font import Font
66
69
 
67
70
  resources = page.get_inherited("/Resources", None)
68
71
  table = resources.get("/Font") if resources else None
@@ -10,6 +10,7 @@ import shutil
10
10
  import tempfile
11
11
  from pathlib import Path
12
12
  from typing import Any
13
+ from ..atomic_temp import atomic_temp_path
13
14
 
14
15
 
15
16
  def manifest_path(vault: Path) -> Path:
@@ -32,7 +33,7 @@ def load_manifest(vault: Path) -> dict[str, str]:
32
33
  def save_manifest(vault: Path, manifest: dict[str, str]) -> None:
33
34
  path = manifest_path(vault)
34
35
  path.parent.mkdir(parents=True, exist_ok=True)
35
- tmp = path.with_suffix(".tmp")
36
+ tmp = atomic_temp_path(path)
36
37
  tmp.write_text(json.dumps(manifest, indent=2, sort_keys=True), encoding="utf-8")
37
38
  os.replace(tmp, path)
38
39
 
@@ -57,7 +58,7 @@ def load_failures(vault: Path) -> dict[str, int]:
57
58
  def save_failures(vault: Path, failures: dict[str, int]) -> None:
58
59
  path = failures_path(vault)
59
60
  path.parent.mkdir(parents=True, exist_ok=True)
60
- tmp = path.with_suffix(".tmp")
61
+ tmp = atomic_temp_path(path)
61
62
  tmp.write_text(json.dumps(failures, indent=2, sort_keys=True), encoding="utf-8")
62
63
  os.replace(tmp, path)
63
64
 
@@ -88,7 +89,11 @@ def read_nofollow(path: Path, *, max_bytes: int | None = None) -> bytes:
88
89
 
89
90
  if path.is_symlink():
90
91
  raise ReadRefused(f"{path.name} is a symlink — refused")
91
- flags = os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0) | getattr(os, "O_BINARY", 0)
92
+ # O_NONBLOCK: a FIFO planted at the name would otherwise block the open
93
+ # until a writer arrives, before the S_ISREG check below can refuse it
94
+ # (same guard as ``cos._io``). It changes nothing for a regular file.
95
+ flags = (os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0) | getattr(os, "O_BINARY", 0)
96
+ | getattr(os, "O_NONBLOCK", 0))
92
97
  try:
93
98
  fd = os.open(path, flags)
94
99
  except OSError as exc:
@@ -27,6 +27,7 @@ import os
27
27
  import re
28
28
  from pathlib import Path
29
29
  from typing import Any
30
+ from .atomic_temp import atomic_temp_path
30
31
 
31
32
  SCHEMA = "brain-interview/1"
32
33
  STATE_RELPATH = ".brain/interview/state.json"
@@ -103,7 +104,7 @@ def read_state(vault: Any) -> dict[str, Any]:
103
104
  def write_state(vault: Any, state: dict[str, Any]) -> Path:
104
105
  p = state_path(vault)
105
106
  p.parent.mkdir(parents=True, exist_ok=True)
106
- tmp = p.with_suffix(".json.tmp")
107
+ tmp = atomic_temp_path(p)
107
108
  tmp.write_text(json.dumps(state, ensure_ascii=False, sort_keys=True,
108
109
  indent=1) + "\n", encoding="utf-8")
109
110
  os.chmod(tmp, 0o600)
@@ -9,6 +9,7 @@ from pathlib import Path
9
9
  from typing import Any
10
10
 
11
11
  from .invariant_shared import SAMPLE_CAP
12
+ from .atomic_temp import atomic_temp_path
12
13
 
13
14
  # ---------------------------------------------------------------------------
14
15
  # G12 — the exclusion set, defined ONCE here and imported by every consumer
@@ -437,7 +438,7 @@ def write_link_lane(conn: Any, vault: Path, *, limit: int | None = None) -> dict
437
438
  except (OSError, ValueError):
438
439
  previous = None
439
440
  payload.update(lane_consumption(previous, all_ids, payload["generated"]))
440
- tmp = out.with_suffix(".json.tmp")
441
+ tmp = atomic_temp_path(out)
441
442
  tmp.write_text(json.dumps(payload, indent=1), encoding="utf-8")
442
443
  tmp.replace(out)
443
444
  return {"path": str(out), "written": len(payload["candidates"]),
@@ -13,6 +13,7 @@ import os
13
13
  import platform
14
14
  import subprocess
15
15
  import hashlib
16
+ from .atomic_temp import atomic_temp_path
16
17
 
17
18
 
18
19
  # ---------------------------------------------------------------------------
@@ -138,7 +139,7 @@ def record_current_findings(
138
139
  }
139
140
  try:
140
141
  path.parent.mkdir(parents=True, exist_ok=True)
141
- tmp = path.with_name(path.name + ".tmp")
142
+ tmp = atomic_temp_path(path)
142
143
  tmp.write_text(json.dumps(payload, indent=1), encoding="utf-8")
143
144
  os.replace(tmp, path)
144
145
  except OSError:
@@ -0,0 +1,110 @@
1
+ """Machine-wide cap on concurrent model work (load + inference).
2
+
3
+ WHY. One `brain search` peaks at ~4 GB for a few seconds: loading the fp32
4
+ reranker alone measures 3.0 GB, a 15-pair rerank adds ~1 GB (measured
5
+ 2026-10-18, M4 Pro). On 2026-10-18 three subagents ran ten searches at once on
6
+ a 24 GB Mac; macOS recorded ten Python processes at 4.1-6.8 GB each and the
7
+ machine froze until a forced reset. Jetsam stopped only small system daemons,
8
+ never the searches. Nothing in brain bounded how many ran together.
9
+
10
+ WHAT. `model_slot()` waits for one of `$BRAIN_MODEL_SLOTS` (default 2) slot
11
+ files under `$BRAIN_MODEL_SLOT_DIR` (default ~/.cache/brain-model-slots) and
12
+ holds an exclusive `flock` on it for the duration of the block. The kernel drops
13
+ the lock when the process dies, so a crash never leaks a slot.
14
+
15
+ RE-ENTRANT PER PROCESS. A search holds a slot and its reranker worker thread
16
+ runs inside it; that work must not wait for a second slot. So the first
17
+ acquire in a process takes a slot, and nested or concurrent acquires in the
18
+ same process ride on it until the depth returns to zero. The cap is therefore
19
+ "processes doing model work", which is what costs memory.
20
+
21
+ FAIL OPEN. If the slot directory cannot be created or opened (read-only HOME,
22
+ Windows without fcntl), the work runs unguarded: a guard must never stop a
23
+ search from working. ponytail: no Windows lock — add msvcrt when a Windows
24
+ host runs parallel searches.
25
+ """
26
+ from __future__ import annotations
27
+
28
+ import contextlib
29
+ import os
30
+ import sys
31
+ import threading
32
+ import time
33
+ from pathlib import Path
34
+ from typing import Iterator
35
+
36
+ try:
37
+ import fcntl
38
+ except ImportError: # Windows
39
+ fcntl = None # type: ignore[assignment]
40
+
41
+ _POLL_SECONDS = 0.2
42
+ _gate = threading.Lock() # a second thread waits here while the first acquires
43
+ _state = threading.Lock()
44
+ _depth = 0
45
+ _fd: int | None = None
46
+
47
+
48
+ def slot_count() -> int:
49
+ raw = os.environ.get("BRAIN_MODEL_SLOTS", "").strip()
50
+ return int(raw) if raw.isdigit() and int(raw) > 0 else 2
51
+
52
+
53
+ def slot_dir() -> Path:
54
+ raw = os.environ.get("BRAIN_MODEL_SLOT_DIR")
55
+ return Path(raw) if raw else Path.home() / ".cache" / "brain-model-slots"
56
+
57
+
58
+ def _try_take(paths: list[Path]) -> int | None:
59
+ for path in paths:
60
+ fd = os.open(path, os.O_RDWR | os.O_CREAT, 0o644)
61
+ try:
62
+ fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
63
+ except OSError:
64
+ os.close(fd)
65
+ continue
66
+ return fd
67
+ return None
68
+
69
+
70
+ def _acquire() -> int | None:
71
+ if fcntl is None:
72
+ return None
73
+ try:
74
+ root = slot_dir()
75
+ root.mkdir(parents=True, exist_ok=True)
76
+ paths = [root / f"slot-{i}.lock" for i in range(slot_count())]
77
+ fd = _try_take(paths)
78
+ if fd is not None:
79
+ return fd
80
+ print(f"brain: waiting for a model slot ({len(paths)} in use on this "
81
+ "machine; $BRAIN_MODEL_SLOTS sets the cap)", file=sys.stderr, flush=True)
82
+ while fd is None:
83
+ time.sleep(_POLL_SECONDS)
84
+ fd = _try_take(paths)
85
+ return fd
86
+ except OSError as exc:
87
+ print(f"brain: model slot unavailable ({exc}); running unguarded",
88
+ file=sys.stderr, flush=True)
89
+ return None
90
+
91
+
92
+ @contextlib.contextmanager
93
+ def model_slot() -> Iterator[None]:
94
+ global _depth, _fd
95
+ with _gate:
96
+ with _state:
97
+ first = _depth == 0
98
+ fd = _acquire() if first else None # a KeyboardInterrupt here holds nothing
99
+ with _state:
100
+ _depth += 1
101
+ if first:
102
+ _fd = fd
103
+ try:
104
+ yield
105
+ finally:
106
+ with _state:
107
+ _depth -= 1
108
+ if _depth == 0 and _fd is not None:
109
+ os.close(_fd) # closing the only fd on the file drops the flock
110
+ _fd = None
@@ -64,6 +64,7 @@ from typing import Any, NamedTuple
64
64
 
65
65
  from . import frontmatter
66
66
  from . import overlay as ov
67
+ from .atomic_temp import atomic_temp_path
67
68
 
68
69
  #: Sibling of ``keywords/`` — deliberately NOT inside it. See the module
69
70
  #: docstring: ``keywords/`` drives ingest classification and this does not.
@@ -223,7 +224,7 @@ def _publish(path: Path, text: str) -> None:
223
224
  ``*.md`` glob in :func:`brain.overlay._ring_files` cannot pick a half-written
224
225
  temp file up as a ring file of its own.
225
226
  """
226
- tmp = path.with_name(f".{path.name}.tmp")
227
+ tmp = atomic_temp_path(path)
227
228
  try:
228
229
  tmp.write_text(text, encoding="utf-8")
229
230
  os.replace(tmp, path)
@@ -47,7 +47,7 @@ from datetime import datetime, timezone
47
47
  from pathlib import Path
48
48
  from typing import Any, Callable, Optional
49
49
 
50
- from . import workspaces
50
+ from . import atomic_temp, workspaces
51
51
  # Wire 5 moved to `provision_mcp` (its own module: the Desktop config is a
52
52
  # third-party file with a third party still writing to it). Re-exported here
53
53
  # because this module is the one every caller already imports.
@@ -94,7 +94,7 @@ def write_request(vault: str | os.PathLike[str], *, role: str) -> dict[str, Any]
94
94
  for zone in ("brain", "raw", "inbox"):
95
95
  (vault_p / zone).mkdir(exist_ok=True)
96
96
  payload = {"version": 1, "requested_at": _now_iso(), "role": role}
97
- tmp = marker.with_suffix(".tmp")
97
+ tmp = atomic_temp.atomic_temp_path(marker)
98
98
  tmp.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8")
99
99
  os.replace(tmp, marker)
100
100
  return {"action": "provision-request", "status": "staged",