brainlayer 1.5.83__tar.gz → 1.5.85__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.
- {brainlayer-1.5.83 → brainlayer-1.5.85}/PKG-INFO +1 -1
- {brainlayer-1.5.83 → brainlayer-1.5.85}/pyproject.toml +1 -1
- {brainlayer-1.5.83 → brainlayer-1.5.85}/scripts/hotlane_brainbar_daemon.py +287 -24
- {brainlayer-1.5.83 → brainlayer-1.5.85}/scripts/launchd/com.brainlayer.hotlane-brainbar.plist +1 -1
- {brainlayer-1.5.83 → brainlayer-1.5.85}/server.json +2 -2
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/__init__.py +1 -1
- brainlayer-1.5.85/src/brainlayer/_build.py +1 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/backup_daily.py +70 -6
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/badge_state.py +34 -13
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/dedupe.py +113 -15
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/doctor.py +110 -5
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/embeddings.py +20 -7
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/health_check.py +32 -5
- brainlayer-1.5.85/src/brainlayer/health_facts.py +540 -0
- brainlayer-1.5.85/src/brainlayer/health_verdict.py +742 -0
- brainlayer-1.5.85/src/brainlayer/hotlane_budget.py +91 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/launchd_consolidation.py +29 -3
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/maintenance.py +149 -35
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/migrate.py +2 -2
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/pipeline/semantic_style.py +6 -4
- brainlayer-1.5.85/src/brainlayer/policy_reconcile.py +84 -0
- brainlayer-1.5.85/src/brainlayer/readonly_snapshot.py +217 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/reembed_backfill.py +2 -2
- brainlayer-1.5.85/src/brainlayer/supervisor_child.py +76 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/supervisor_jobs.py +2 -1
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/vector_store.py +48 -6
- brainlayer-1.5.83/src/brainlayer/_build.py +0 -1
- brainlayer-1.5.83/src/brainlayer/supervisor_child.py +0 -38
- {brainlayer-1.5.83 → brainlayer-1.5.85}/.gitignore +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/LICENSE +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/README.md +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/scripts/launchd/backup-daily.sh +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/scripts/launchd/brainlayer-env-run.sh +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/scripts/launchd/brainlayer.env.example +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/scripts/launchd/com.brainlayer.backup-daily.plist +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/scripts/launchd/com.brainlayer.daemon.plist +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/scripts/launchd/com.brainlayer.decay.plist +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/scripts/launchd/com.brainlayer.drain.plist +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/scripts/launchd/com.brainlayer.health-check.plist +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/scripts/launchd/com.brainlayer.index.plist +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/scripts/launchd/com.brainlayer.jsonl-backup.plist +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/scripts/launchd/com.brainlayer.log-cap.plist +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/scripts/launchd/com.brainlayer.maintenance-nightly.plist +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/scripts/launchd/com.brainlayer.maintenance-weekly.plist +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/scripts/launchd/com.brainlayer.maintenance.plist +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/scripts/launchd/com.brainlayer.observability.plist +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/scripts/launchd/com.brainlayer.p0-counter.plist +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/scripts/launchd/com.brainlayer.repair-fts.plist +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/scripts/launchd/com.brainlayer.t3-ingest.plist +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/scripts/launchd/com.brainlayer.throughput-watchdog.plist +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/scripts/launchd/com.brainlayer.tier0-watchdog.plist +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/scripts/launchd/com.brainlayer.wal-checkpoint.plist +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/scripts/launchd/com.brainlayer.watch.plist +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/scripts/launchd/com.etanhey.brainlayer-fleet-watchdog.plist +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/scripts/launchd/fleet-watchdog.sh +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/scripts/launchd/install.sh +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/scripts/launchd/jsonl-backup.sh +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/scripts/launchd/throughput-watchdog.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/scripts/release-verify-signatures.sh +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/scripts/tier0-watchdog.sh +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/__main__.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/_helpers.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/agent_profiles.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/agent_provenance.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/alarm.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/archive_collapse.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/archive_lifecycle.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/backup_retention_invariant.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/bitemporal.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/brainbar_activation.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/brainbar_auto_activation.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/brainbar_hybrid_helper.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/brainbar_identity.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/cachedelete_purge.swift +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/calibrate.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/chunk_origin.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/chunk_origin_backfill.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/chunk_origin_wipe.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/chunk_write.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/classify.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/claude_paths.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/cli/__init__.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/cli/wizard.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/cli_jobs.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/cli_new.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/cloud_backfill.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/clustering.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/component_jobs.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/config.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/consolidation_plan.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/content_class.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/correction_judge.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/data/agents_registry.yaml +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/data/sandbox_seeds/skill-eval-baseline.json +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/db_shrink.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/decay.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/decay_backfill.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/decay_job.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/dedupe_merge.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/deploy_drift.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/disk_space.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/drain.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/drain_liveness.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/drive_credentials.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/engine.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/enrichment_replay.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/eval/__init__.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/eval/abcde_enrich_runner.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/eval/abcde_variants.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/eval/abcde_variants.yaml +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/eval/benchmark.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/eval/enrichment_gold.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/eval/enrichment_graders.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/eval/enrichment_judge.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/eval/enrichment_llm_judge.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/eval/enrichment_quality_benchmark.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/eval/experiment_store.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/eval/fixtures/README.md +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/eval/labeling/enrichment-labeler.html +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/eval/phoenix_gate/__init__.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/eval/phoenix_gate/__main__.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/eval/phoenix_gate/baseline_store.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/eval/phoenix_gate/cli.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/eval/phoenix_gate/models.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/eval/phoenix_gate/phoenix_client.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/eval/phoenix_gate/regression_gate.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/eval/phoenix_gate/triggers.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/eval/pilot_artifact_report.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/fallback_replay.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/git_learning.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/hook_python.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/hooks/__init__.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/hooks/indexer.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/import_sweep.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/index_admission.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/index_budget.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/index_completeness.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/index_new.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/index_watchdog.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/ingest/__init__.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/ingest/codex.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/ingest/t3.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/ingest_denylist.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/ingest_guard.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/isolation_proof.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/job_alerts.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/job_lifecycle_health.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/jobs.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/jsonl_backup.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/kg/__init__.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/kg_cleanup.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/kg_judge.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/kg_promotion.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/kg_repo.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/kg_review_session.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/kg_session_finish.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/kg_session_harvest.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/launchd_install.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/launchd_primitive.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/launchd_revert.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/lexical_defense.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/lexical_defense_dictionary.json +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/linkedin_import.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/log_cap.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/maintenance_calendar.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/maintenance_control.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/mcp/__init__.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/mcp/_format.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/mcp/_shared.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/mcp/enrich_handler.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/mcp/entity_handler.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/mcp/palette.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/mcp/search_handler.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/mcp/store_handler.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/mcp/tags_handler.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/mcp_socket_config.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/mcp_stdio_bridge.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/memory_types.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/migration_custody.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/migration_journal.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/migration_runtime.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/notification_policy.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/observability_backup.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/observability_surface.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/observability_transport.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/p0_longitudinal_count.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/parent_death.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/paths.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/pause.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/phonetic.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/pipeline/__init__.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/pipeline/agent_enrichment.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/pipeline/analyze_communication.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/pipeline/batch_extraction.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/pipeline/brain_graph.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/pipeline/chat_tags.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/pipeline/chunk.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/pipeline/classify.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/pipeline/cloud_scrub.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/pipeline/cluster_sampling.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/pipeline/code_intelligence.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/pipeline/correction_detection.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/pipeline/correction_mining.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/pipeline/digest.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/pipeline/enrichment_prompts.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/pipeline/enrichment_results.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/pipeline/enrichment_tiers.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/pipeline/entity_extraction.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/pipeline/entity_resolution.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/pipeline/extract.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/pipeline/extract_claude_desktop.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/pipeline/extract_corrections.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/pipeline/extract_markdown.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/pipeline/extract_whatsapp.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/pipeline/frustration_mining.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/pipeline/git_overlay.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/pipeline/kg_extraction.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/pipeline/kg_extraction_groq.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/pipeline/longitudinal_analyzer.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/pipeline/obsidian_export.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/pipeline/operation_grouping.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/pipeline/plan_linking.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/pipeline/rate_limiter.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/pipeline/relation_backfill.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/pipeline/relation_verification.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/pipeline/sanitize.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/pipeline/secret_scrub.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/pipeline/sentiment.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/pipeline/session_history.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/pipeline/style_embed.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/pipeline/style_index.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/pipeline/tag_entity_promotion.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/pipeline/temporal_chains.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/pipeline/time_batcher.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/pipeline/unified_timeline.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/pipeline/write_queue.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/provenance.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/provenance_autosupersede.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/provenance_integration.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/queue_io.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/queue_merge.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/retired_services.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/runtime_store.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/sandbox_db.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/scoping.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/scrub_at_rest.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/search_fanout.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/search_profile.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/search_repo.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/service_supervisor.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/session_repo.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/setup.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/socket_hygiene.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/source_class_migration.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/spotlight.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/storage.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/store.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/supervisor_adapters.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/supervisor_diagnostics.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/supervisor_guard.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/system_prompt_guard.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/t3_linkage_alarm.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/t3_provenance.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/t3_sources.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/tag_normalization.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/taxonomy.json +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/telemetry.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/timestamp_iso.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/types.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/wal_checkpoint.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/watcher.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/watcher_bridge.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/writer_policy.py +0 -0
- {brainlayer-1.5.83 → brainlayer-1.5.85}/src/brainlayer/writer_telemetry.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: brainlayer
|
|
3
|
-
Version: 1.5.
|
|
3
|
+
Version: 1.5.85
|
|
4
4
|
Summary: Persistent local memory for AI agents — 16 BrainBar MCP tools, semantic search, knowledge graph, SQLite
|
|
5
5
|
Project-URL: Homepage, https://brainlayer.etanheyman.com
|
|
6
6
|
Project-URL: Repository, https://github.com/EtanHey/brainlayer
|
|
@@ -13,8 +13,11 @@ import argparse
|
|
|
13
13
|
import inspect
|
|
14
14
|
import json
|
|
15
15
|
import logging
|
|
16
|
+
import math
|
|
16
17
|
import os
|
|
17
18
|
import signal
|
|
19
|
+
import subprocess
|
|
20
|
+
import sys
|
|
18
21
|
import time
|
|
19
22
|
from collections import OrderedDict
|
|
20
23
|
from pathlib import Path
|
|
@@ -25,9 +28,10 @@ import sqlite_vec
|
|
|
25
28
|
|
|
26
29
|
from brainlayer._helpers import serialize_f32
|
|
27
30
|
from brainlayer.embeddings import get_embedding_model
|
|
31
|
+
from brainlayer.hotlane_budget import HotlaneCpuSampler
|
|
28
32
|
from brainlayer.paths import get_db_path
|
|
29
33
|
from brainlayer.store import embed_hot_chunk, embed_pending_chunks
|
|
30
|
-
from brainlayer.vector_store import VectorStore
|
|
34
|
+
from brainlayer.vector_store import VectorStore, skip_connection_hooks, temporary_write_busy_timeout_ms
|
|
31
35
|
from brainlayer.writer_telemetry import IngestMetrics, start_writer_span
|
|
32
36
|
|
|
33
37
|
LOGGER = logging.getLogger("brainlayer.hotlane_brainbar")
|
|
@@ -40,6 +44,16 @@ MAX_BACKLOG_BATCH = 16
|
|
|
40
44
|
DEFAULT_HOTLANE_WRITE_BUSY_TIMEOUT_MS = 1000
|
|
41
45
|
MAX_APSW_BUSY_TIMEOUT_MS = 2_147_483_647
|
|
42
46
|
VECTOR_WRITE_YIELD_SECONDS = 0.005
|
|
47
|
+
# S17 (2026-11-02): a busy writer slot must never cost an embedded batch. The write retries
|
|
48
|
+
# inside the cycle (each try waits up to the hotlane busy timeout at BEGIN IMMEDIATE), then
|
|
49
|
+
# whatever is still unwritten is carried to the next cycle instead of being discarded.
|
|
50
|
+
HOTLANE_WRITE_ATTEMPTS = 3
|
|
51
|
+
HOTLANE_WRITE_RETRY_BACKOFF_SECONDS = 0.25
|
|
52
|
+
MAX_CARRIED_VECTORS = 64
|
|
53
|
+
# Hooks the hotlane writer never runs in its Connection() constructor. `connection_optimize`
|
|
54
|
+
# runs PRAGMA optimize (a write lock) on every open; `connection_busy_timeout` would reset the
|
|
55
|
+
# constructor's busy timeout to APSW's 100 ms. connection_wal and the rest still run.
|
|
56
|
+
HOTLANE_SKIPPED_CONNECTION_HOOKS = ("connection_optimize", "connection_busy_timeout")
|
|
43
57
|
|
|
44
58
|
# Incident 2026-08-24: the loop slept a fixed `interval` (1.0s) whether or not a cycle
|
|
45
59
|
# did any work, so an idle hotlane kept probing once per second forever. Idle cycles now
|
|
@@ -144,6 +158,8 @@ class CycleResult(NamedTuple):
|
|
|
144
158
|
enrich_skipped: int = 0
|
|
145
159
|
enrich_failed: int = 0
|
|
146
160
|
enrich_daily_cap_reached: bool = False
|
|
161
|
+
deferred: int = 0
|
|
162
|
+
backlog_encoded: int = 0
|
|
147
163
|
|
|
148
164
|
|
|
149
165
|
class EmbedCandidate(NamedTuple):
|
|
@@ -151,6 +167,59 @@ class EmbedCandidate(NamedTuple):
|
|
|
151
167
|
content: str
|
|
152
168
|
|
|
153
169
|
|
|
170
|
+
def _sysctl(name: str) -> str:
|
|
171
|
+
return subprocess.check_output(["/usr/sbin/sysctl", "-n", name], text=True, timeout=5).strip()
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _performance_cores() -> int:
|
|
175
|
+
if sys.platform == "darwin":
|
|
176
|
+
return max(1, int(_sysctl("hw.perflevel0.physicalcpu")))
|
|
177
|
+
return os.cpu_count() or 1
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _backlog_max_load() -> float:
|
|
181
|
+
value = float(os.environ.get("BRAINLAYER_HOTLANE_BACKLOG_MAX_LOAD", "") or _performance_cores())
|
|
182
|
+
if not math.isfinite(value) or value < 0:
|
|
183
|
+
raise ValueError("BRAINLAYER_HOTLANE_BACKLOG_MAX_LOAD must be finite and nonnegative")
|
|
184
|
+
return value
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def setup_cpu_budget(db_path: Path) -> dict:
|
|
188
|
+
"""CPU-only priority: never request Darwin background or a disk I/O policy."""
|
|
189
|
+
policy = {"policy": "not applicable", "platform": sys.platform}
|
|
190
|
+
if sys.platform == "darwin":
|
|
191
|
+
os.setpriority(os.PRIO_PROCESS, 0, 20)
|
|
192
|
+
policy.update(policy="nice", nice=os.getpriority(os.PRIO_PROCESS, 0))
|
|
193
|
+
default_threads = 1 if sys.platform == "darwin" and "Apple M1" in _sysctl("machdep.cpu.brand_string") else 2
|
|
194
|
+
threads = int(os.environ.get("BRAINLAYER_HOTLANE_THREADS", str(default_threads)))
|
|
195
|
+
if threads < 1:
|
|
196
|
+
raise ValueError("BRAINLAYER_HOTLANE_THREADS must be positive")
|
|
197
|
+
for name in ("OMP_NUM_THREADS", "MKL_NUM_THREADS", "VECLIB_MAXIMUM_THREADS", "OPENBLAS_NUM_THREADS"):
|
|
198
|
+
os.environ[name] = str(threads)
|
|
199
|
+
# embeddings.py imports sentence_transformers/torch only when the model first encodes.
|
|
200
|
+
import torch
|
|
201
|
+
|
|
202
|
+
torch.set_num_threads(threads)
|
|
203
|
+
policy.update(threads=torch.get_num_threads(), backlog_max_load=_backlog_max_load(), pid=os.getpid())
|
|
204
|
+
receipt = db_path.parent / "hotlane-budget.json"
|
|
205
|
+
receipt.parent.mkdir(parents=True, exist_ok=True)
|
|
206
|
+
temporary = receipt.with_suffix(".tmp")
|
|
207
|
+
temporary.write_text(json.dumps(policy, sort_keys=True) + "\n")
|
|
208
|
+
temporary.replace(receipt)
|
|
209
|
+
LOGGER.info("hotlane CPU budget %s receipt=%s", json.dumps(policy, sort_keys=True), receipt)
|
|
210
|
+
return policy
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _backlog_yield_reason(db_path: Path, max_load: float) -> str:
|
|
214
|
+
if (db_path.parent / "hotlane.pause").exists():
|
|
215
|
+
return "pause marker"
|
|
216
|
+
try:
|
|
217
|
+
load = os.getloadavg()[0]
|
|
218
|
+
except OSError:
|
|
219
|
+
return "load unavailable"
|
|
220
|
+
return "high load" if load > max_load else ""
|
|
221
|
+
|
|
222
|
+
|
|
154
223
|
class PendingCandidateScanState:
|
|
155
224
|
def __init__(self) -> None:
|
|
156
225
|
self.active = False
|
|
@@ -169,6 +238,70 @@ class EmbeddedVector(NamedTuple):
|
|
|
169
238
|
embedding: list[float]
|
|
170
239
|
|
|
171
240
|
|
|
241
|
+
class HotlaneVectorWriteDeferred(apsw.BusyError):
|
|
242
|
+
"""A busy write with progress for cycle carry-over.
|
|
243
|
+
|
|
244
|
+
Remains a BusyError so callers using the writer's existing lock-error contract
|
|
245
|
+
can retry after contention; the split cycle also retains ``remaining``.
|
|
246
|
+
"""
|
|
247
|
+
|
|
248
|
+
def __init__(self, *, written: int, remaining: list[EmbeddedVector], attempts: int, cause: BaseException):
|
|
249
|
+
super().__init__(f"{type(cause).__name__}: {cause} (after {attempts} attempts, {len(remaining)} unwritten)")
|
|
250
|
+
self.written = written
|
|
251
|
+
self.remaining = remaining
|
|
252
|
+
self.attempts = attempts
|
|
253
|
+
self.cause = cause
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
class VectorCarryover:
|
|
257
|
+
"""Computed vectors whose write was deferred by a busy writer slot (S17).
|
|
258
|
+
|
|
259
|
+
Held in memory for the next cycle so a lock never costs the embedding work. The
|
|
260
|
+
writer re-checks each chunk's content and vector state at write time, so a stale
|
|
261
|
+
entry (content edited, or vectorized meanwhile) is skipped, never written wrongly.
|
|
262
|
+
Bounded: past ``capacity`` the oldest entries are dropped, loudly and counted. A
|
|
263
|
+
dropped vector loses only compute; its chunk stays unvectored and is re-embedded.
|
|
264
|
+
"""
|
|
265
|
+
|
|
266
|
+
def __init__(self, capacity: int = MAX_CARRIED_VECTORS) -> None:
|
|
267
|
+
self.capacity = capacity
|
|
268
|
+
self._vectors: OrderedDict[str, EmbeddedVector] = OrderedDict()
|
|
269
|
+
self.carried_total = 0
|
|
270
|
+
self.dropped_total = 0
|
|
271
|
+
|
|
272
|
+
def __len__(self) -> int:
|
|
273
|
+
return len(self._vectors)
|
|
274
|
+
|
|
275
|
+
def put(self, vectors: list[EmbeddedVector]) -> int:
|
|
276
|
+
for vector in vectors:
|
|
277
|
+
self._vectors.pop(vector.chunk_id, None)
|
|
278
|
+
self._vectors[vector.chunk_id] = vector
|
|
279
|
+
self.carried_total += len(vectors)
|
|
280
|
+
dropped = 0
|
|
281
|
+
while len(self._vectors) > self.capacity:
|
|
282
|
+
self._vectors.popitem(last=False)
|
|
283
|
+
dropped += 1
|
|
284
|
+
if dropped:
|
|
285
|
+
self.dropped_total += dropped
|
|
286
|
+
LOGGER.warning(
|
|
287
|
+
"hotlane carry-over full; dropped %d oldest computed vectors (chunks stay unvectored and "
|
|
288
|
+
"will be re-embedded) capacity=%d dropped_total=%d",
|
|
289
|
+
dropped,
|
|
290
|
+
self.capacity,
|
|
291
|
+
self.dropped_total,
|
|
292
|
+
)
|
|
293
|
+
return dropped
|
|
294
|
+
|
|
295
|
+
def peek(self) -> list[EmbeddedVector]:
|
|
296
|
+
return list(self._vectors.values())
|
|
297
|
+
|
|
298
|
+
def clear(self) -> None:
|
|
299
|
+
self._vectors.clear()
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
VECTOR_CARRYOVER = VectorCarryover()
|
|
303
|
+
|
|
304
|
+
|
|
172
305
|
def _candidates_from_scanned_rows(
|
|
173
306
|
rows: list[tuple], *, limit: int, exclude_ids: set[str] | None = None
|
|
174
307
|
) -> tuple[list[EmbedCandidate], int | None, int | None, bool]:
|
|
@@ -546,11 +679,20 @@ def _hotlane_write_busy_timeout_ms() -> int:
|
|
|
546
679
|
|
|
547
680
|
|
|
548
681
|
def _open_hotlane_write_connection(db_path: Path) -> apsw.Connection:
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
682
|
+
timeout_ms = _hotlane_write_busy_timeout_ms()
|
|
683
|
+
# S17: the constructor runs the connection hooks. Without this, PRAGMA optimize took the
|
|
684
|
+
# write lock on every cycle at APSW's 100 ms timeout and threw away the embedded batch
|
|
685
|
+
# (1,264 of 1,430 logged cycle failures). The hotlane timeout covers the hooks that do run.
|
|
686
|
+
with skip_connection_hooks(*HOTLANE_SKIPPED_CONNECTION_HOOKS), temporary_write_busy_timeout_ms(timeout_ms):
|
|
687
|
+
conn = apsw.Connection(str(db_path), flags=apsw.SQLITE_OPEN_READWRITE)
|
|
688
|
+
try:
|
|
689
|
+
conn.setbusytimeout(timeout_ms)
|
|
690
|
+
conn.enableloadextension(True)
|
|
691
|
+
conn.loadextension(sqlite_vec.loadable_path())
|
|
692
|
+
conn.enableloadextension(False)
|
|
693
|
+
except BaseException:
|
|
694
|
+
conn.close()
|
|
695
|
+
raise
|
|
554
696
|
return conn
|
|
555
697
|
|
|
556
698
|
|
|
@@ -592,9 +734,56 @@ def _upsert_chunk_vector_raw(
|
|
|
592
734
|
)
|
|
593
735
|
|
|
594
736
|
|
|
737
|
+
class _WriteProgress:
|
|
738
|
+
def __init__(self) -> None:
|
|
739
|
+
self.written = 0
|
|
740
|
+
self.processed = 0
|
|
741
|
+
|
|
742
|
+
|
|
595
743
|
def _write_embedded_vectors(store_or_path: VectorStore | Path | str, vectors: list[EmbeddedVector]) -> int:
|
|
744
|
+
"""Write vectors one transaction each; retry busy/locked failures, never drop the batch.
|
|
745
|
+
|
|
746
|
+
Each vector is its own transaction, so a lock error part-way leaves earlier vectors
|
|
747
|
+
committed; the retry resumes at the first unprocessed vector. After
|
|
748
|
+
``HOTLANE_WRITE_ATTEMPTS`` busy failures it raises ``HotlaneVectorWriteDeferred``
|
|
749
|
+
carrying what is still unwritten, for the caller to keep for the next cycle.
|
|
750
|
+
"""
|
|
751
|
+
remaining = list(vectors)
|
|
752
|
+
written = 0
|
|
753
|
+
for attempt in range(1, HOTLANE_WRITE_ATTEMPTS + 1):
|
|
754
|
+
if not remaining:
|
|
755
|
+
break
|
|
756
|
+
progress = _WriteProgress()
|
|
757
|
+
try:
|
|
758
|
+
_write_embedded_vectors_once(store_or_path, remaining, progress)
|
|
759
|
+
except (apsw.BusyError, apsw.LockedError) as exc:
|
|
760
|
+
written += progress.written
|
|
761
|
+
remaining = remaining[progress.processed :]
|
|
762
|
+
if attempt == HOTLANE_WRITE_ATTEMPTS:
|
|
763
|
+
raise HotlaneVectorWriteDeferred(
|
|
764
|
+
written=written, remaining=remaining, attempts=attempt, cause=exc
|
|
765
|
+
) from exc
|
|
766
|
+
LOGGER.info(
|
|
767
|
+
"hotlane vector write busy; retrying attempt=%d/%d unwritten=%d error_type=%s",
|
|
768
|
+
attempt + 1,
|
|
769
|
+
HOTLANE_WRITE_ATTEMPTS,
|
|
770
|
+
len(remaining),
|
|
771
|
+
type(exc).__name__,
|
|
772
|
+
)
|
|
773
|
+
_sleep(HOTLANE_WRITE_RETRY_BACKOFF_SECONDS * (2 ** (attempt - 1)))
|
|
774
|
+
continue
|
|
775
|
+
written += progress.written
|
|
776
|
+
remaining = []
|
|
777
|
+
return written
|
|
778
|
+
|
|
779
|
+
|
|
780
|
+
def _write_embedded_vectors_once(
|
|
781
|
+
store_or_path: VectorStore | Path | str,
|
|
782
|
+
vectors: list[EmbeddedVector],
|
|
783
|
+
progress: _WriteProgress,
|
|
784
|
+
) -> None:
|
|
596
785
|
if not vectors:
|
|
597
|
-
return
|
|
786
|
+
return
|
|
598
787
|
|
|
599
788
|
conn: apsw.Connection | None = None
|
|
600
789
|
db_path: Path | None
|
|
@@ -666,6 +855,8 @@ def _write_embedded_vectors(store_or_path: VectorStore | Path | str, vectors: li
|
|
|
666
855
|
metrics.committed([{"queued_at": metadata.get("ingest_queued_at"), "metadata": metadata}])
|
|
667
856
|
metrics.finish("commit")
|
|
668
857
|
count += rows_touched
|
|
858
|
+
progress.written = count
|
|
859
|
+
progress.processed = vector_index + 1
|
|
669
860
|
telemetry_span.finish("commit", rows_touched=rows_touched)
|
|
670
861
|
if vector_index < len(vectors) - 1:
|
|
671
862
|
_sleep(VECTOR_WRITE_YIELD_SECONDS)
|
|
@@ -676,6 +867,7 @@ def _write_embedded_vectors(store_or_path: VectorStore | Path | str, vectors: li
|
|
|
676
867
|
telemetry_span.finish("rollback", error=f"{type(exc).__name__}: {exc}")
|
|
677
868
|
if isinstance(exc, (apsw.BusyError, apsw.LockedError)):
|
|
678
869
|
raise
|
|
870
|
+
progress.processed = vector_index + 1
|
|
679
871
|
LOGGER.error(
|
|
680
872
|
"hotlane vector write skipped chunk_id=%s error_type=%s error=%s",
|
|
681
873
|
chunk_id,
|
|
@@ -691,7 +883,6 @@ def _write_embedded_vectors(store_or_path: VectorStore | Path | str, vectors: li
|
|
|
691
883
|
from brainlayer.search_repo import clear_hybrid_search_cache
|
|
692
884
|
|
|
693
885
|
clear_hybrid_search_cache(db_path)
|
|
694
|
-
return count
|
|
695
886
|
|
|
696
887
|
|
|
697
888
|
def _open_store(
|
|
@@ -751,9 +942,14 @@ def _run_split_cycle(
|
|
|
751
942
|
candidate_rows_fn: Callable[..., list[EmbedCandidate]] = _candidate_chunk_rows,
|
|
752
943
|
pending_rows_fn: Callable[..., list[EmbedCandidate]] = _pending_chunk_rows_with_resume,
|
|
753
944
|
write_vectors_fn: Callable[..., int] = _write_embedded_vectors,
|
|
945
|
+
carryover: VectorCarryover | None = None,
|
|
754
946
|
) -> CycleResult:
|
|
947
|
+
carryover = VECTOR_CARRYOVER if carryover is None else carryover
|
|
755
948
|
metrics = IngestMetrics("hotlane", "hotlane")
|
|
756
949
|
embedded = 0
|
|
950
|
+
# Peek, not take: a scan or encode failure later in this cycle must not lose them.
|
|
951
|
+
carried = carryover.peek()
|
|
952
|
+
carried_by_id = {vector.chunk_id: vector for vector in carried}
|
|
757
953
|
hot_rows: list[EmbedCandidate] = []
|
|
758
954
|
pending_rows: list[EmbedCandidate] = []
|
|
759
955
|
pending_scan_state = PENDING_CANDIDATE_SCAN_STATE if pending_rows_fn is _pending_chunk_rows_with_resume else None
|
|
@@ -778,15 +974,28 @@ def _run_split_cycle(
|
|
|
778
974
|
|
|
779
975
|
seen_hot_ids = {candidate.chunk_id for candidate in hot_rows}
|
|
780
976
|
pending_rows = [candidate for candidate in pending_rows if candidate.chunk_id not in seen_hot_ids]
|
|
977
|
+
|
|
978
|
+
def needs_embedding(candidate: EmbedCandidate) -> bool:
|
|
979
|
+
# A carried vector for the same content is reused, not recomputed.
|
|
980
|
+
carried_vector = carried_by_id.get(candidate.chunk_id)
|
|
981
|
+
return carried_vector is None or carried_vector.content != candidate.content
|
|
982
|
+
|
|
983
|
+
hot_rows = [candidate for candidate in hot_rows if needs_embedding(candidate)]
|
|
984
|
+
pending_rows = [candidate for candidate in pending_rows if needs_embedding(candidate)]
|
|
781
985
|
vectors = metrics.run("encode", _embed_candidates, hot_rows, embed_fn=embed_fn, embed_batch_fn=embed_batch_fn)
|
|
782
|
-
|
|
783
|
-
|
|
986
|
+
backlog_vectors = metrics.run(
|
|
987
|
+
"encode", _embed_candidates, pending_rows, embed_fn=embed_fn, embed_batch_fn=embed_batch_fn
|
|
784
988
|
)
|
|
989
|
+
vectors.extend(backlog_vectors)
|
|
990
|
+
fresh_ids = {vector.chunk_id for vector in vectors}
|
|
991
|
+
vectors = [vector for vector in carried if vector.chunk_id not in fresh_ids] + vectors
|
|
785
992
|
if vectors:
|
|
786
993
|
try:
|
|
787
994
|
embedded += metrics.run("write", write_vectors_fn, db_path, vectors)
|
|
995
|
+
carryover.clear()
|
|
788
996
|
except Exception as exc:
|
|
789
|
-
|
|
997
|
+
deferred = exc if isinstance(exc, HotlaneVectorWriteDeferred) else None
|
|
998
|
+
is_lock_error = deferred is not None or isinstance(exc, (apsw.BusyError, apsw.LockedError))
|
|
790
999
|
if is_lock_error and pending_scan_checkpoint is not None and pending_scan_state is not None:
|
|
791
1000
|
# Earlier one-row commits stay indexed; retry only rows still missing vectors.
|
|
792
1001
|
pending_scan_state.active, pending_scan_state.after_created_at, pending_scan_state.after_rowid = (
|
|
@@ -798,12 +1007,33 @@ def _run_split_cycle(
|
|
|
798
1007
|
[candidate.chunk_id for candidate in pending_rows],
|
|
799
1008
|
type(exc).__name__,
|
|
800
1009
|
)
|
|
801
|
-
|
|
802
|
-
|
|
1010
|
+
if not is_lock_error:
|
|
1011
|
+
metrics.finish("error")
|
|
1012
|
+
raise
|
|
1013
|
+
# S17: a busy writer slot never costs the embedded batch. A writer that does not
|
|
1014
|
+
# report progress gets all of its vectors carried; its writes are idempotent
|
|
1015
|
+
# because each one re-checks that the chunk still lacks a vector.
|
|
1016
|
+
unwritten = deferred.remaining if deferred is not None else vectors
|
|
1017
|
+
embedded += deferred.written if deferred is not None else 0
|
|
1018
|
+
carryover.clear()
|
|
1019
|
+
carryover.put(unwritten)
|
|
1020
|
+
LOGGER.warning(
|
|
1021
|
+
"hotlane vector write deferred: writer slot busy; carrying %d computed vectors to the next "
|
|
1022
|
+
"cycle written=%d carried_now=%d carried_total=%d dropped_total=%d error=%s",
|
|
1023
|
+
len(unwritten),
|
|
1024
|
+
embedded,
|
|
1025
|
+
len(carryover),
|
|
1026
|
+
carryover.carried_total,
|
|
1027
|
+
carryover.dropped_total,
|
|
1028
|
+
exc,
|
|
1029
|
+
)
|
|
1030
|
+
metrics.finish("deferred")
|
|
1031
|
+
_warn_retired_enrichment(enrich_limit, enrich_since_hours, enrich_fn)
|
|
1032
|
+
return CycleResult(embedded=embedded, deferred=len(unwritten), backlog_encoded=len(backlog_vectors))
|
|
803
1033
|
|
|
804
1034
|
_warn_retired_enrichment(enrich_limit, enrich_since_hours, enrich_fn)
|
|
805
1035
|
metrics.finish()
|
|
806
|
-
return CycleResult(embedded=embedded)
|
|
1036
|
+
return CycleResult(embedded=embedded, backlog_encoded=len(backlog_vectors))
|
|
807
1037
|
|
|
808
1038
|
|
|
809
1039
|
def _warn_retired_enrichment(limit: int, since_hours: int, callback: object) -> None:
|
|
@@ -891,6 +1121,7 @@ def run(
|
|
|
891
1121
|
queue_dir: Path | None = None,
|
|
892
1122
|
queue_depth_fn: Callable[[Path], int] = _queue_depth,
|
|
893
1123
|
high_priority_queue_depth_fn: Callable[[Path], int] = _high_priority_queue_depth,
|
|
1124
|
+
cpu_sampler: HotlaneCpuSampler | None = None,
|
|
894
1125
|
) -> None:
|
|
895
1126
|
model = _create_embedding_model(model_factory, device=embedding_device)
|
|
896
1127
|
embed_batch_fn = getattr(model, "embed_texts", None)
|
|
@@ -907,10 +1138,16 @@ def run(
|
|
|
907
1138
|
queue_dir_was_explicit = queue_dir is not None
|
|
908
1139
|
queue_dir = queue_dir or _default_queue_dir()
|
|
909
1140
|
last_backlog = time_fn() - backlog_interval
|
|
1141
|
+
last_backlog_duty = last_backlog + backlog_interval
|
|
1142
|
+
max_backlog_wait = float(os.environ.get("BRAINLAYER_HOTLANE_BACKLOG_MAX_WAIT_SECONDS", "300"))
|
|
1143
|
+
if not math.isfinite(max_backlog_wait) or max_backlog_wait <= 0:
|
|
1144
|
+
raise ValueError("BRAINLAYER_HOTLANE_BACKLOG_MAX_WAIT_SECONDS must be finite and positive")
|
|
910
1145
|
_warn_retired_enrichment(enrich_limit, enrich_since_hours, None)
|
|
911
1146
|
if enrich_limit > 0:
|
|
912
1147
|
LOGGER.warning("Ignoring retired hotlane enrichment interval=%s", enrich_interval)
|
|
913
1148
|
queue_backpressure_active = False
|
|
1149
|
+
backlog_max_load = _backlog_max_load()
|
|
1150
|
+
backlog_yield_reason = ""
|
|
914
1151
|
backlog_slice_logged = False
|
|
915
1152
|
hot_candidate_scanner = HotCandidateScanner()
|
|
916
1153
|
cycles = 0
|
|
@@ -927,11 +1164,23 @@ def run(
|
|
|
927
1164
|
queue_has_high_priority_backlog = queue_has_backlog and high_priority_queue_depth_fn(queue_dir) > 0
|
|
928
1165
|
cycle_backlog_batch = backlog_batch if backlog_batch > 0 and now - last_backlog >= backlog_interval else 0
|
|
929
1166
|
cycle_recent_limit = recent_limit
|
|
1167
|
+
reason = _backlog_yield_reason(db_path, backlog_max_load) if backlog_batch else ""
|
|
1168
|
+
if reason == "high load" and now - last_backlog_duty >= max_backlog_wait:
|
|
1169
|
+
# Reserve the existing backlog lane; never lift store-queue backpressure.
|
|
1170
|
+
reason = ""
|
|
1171
|
+
cycle_backlog_batch = backlog_batch
|
|
1172
|
+
if reason != backlog_yield_reason:
|
|
1173
|
+
LOGGER.info("backlog embedding %s", f"yielding: {reason}" if reason else "resumed")
|
|
1174
|
+
backlog_yield_reason = reason
|
|
1175
|
+
if reason:
|
|
1176
|
+
cycle_backlog_batch = 0
|
|
930
1177
|
if queue_has_high_priority_backlog:
|
|
931
1178
|
if not queue_backpressure_active:
|
|
932
1179
|
LOGGER.info("durable high-priority queue has backlog; suppressing hot embedding")
|
|
933
1180
|
queue_backpressure_active = True
|
|
934
1181
|
if cycle_backlog_batch <= 0:
|
|
1182
|
+
if cpu_sampler is not None:
|
|
1183
|
+
cpu_sampler.observe("store queue")
|
|
935
1184
|
cycles += 1
|
|
936
1185
|
# Backpressure is not idleness: hold the configured cadence so the
|
|
937
1186
|
# reserved backlog slice resumes on time.
|
|
@@ -950,6 +1199,8 @@ def run(
|
|
|
950
1199
|
backlog_slice_logged = False
|
|
951
1200
|
if cycle_backlog_batch > 0:
|
|
952
1201
|
last_backlog = now
|
|
1202
|
+
last_backlog_duty = now
|
|
1203
|
+
cycle_start = cpu_sampler.begin_cycle() if cpu_sampler is not None else None
|
|
953
1204
|
if cycle_fn is run_cycle:
|
|
954
1205
|
result = _run_split_cycle(
|
|
955
1206
|
db_path=db_path,
|
|
@@ -980,10 +1231,15 @@ def run(
|
|
|
980
1231
|
# nothing (the hot slice is deliberately suppressed to yield to the drain),
|
|
981
1232
|
# so it must not be treated as idle -- backing off there would slow recovery.
|
|
982
1233
|
did_work = bool(result.embedded) or queue_has_backlog
|
|
1234
|
+
if cpu_sampler is not None:
|
|
1235
|
+
state = reason or ("running" if result.backlog_encoded else "waiting")
|
|
1236
|
+
cpu_sampler.observe(state, result.backlog_encoded, cycle_start=cycle_start)
|
|
983
1237
|
if result.embedded:
|
|
984
1238
|
LOGGER.info("embedded=%d", result.embedded)
|
|
985
1239
|
except Exception:
|
|
986
1240
|
LOGGER.exception("hotlane adapter cycle failed")
|
|
1241
|
+
if cpu_sampler is not None:
|
|
1242
|
+
cpu_sampler.observe("error")
|
|
987
1243
|
cycles += 1
|
|
988
1244
|
# A raised cycle is a RETRY, not an idle cycle (PR #735 review, macroscope):
|
|
989
1245
|
# it already slept its own backoff here, and letting it fall through to the
|
|
@@ -1021,16 +1277,23 @@ def main() -> None:
|
|
|
1021
1277
|
LOGGER.warning("Hotlane enrichment options are retired and ignored; continuing local embedding")
|
|
1022
1278
|
signal.signal(signal.SIGTERM, _stop)
|
|
1023
1279
|
signal.signal(signal.SIGINT, _stop)
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1280
|
+
setup_cpu_budget(args.db)
|
|
1281
|
+
sampler = HotlaneCpuSampler(args.db)
|
|
1282
|
+
sampler.start()
|
|
1283
|
+
try:
|
|
1284
|
+
run(
|
|
1285
|
+
db_path=args.db,
|
|
1286
|
+
interval=max(args.interval, 0.25),
|
|
1287
|
+
recent_limit=max(args.recent_limit, 1),
|
|
1288
|
+
backlog_interval=max(args.backlog_interval, 1.0),
|
|
1289
|
+
backlog_batch=min(max(args.backlog_batch, 0), MAX_BACKLOG_BATCH),
|
|
1290
|
+
enrich_interval=0.0,
|
|
1291
|
+
enrich_limit=0,
|
|
1292
|
+
enrich_since_hours=0,
|
|
1293
|
+
cpu_sampler=sampler,
|
|
1294
|
+
)
|
|
1295
|
+
finally:
|
|
1296
|
+
sampler.close()
|
|
1034
1297
|
|
|
1035
1298
|
|
|
1036
1299
|
if __name__ == "__main__":
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "io.github.etanhey/brainlayer",
|
|
4
4
|
"title": "BrainLayer",
|
|
5
5
|
"description": "Persistent local memory for AI agents \u2014 semantic search, knowledge graph, and 16 BrainBar MCP tools with ToolAnnotations over SQLite.",
|
|
6
|
-
"version": "1.5.
|
|
6
|
+
"version": "1.5.85",
|
|
7
7
|
"websiteUrl": "https://brainlayer.etanheyman.com",
|
|
8
8
|
"repository": {
|
|
9
9
|
"url": "https://github.com/EtanHey/brainlayer",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"registryType": "pypi",
|
|
15
15
|
"registryBaseUrl": "https://pypi.org",
|
|
16
16
|
"identifier": "brainlayer",
|
|
17
|
-
"version": "1.5.
|
|
17
|
+
"version": "1.5.85",
|
|
18
18
|
"runtimeHint": "pipx",
|
|
19
19
|
"transport": {
|
|
20
20
|
"type": "stdio"
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
__version__ = "1.5.
|
|
5
|
+
__version__ = "1.5.85"
|
|
6
6
|
# Git sha the distributed package was built from; stamped by the release build, None in a source tree.
|
|
7
7
|
# scripts/sprint_gate.py reads it in "keg" mode to prove the served package is the code under test.
|
|
8
8
|
# No runtime `str | None` annotation: launchd/install.sh imports this under /usr/bin/python3 (3.9).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
BUILD_SHA = "48d9df608550988faa3c8a67a505f3c9751f9adc"
|