ltcai 11.5.2 → 11.6.0
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.
- package/README.md +91 -148
- package/bin/ltcai.js +234 -24
- package/docs/CHANGELOG.md +75 -0
- package/docs/COMMUNITY_AND_PLUGINS.md +1 -1
- package/docs/DEVELOPMENT.md +8 -6
- package/docs/ENTERPRISE.md +2 -1
- package/docs/MULTI_AGENT_RUNTIME.md +12 -5
- package/docs/ONBOARDING.md +1 -1
- package/docs/OPERATIONS.md +6 -3
- package/docs/REALTIME_COLLABORATION.md +1 -1
- package/docs/TRUST_MODEL.md +1 -1
- package/docs/WHY_LATTICE.md +1 -1
- package/docs/WORKFLOW_DESIGNER.md +3 -2
- package/docs/kg-schema.md +1 -1
- package/docs/v11.6.0_ONE_DOOR_PLAN.md +170 -0
- package/lattice_brain/__init__.py +42 -79
- package/lattice_brain/graph/__init__.py +9 -24
- package/lattice_brain/graph/_kg_common/__init__.py +13 -24
- package/lattice_brain/ingestion/__init__.py +19 -58
- package/lattice_brain/ingestion/pipeline.py +20 -398
- package/lattice_brain/multimodal/__init__.py +7 -18
- package/lattice_brain/multimodal/images.py +6 -264
- package/lattice_brain/multimodal/video.py +8 -247
- package/lattice_brain/runtime/__init__.py +8 -79
- package/lattice_brain/runtime/hooks.py +22 -584
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/agent_worker_seam.py +7 -80
- package/latticeai/api/health.py +6 -20
- package/latticeai/api/local_files.py +16 -613
- package/latticeai/api/models.py +12 -87
- package/latticeai/api/search.py +17 -255
- package/latticeai/api/tools.py +69 -750
- package/latticeai/api/voice_capture.py +8 -70
- package/latticeai/api/worker_compute.py +842 -0
- package/latticeai/api/worker_seams.py +216 -0
- package/latticeai/app_factory.py +23 -232
- package/latticeai/cli/entrypoint.py +36 -249
- package/latticeai/core/agent_permission.py +16 -91
- package/latticeai/core/messages.py +89 -531
- package/latticeai/runtime/access_runtime.py +0 -14
- package/latticeai/runtime/bootstrap.py +10 -21
- package/latticeai/runtime/brain_runtime.py +35 -43
- package/latticeai/runtime/build_phases/__init__.py +21 -37
- package/latticeai/runtime/build_phases/features.py +99 -389
- package/latticeai/runtime/build_phases/foundation.py +82 -392
- package/latticeai/runtime/build_phases/web.py +91 -408
- package/latticeai/runtime/build_phases/worker_profile.py +244 -0
- package/latticeai/runtime/lifespan_runtime.py +7 -16
- package/latticeai/runtime/platform_services_runtime.py +1 -15
- package/latticeai/runtime/runtime_context.py +13 -130
- package/latticeai/runtime/security_runtime.py +10 -103
- package/latticeai/services/architecture_readiness.py +82 -43
- package/latticeai/services/model_runtime/__init__.py +2 -11
- package/latticeai/services/model_runtime/service.py +4 -28
- package/latticeai/services/p_reinforce.py +10 -261
- package/latticeai/services/product_readiness.py +31 -38
- package/latticeai/services/search_service.py +37 -795
- package/latticeai/services/tool_dispatch.py +30 -386
- package/latticeai/services/voice_capture.py +13 -107
- package/latticeai/tools/__init__.py +22 -49
- package/latticeai/tools/commands.py +0 -163
- package/latticeai/tools/computer.py +0 -39
- package/latticeai/tools/documents.py +1 -134
- package/latticeai/tools/filesystem.py +1 -247
- package/latticeai/tools/knowledge.py +1 -52
- package/latticeai/tools/local_files.py +0 -20
- package/latticeai/worker_app.py +75 -0
- package/package.json +2 -3
- package/requirements.txt +0 -5
- package/scripts/agent_eval.py +16 -28
- package/scripts/brain_quality_eval.py +20 -183
- package/scripts/bump_version.py +5 -5
- package/scripts/check_current_release_docs.mjs +7 -4
- package/scripts/check_openapi_drift.mjs +10 -0
- package/scripts/check_server_i18n.mjs +2 -18
- package/scripts/compose_openapi.py +377 -0
- package/scripts/export_openapi.py +32 -4
- package/scripts/gen_messages_catalog_fixture.py +302 -0
- package/scripts/gen_openapi_fragments.py +365 -0
- package/scripts/gen_redact_fixture.py +196 -0
- package/scripts/gen_worker_allowlist_fixture.py +115 -0
- package/scripts/generate_agent_parity_fixtures.py +33 -14
- package/scripts/openapi_route_families.json +2161 -0
- package/scripts/release_screen_claims.json +21 -0
- package/scripts/run_integration_tests.mjs +134 -29
- package/scripts/run_sidecar_e2e.mjs +104 -11
- package/scripts/wheel_smoke.py +32 -22
- package/src-tauri/Cargo.lock +375 -9
- package/src-tauri/Cargo.toml +1 -1
- package/src-tauri/src/backend.rs +151 -45
- package/src-tauri/src/main.rs +18 -18
- package/src-tauri/src/topology.rs +58 -88
- package/src-tauri/tauri.conf.json +1 -2
- package/static/app/asset-manifest.json +41 -41
- package/static/app/assets/{Act-DcQizkl1.js → Act-CToZnOHz.js} +1 -1
- package/static/app/assets/{AdminConsole-cf4npybT.js → AdminConsole-D5kbrBu8.js} +1 -1
- package/static/app/assets/{Brain-3VCSHFcn.js → Brain-C6zCdv4S.js} +1 -1
- package/static/app/assets/{BrainHome-Qm8eaztx.js → BrainHome-D4n3EDUt.js} +1 -1
- package/static/app/assets/{BrainSignals-DS9BtKOW.js → BrainSignals-CzeZ-vQ9.js} +1 -1
- package/static/app/assets/{Capture-DiQ219jW.js → Capture-DPcyTczq.js} +1 -1
- package/static/app/assets/{Chronicle-BGvuAchH.js → Chronicle-D3Nx-oWS.js} +1 -1
- package/static/app/assets/{CommandPalette-Bqhm0Urn.js → CommandPalette-DPPvpp28.js} +1 -1
- package/static/app/assets/{Library-BV6NnF0a.js → Library-Co2qTu3I.js} +1 -1
- package/static/app/assets/{LivingBrain-GzenJchP.js → LivingBrain-Bsmzwy8_.js} +1 -1
- package/static/app/assets/{ProductFlow-DEP6-vML.js → ProductFlow-CBLdmesT.js} +1 -1
- package/static/app/assets/{ReviewCard-CNZ7XjWG.js → ReviewCard-CfR6DLs7.js} +1 -1
- package/static/app/assets/{System-CieofHQa.js → System-Ce-P2YL6.js} +1 -1
- package/static/app/assets/arrow-left-DD5jFGHV.js +1 -0
- package/static/app/assets/{bot-B_K1Tdmw.js → bot-C5-XIww2.js} +1 -1
- package/static/app/assets/{brain-DWyaV1L1.js → brain-CFRJUvdX.js} +1 -1
- package/static/app/assets/{button-aTn4s84A.js → button-DI1KcQP-.js} +1 -1
- package/static/app/assets/circle-check-CY20XMCh.js +1 -0
- package/static/app/assets/{circle-pause-xKgeGXkT.js → circle-pause-C8HHyood.js} +1 -1
- package/static/app/assets/{circle-play-DkT6tYPX.js → circle-play-Cufc235E.js} +1 -1
- package/static/app/assets/{cpu-85xYObUC.js → cpu-DW2_JVsv.js} +1 -1
- package/static/app/assets/{download-B5Fm7YXo.js → download-gbdKTBb8.js} +1 -1
- package/static/app/assets/{folder-open-kk2Xa52u.js → folder-open-C9ydyX33.js} +1 -1
- package/static/app/assets/{hard-drive-DkA3zBW_.js → hard-drive-BxNMzBM3.js} +1 -1
- package/static/app/assets/index-BovSWRmQ.css +2 -0
- package/static/app/assets/{index-BMPdTmlY.js → index-q9XyDY7A.js} +3 -3
- package/static/app/assets/{input-B0nRf2jO.js → input-GLD94Y_K.js} +1 -1
- package/static/app/assets/{link-2-Dwb4gnTc.js → link-2-B4Y0gz1S.js} +1 -1
- package/static/app/assets/{permissionCopy-CQDUBrOZ.js → permissionCopy-CxrV0fvU.js} +1 -1
- package/static/app/assets/{primitives-SNp0LRJz.js → primitives-2ANdAsu2.js} +1 -1
- package/static/app/assets/search-DTxtjQ07.js +1 -0
- package/static/app/assets/{share-2-BsrxFglO.js → share-2-DxArfiuI.js} +1 -1
- package/static/app/assets/{shield-alert-5BStfp2_.js → shield-alert-CgftuFFs.js} +1 -1
- package/static/app/assets/{textarea-Cg8IUA-k.js → textarea-BHVBAoCr.js} +1 -1
- package/static/app/assets/{useFocusTrap-CYKvE46M.js → useFocusTrap-Dzq2j_OG.js} +1 -1
- package/static/app/assets/{useMutation-CSn9t1op.js → useMutation-BcqfMLNq.js} +1 -1
- package/static/app/assets/{useQuery-CY2OI2uy.js → useQuery-DBzLKCOv.js} +1 -1
- package/static/app/assets/{utils-Ddol2RWD.js → utils-B-Aah1bO.js} +1 -1
- package/static/app/assets/{workspace-BqDwOz_p.js → workspace-B6fFhoko.js} +1 -1
- package/static/app/index.html +4 -4
- package/static/sw.js +1 -1
- package/lattice_brain/archive.py +0 -522
- package/lattice_brain/context.py +0 -325
- package/lattice_brain/conversations.py +0 -382
- package/lattice_brain/core.py +0 -82
- package/lattice_brain/graph/_kg_contract.py +0 -249
- package/lattice_brain/graph/curator.py +0 -676
- package/lattice_brain/graph/discovery.py +0 -595
- package/lattice_brain/graph/discovery_index/__init__.py +0 -35
- package/lattice_brain/graph/discovery_index/cleanup.py +0 -182
- package/lattice_brain/graph/discovery_index/extract.py +0 -137
- package/lattice_brain/graph/discovery_index/scan.py +0 -411
- package/lattice_brain/graph/discovery_index/upsert.py +0 -495
- package/lattice_brain/graph/documents.py +0 -380
- package/lattice_brain/graph/fusion.py +0 -395
- package/lattice_brain/graph/identity.py +0 -175
- package/lattice_brain/graph/image_vectors.py +0 -230
- package/lattice_brain/graph/ingest.py +0 -829
- package/lattice_brain/graph/network.py +0 -205
- package/lattice_brain/graph/proactive.py +0 -724
- package/lattice_brain/graph/projection/__init__.py +0 -42
- package/lattice_brain/graph/projection/curation.py +0 -500
- package/lattice_brain/graph/projection/v2_schema.py +0 -518
- package/lattice_brain/graph/provenance.py +0 -524
- package/lattice_brain/graph/rerank.py +0 -163
- package/lattice_brain/graph/retrieval/__init__.py +0 -54
- package/lattice_brain/graph/retrieval/context.py +0 -197
- package/lattice_brain/graph/retrieval/graph_view.py +0 -319
- package/lattice_brain/graph/retrieval/hybrid.py +0 -488
- package/lattice_brain/graph/retrieval/maintenance.py +0 -121
- package/lattice_brain/graph/retrieval/signals.py +0 -95
- package/lattice_brain/graph/retrieval_docgen.py +0 -253
- package/lattice_brain/graph/retrieval_policy.py +0 -180
- package/lattice_brain/graph/retrieval_reads.py +0 -769
- package/lattice_brain/graph/retrieval_vector/__init__.py +0 -42
- package/lattice_brain/graph/retrieval_vector/fingerprint.py +0 -97
- package/lattice_brain/graph/retrieval_vector/indexing.py +0 -347
- package/lattice_brain/graph/retrieval_vector/search.py +0 -560
- package/lattice_brain/graph/retrieval_vector/status.py +0 -374
- package/lattice_brain/graph/schema.py +0 -792
- package/lattice_brain/graph/store.py +0 -268
- package/lattice_brain/graph/vector_index/__init__.py +0 -85
- package/lattice_brain/graph/vector_index/base.py +0 -167
- package/lattice_brain/graph/vector_index/brute_force.py +0 -110
- package/lattice_brain/graph/vector_index/hnsw.py +0 -290
- package/lattice_brain/graph/vector_index/jobs.py +0 -287
- package/lattice_brain/graph/vector_index/quantized.py +0 -148
- package/lattice_brain/graph/vector_index/selector.py +0 -161
- package/lattice_brain/graph/write_master.py +0 -308
- package/lattice_brain/ingestion/_contract.py +0 -90
- package/lattice_brain/ingestion/folder_scan.py +0 -57
- package/lattice_brain/ingestion/folders.py +0 -258
- package/lattice_brain/ingestion/jobs_api.py +0 -107
- package/lattice_brain/ingestion/routing.py +0 -295
- package/lattice_brain/ingestion_jobs.py +0 -380
- package/lattice_brain/memory.py +0 -75
- package/lattice_brain/portability/__init__.py +0 -90
- package/lattice_brain/portability/_contract.py +0 -42
- package/lattice_brain/portability/backups.py +0 -338
- package/lattice_brain/portability/bundles.py +0 -136
- package/lattice_brain/portability/constants.py +0 -93
- package/lattice_brain/portability/fsops.py +0 -138
- package/lattice_brain/portability/service.py +0 -41
- package/lattice_brain/portability/sharing.py +0 -710
- package/lattice_brain/quality.py +0 -543
- package/lattice_brain/retrieval_benchmark_fixtures.py +0 -92
- package/lattice_brain/runtime/agent_runtime.py +0 -859
- package/lattice_brain/runtime/contracts.py +0 -460
- package/lattice_brain/runtime/multi_agent.py +0 -942
- package/lattice_brain/runtime/statuses.py +0 -10
- package/lattice_brain/sealed_box.py +0 -240
- package/lattice_brain/self_model.py +0 -675
- package/lattice_brain/sensitivity.py +0 -94
- package/lattice_brain/storage/__init__.py +0 -22
- package/lattice_brain/storage/base.py +0 -100
- package/lattice_brain/storage/docker.py +0 -105
- package/lattice_brain/storage/factory.py +0 -31
- package/lattice_brain/storage/migration.py +0 -191
- package/lattice_brain/storage/postgres.py +0 -123
- package/lattice_brain/storage/sqlite.py +0 -143
- package/lattice_brain/synthesis.py +0 -824
- package/lattice_brain/workflow.py +0 -497
- package/latticeai/api/admin.py +0 -471
- package/latticeai/api/agent_registry.py +0 -105
- package/latticeai/api/agents.py +0 -228
- package/latticeai/api/auth.py +0 -383
- package/latticeai/api/automation_intelligence.py +0 -401
- package/latticeai/api/brain_intelligence.py +0 -199
- package/latticeai/api/browser.py +0 -493
- package/latticeai/api/change_proposals.py +0 -89
- package/latticeai/api/chat.py +0 -572
- package/latticeai/api/chat_agent_http.py +0 -892
- package/latticeai/api/chat_contracts.py +0 -73
- package/latticeai/api/chat_documents.py +0 -276
- package/latticeai/api/chat_helpers.py +0 -460
- package/latticeai/api/chat_history.py +0 -101
- package/latticeai/api/chat_hybrid.py +0 -113
- package/latticeai/api/chat_intents.py +0 -646
- package/latticeai/api/chat_stream.py +0 -216
- package/latticeai/api/chronicle.py +0 -63
- package/latticeai/api/command_center.py +0 -51
- package/latticeai/api/computer_use.py +0 -474
- package/latticeai/api/evidence_actions.py +0 -48
- package/latticeai/api/features.py +0 -70
- package/latticeai/api/funnel_metrics.py +0 -31
- package/latticeai/api/garden.py +0 -34
- package/latticeai/api/hooks.py +0 -165
- package/latticeai/api/index_jobs.py +0 -145
- package/latticeai/api/invitations.py +0 -100
- package/latticeai/api/knowledge_graph.py +0 -536
- package/latticeai/api/marketplace.py +0 -105
- package/latticeai/api/mcp.py +0 -482
- package/latticeai/api/memory.py +0 -270
- package/latticeai/api/network.py +0 -81
- package/latticeai/api/network_boundary.py +0 -225
- package/latticeai/api/permission_mode.py +0 -61
- package/latticeai/api/permissions.py +0 -436
- package/latticeai/api/plugins.py +0 -126
- package/latticeai/api/portability.py +0 -391
- package/latticeai/api/project_sessions.py +0 -114
- package/latticeai/api/realtime.py +0 -118
- package/latticeai/api/review_queue.py +0 -364
- package/latticeai/api/security_dashboard.py +0 -604
- package/latticeai/api/setup.py +0 -319
- package/latticeai/api/static_routes.py +0 -354
- package/latticeai/api/ui_redirects.py +0 -26
- package/latticeai/api/workflow_designer.py +0 -394
- package/latticeai/api/workspace.py +0 -856
- package/latticeai/api/workspace_scope.py +0 -125
- package/latticeai/core/agent/__init__.py +0 -93
- package/latticeai/core/agent/_contract.py +0 -79
- package/latticeai/core/agent/context.py +0 -57
- package/latticeai/core/agent/deps.py +0 -125
- package/latticeai/core/agent/execution.py +0 -622
- package/latticeai/core/agent/planning.py +0 -145
- package/latticeai/core/agent/recovery.py +0 -157
- package/latticeai/core/agent/runtime.py +0 -210
- package/latticeai/core/agent/verification.py +0 -231
- package/latticeai/core/agent_eval.py +0 -739
- package/latticeai/core/agent_helpers.py +0 -493
- package/latticeai/core/agent_profiles.py +0 -110
- package/latticeai/core/agent_prompts.py +0 -171
- package/latticeai/core/agent_registry.py +0 -232
- package/latticeai/core/agent_state.py +0 -41
- package/latticeai/core/agent_trace.py +0 -104
- package/latticeai/core/artifact_ledger.py +0 -109
- package/latticeai/core/audit.py +0 -260
- package/latticeai/core/builtin_hooks.py +0 -105
- package/latticeai/core/context_builder.py +0 -394
- package/latticeai/core/document_generator.py +0 -103
- package/latticeai/core/enterprise.py +0 -154
- package/latticeai/core/enterprise_admin.py +0 -158
- package/latticeai/core/file_generation/__init__.py +0 -115
- package/latticeai/core/file_generation/bundles.py +0 -76
- package/latticeai/core/file_generation/extraction.py +0 -154
- package/latticeai/core/file_generation/inference.py +0 -235
- package/latticeai/core/file_generation/orchestration.py +0 -152
- package/latticeai/core/file_generation/prompting.py +0 -117
- package/latticeai/core/file_generation/repair.py +0 -114
- package/latticeai/core/file_generation/sanitize.py +0 -61
- package/latticeai/core/file_generation/validation.py +0 -201
- package/latticeai/core/invitations.py +0 -132
- package/latticeai/core/legacy_compatibility.py +0 -243
- package/latticeai/core/logging_safety.py +0 -46
- package/latticeai/core/marketplace.py +0 -293
- package/latticeai/core/mcp_catalog.py +0 -452
- package/latticeai/core/mcp_registry.py +0 -506
- package/latticeai/core/network_boundary.py +0 -168
- package/latticeai/core/oidc.py +0 -208
- package/latticeai/core/plugins.py +0 -432
- package/latticeai/core/product_hardening.py +0 -218
- package/latticeai/core/project_sessions.py +0 -337
- package/latticeai/core/realtime.py +0 -238
- package/latticeai/core/run_explain.py +0 -426
- package/latticeai/core/run_store.py +0 -252
- package/latticeai/core/timezones.py +0 -80
- package/latticeai/core/workspace_computer_memory.py +0 -84
- package/latticeai/core/workspace_graph_trace.py +0 -155
- package/latticeai/core/workspace_indexing.py +0 -102
- package/latticeai/core/workspace_memory.py +0 -77
- package/latticeai/core/workspace_onboarding.py +0 -104
- package/latticeai/core/workspace_os.py +0 -978
- package/latticeai/core/workspace_os_constants.py +0 -126
- package/latticeai/core/workspace_os_state.py +0 -180
- package/latticeai/core/workspace_os_utils.py +0 -103
- package/latticeai/core/workspace_permissions.py +0 -101
- package/latticeai/core/workspace_plugins.py +0 -97
- package/latticeai/core/workspace_relationships.py +0 -99
- package/latticeai/core/workspace_reorganization.py +0 -335
- package/latticeai/core/workspace_review_items.py +0 -112
- package/latticeai/core/workspace_runs.py +0 -726
- package/latticeai/core/workspace_skills.py +0 -109
- package/latticeai/core/workspace_snapshots.py +0 -198
- package/latticeai/core/workspace_timeline.py +0 -110
- package/latticeai/integrations/__init__.py +0 -0
- package/latticeai/integrations/telegram_bot/__init__.py +0 -123
- package/latticeai/integrations/telegram_bot/__main__.py +0 -17
- package/latticeai/integrations/telegram_bot/config.py +0 -86
- package/latticeai/integrations/telegram_bot/dispatch.py +0 -311
- package/latticeai/integrations/telegram_bot/flows.py +0 -478
- package/latticeai/integrations/telegram_bot/helpers.py +0 -322
- package/latticeai/integrations/telegram_bot/screens.py +0 -394
- package/latticeai/runtime/audit_runtime.py +0 -76
- package/latticeai/runtime/automation_runtime.py +0 -81
- package/latticeai/runtime/chat_wiring.py +0 -141
- package/latticeai/runtime/context_runtime.py +0 -66
- package/latticeai/runtime/feature_toggle_wiring.py +0 -157
- package/latticeai/runtime/history_runtime.py +0 -163
- package/latticeai/runtime/history_writer.py +0 -138
- package/latticeai/runtime/hooks_runtime.py +0 -77
- package/latticeai/runtime/model_wiring.py +0 -68
- package/latticeai/runtime/namespace_runtime.py +0 -163
- package/latticeai/runtime/network_boundary_wiring.py +0 -117
- package/latticeai/runtime/network_config_runtime.py +0 -56
- package/latticeai/runtime/permission_mode_wiring.py +0 -112
- package/latticeai/runtime/persistence_runtime.py +0 -159
- package/latticeai/runtime/platform_runtime_wiring.py +0 -89
- package/latticeai/runtime/review_wiring.py +0 -42
- package/latticeai/runtime/router_registration.py +0 -693
- package/latticeai/runtime/service_singletons.py +0 -55
- package/latticeai/runtime/sso_config_runtime.py +0 -128
- package/latticeai/runtime/user_key_runtime.py +0 -106
- package/latticeai/runtime/web_runtime.py +0 -92
- package/latticeai/server_app.py +0 -51
- package/latticeai/services/app_context.py +0 -130
- package/latticeai/services/automation_execution.py +0 -266
- package/latticeai/services/automation_intelligence.py +0 -614
- package/latticeai/services/brain_automation.py +0 -191
- package/latticeai/services/brain_intelligence/__init__.py +0 -58
- package/latticeai/services/brain_intelligence/_contract.py +0 -71
- package/latticeai/services/brain_intelligence/consistency.py +0 -193
- package/latticeai/services/brain_intelligence/constants.py +0 -47
- package/latticeai/services/brain_intelligence/digest.py +0 -258
- package/latticeai/services/brain_intelligence/health.py +0 -331
- package/latticeai/services/brain_intelligence/proposals.py +0 -259
- package/latticeai/services/brain_intelligence/sampling.py +0 -84
- package/latticeai/services/brain_intelligence/service.py +0 -48
- package/latticeai/services/change_proposals.py +0 -471
- package/latticeai/services/chat_service.py +0 -243
- package/latticeai/services/chronicle.py +0 -555
- package/latticeai/services/cloud_egress_audit.py +0 -85
- package/latticeai/services/cloud_extraction.py +0 -129
- package/latticeai/services/cloud_streaming.py +0 -268
- package/latticeai/services/cloud_token_guard.py +0 -84
- package/latticeai/services/command_center.py +0 -548
- package/latticeai/services/evidence_actions.py +0 -258
- package/latticeai/services/feature_toggles.py +0 -502
- package/latticeai/services/folder_watch.py +0 -520
- package/latticeai/services/funnel_metrics.py +0 -307
- package/latticeai/services/hybrid_chat.py +0 -316
- package/latticeai/services/hybrid_context.py +0 -228
- package/latticeai/services/hybrid_policy.py +0 -129
- package/latticeai/services/interop_bridges.py +0 -978
- package/latticeai/services/local_knowledge.py +0 -465
- package/latticeai/services/memory_service/__init__.py +0 -52
- package/latticeai/services/memory_service/_contract.py +0 -100
- package/latticeai/services/memory_service/brief.py +0 -431
- package/latticeai/services/memory_service/constants.py +0 -57
- package/latticeai/services/memory_service/maintenance.py +0 -138
- package/latticeai/services/memory_service/manager.py +0 -186
- package/latticeai/services/memory_service/proof.py +0 -136
- package/latticeai/services/memory_service/recall.py +0 -225
- package/latticeai/services/memory_service/service.py +0 -48
- package/latticeai/services/memory_service/stores.py +0 -110
- package/latticeai/services/mode_store.py +0 -132
- package/latticeai/services/model_recommendation.py +0 -224
- package/latticeai/services/model_runtime/cloud.py +0 -87
- package/latticeai/services/network_boundary_service.py +0 -117
- package/latticeai/services/obsidian_bridge.py +0 -609
- package/latticeai/services/openai_compatible_adapter.py +0 -101
- package/latticeai/services/permission_mode_service.py +0 -122
- package/latticeai/services/platform_runtime.py +0 -366
- package/latticeai/services/review_queue.py +0 -380
- package/latticeai/services/router_context.py +0 -59
- package/latticeai/services/run_executor.py +0 -387
- package/latticeai/services/self_model_service.py +0 -171
- package/latticeai/services/setup_detection.py +0 -147
- package/latticeai/services/triggers.py +0 -378
- package/latticeai/services/upload_service.py +0 -172
- package/latticeai/services/workspace_service.py +0 -165
- package/latticeai/setup/__init__.py +0 -25
- package/latticeai/setup/auto_setup.py +0 -846
- package/latticeai/setup/demo_corpus.py +0 -98
- package/latticeai/setup/wizard/__init__.py +0 -126
- package/latticeai/setup/wizard/catalog.py +0 -175
- package/latticeai/setup/wizard/detect.py +0 -302
- package/latticeai/setup/wizard/install.py +0 -348
- package/latticeai/setup/wizard/paths.py +0 -165
- package/latticeai/setup/wizard/plans.py +0 -74
- package/latticeai/setup/wizard/recommend.py +0 -320
- package/scripts/bench_agent_smoke.py +0 -409
- package/scripts/bench_models.py +0 -540
- package/scripts/bench_vector_index.py +0 -295
- package/scripts/funnel_soft_gate.py +0 -192
- package/scripts/generate_agent_loop_fixtures.py +0 -994
- package/scripts/generate_rust_parity_fixtures.py +0 -908
- package/scripts/migrate_brain_storage.py +0 -57
- package/scripts/parity_fixture_corpus_context.py +0 -162
- package/scripts/parity_fixture_corpus_docgen.py +0 -341
- package/scripts/profile_kg.py +0 -355
- package/server.py +0 -30
- package/static/app/assets/arrow-left-kfsrk0mv.js +0 -1
- package/static/app/assets/circle-check-qqLug9nU.js +0 -1
- package/static/app/assets/index-DxmOfNRi.css +0 -2
- package/static/app/assets/search-BcHqkjoy.js +0 -1
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
"""int8-quantized exhaustive index — same recall shape, a fraction of the RAM.
|
|
2
|
-
|
|
3
|
-
A 384-dimension embedding held as a Python ``list[float]`` costs ~3 KB (each
|
|
4
|
-
float is a boxed object plus a pointer); the same vector as an ``array("b")``
|
|
5
|
-
of int8 codes costs 384 bytes plus one header. On a 100 000-vector brain that
|
|
6
|
-
is the difference between ~300 MB and ~40 MB of resident scoring state, which
|
|
7
|
-
is what decides whether a large index can be held at all.
|
|
8
|
-
|
|
9
|
-
The trade is precision, not coverage: every vector is still compared (the
|
|
10
|
-
index is *exhaustive*), but each score is reconstructed from 8-bit codes, so
|
|
11
|
-
it lands within roughly ±1% of the exact cosine. Near-ties can therefore swap
|
|
12
|
-
places — which is why this backend reports ``approx = True`` and why the
|
|
13
|
-
default stays :class:`~.brute_force.BruteForceIndex`.
|
|
14
|
-
|
|
15
|
-
Symmetric per-vector scaling: each vector is divided by its own peak absolute
|
|
16
|
-
value before rounding, so a short vector keeps its full 8-bit resolution
|
|
17
|
-
instead of being crushed by the largest vector in the corpus.
|
|
18
|
-
|
|
19
|
-
Measured caveat (docs/PERFORMANCE.md, 10k vectors): the RAM saving above is a
|
|
20
|
-
property of the *data structure*, and the current search path does not cash it
|
|
21
|
-
in. ``retrieval_vector`` already feeds the index in bounded batches, so
|
|
22
|
-
resident vectors were never the dominant term — the fetched SQLite rows are —
|
|
23
|
-
and peak memory moves by ~1 MB while latency roughly doubles. This backend is
|
|
24
|
-
therefore honest, exhaustive, and currently not the one to choose; it exists
|
|
25
|
-
as the representation a *held* (cross-query) index would need.
|
|
26
|
-
"""
|
|
27
|
-
|
|
28
|
-
from __future__ import annotations
|
|
29
|
-
|
|
30
|
-
from array import array
|
|
31
|
-
from operator import mul
|
|
32
|
-
from typing import Any, Dict, Iterable, List, Mapping, Optional, Sequence, Tuple
|
|
33
|
-
|
|
34
|
-
from .base import IndexItem, IndexStats, ScoredId, score_floor, take_top
|
|
35
|
-
|
|
36
|
-
QUANTIZED_BACKEND = "quantized-int8"
|
|
37
|
-
#: int8 keeps ``-128``; the symmetric range stops at ``-127`` so ``+peak`` and
|
|
38
|
-
#: ``-peak`` quantize to codes of equal magnitude.
|
|
39
|
-
INT8_MAX = 127
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
def quantize(vector: Sequence[float]) -> Tuple[array, float]:
|
|
43
|
-
"""``vector`` → (int8 codes, scale) with ``value ≈ code * scale``.
|
|
44
|
-
|
|
45
|
-
An all-zero vector has no peak to scale against; it quantizes to all-zero
|
|
46
|
-
codes with scale ``0.0``, which scores 0 against everything — the same
|
|
47
|
-
answer the exact scan gives for a zero vector.
|
|
48
|
-
"""
|
|
49
|
-
values = [float(value) for value in vector]
|
|
50
|
-
peak = max((abs(value) for value in values), default=0.0)
|
|
51
|
-
if peak <= 0.0:
|
|
52
|
-
return array("b", bytes(len(values))), 0.0
|
|
53
|
-
scale = peak / INT8_MAX
|
|
54
|
-
codes = array(
|
|
55
|
-
"b",
|
|
56
|
-
(
|
|
57
|
-
max(-INT8_MAX, min(INT8_MAX, int(round(value / scale))))
|
|
58
|
-
for value in values
|
|
59
|
-
),
|
|
60
|
-
)
|
|
61
|
-
return codes, scale
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
class QuantizedIndex:
|
|
65
|
-
"""Exhaustive int8 index: full coverage, approximate scores."""
|
|
66
|
-
|
|
67
|
-
def __init__(self, *, dim: int = 0) -> None:
|
|
68
|
-
self._dim = int(dim)
|
|
69
|
-
self._codes: Dict[str, array] = {}
|
|
70
|
-
self._scales: Dict[str, float] = {}
|
|
71
|
-
self._metadata: Dict[str, Dict[str, Any]] = {}
|
|
72
|
-
|
|
73
|
-
@property
|
|
74
|
-
def backend(self) -> str:
|
|
75
|
-
return QUANTIZED_BACKEND
|
|
76
|
-
|
|
77
|
-
@property
|
|
78
|
-
def approx(self) -> bool:
|
|
79
|
-
return True
|
|
80
|
-
|
|
81
|
-
@property
|
|
82
|
-
def exhaustive(self) -> bool:
|
|
83
|
-
return True
|
|
84
|
-
|
|
85
|
-
def add(
|
|
86
|
-
self,
|
|
87
|
-
id: str,
|
|
88
|
-
vector: Sequence[float],
|
|
89
|
-
metadata: Optional[Mapping[str, Any]] = None,
|
|
90
|
-
) -> None:
|
|
91
|
-
codes, scale = quantize(vector)
|
|
92
|
-
if not self._dim:
|
|
93
|
-
self._dim = len(codes)
|
|
94
|
-
key = str(id)
|
|
95
|
-
self._codes[key] = codes
|
|
96
|
-
self._scales[key] = scale
|
|
97
|
-
self._metadata[key] = dict(metadata or {})
|
|
98
|
-
|
|
99
|
-
def remove(self, id: str) -> None:
|
|
100
|
-
key = str(id)
|
|
101
|
-
self._codes.pop(key, None)
|
|
102
|
-
self._scales.pop(key, None)
|
|
103
|
-
self._metadata.pop(key, None)
|
|
104
|
-
|
|
105
|
-
def rebuild(self, items: Iterable[IndexItem]) -> None:
|
|
106
|
-
self._codes.clear()
|
|
107
|
-
self._scales.clear()
|
|
108
|
-
self._metadata.clear()
|
|
109
|
-
for item_id, vector, metadata in items:
|
|
110
|
-
self.add(item_id, vector, metadata)
|
|
111
|
-
|
|
112
|
-
def search(
|
|
113
|
-
self,
|
|
114
|
-
query: Sequence[float],
|
|
115
|
-
top_k: int,
|
|
116
|
-
filter: Optional[Mapping[str, Any]] = None,
|
|
117
|
-
) -> List[ScoredId]:
|
|
118
|
-
floor = score_floor(filter)
|
|
119
|
-
query_codes, query_scale = quantize(query)
|
|
120
|
-
scored: List[ScoredId] = []
|
|
121
|
-
for item_id, codes in self._codes.items():
|
|
122
|
-
if len(codes) != len(query_codes):
|
|
123
|
-
raise ValueError(
|
|
124
|
-
f"embedding dimension mismatch: {len(query_codes)} vs "
|
|
125
|
-
f"{len(codes)}; the vector index was built with a "
|
|
126
|
-
"different model"
|
|
127
|
-
)
|
|
128
|
-
# Integer dot product, then one float multiply for the two scales:
|
|
129
|
-
# all the arithmetic that decides the ranking stays in ints.
|
|
130
|
-
raw = sum(map(mul, query_codes, codes))
|
|
131
|
-
score = float(raw) * query_scale * self._scales[item_id]
|
|
132
|
-
if score < floor:
|
|
133
|
-
continue
|
|
134
|
-
scored.append((item_id, score))
|
|
135
|
-
return take_top(scored, top_k)
|
|
136
|
-
|
|
137
|
-
def stats(self) -> IndexStats:
|
|
138
|
-
return IndexStats(
|
|
139
|
-
backend=self.backend,
|
|
140
|
-
size=len(self._codes),
|
|
141
|
-
dim=self._dim,
|
|
142
|
-
approx=True,
|
|
143
|
-
exhaustive=True,
|
|
144
|
-
detail="scores are reconstructed from 8-bit codes (~1% error)",
|
|
145
|
-
)
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
__all__ = ["INT8_MAX", "QUANTIZED_BACKEND", "QuantizedIndex", "quantize"]
|
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
"""Which backend scores this search, and — when it is not the one you asked
|
|
2
|
-
for — why.
|
|
3
|
-
|
|
4
|
-
``LATTICEAI_VECTOR_INDEX`` selects ``brute`` (default) / ``quantized`` /
|
|
5
|
-
``hnsw``. Two failure modes have to stay loud, because both otherwise look
|
|
6
|
-
exactly like "search is a bit worse today":
|
|
7
|
-
|
|
8
|
-
* an unknown name (typo, stale config) must not silently mean "the default";
|
|
9
|
-
* asking for ``hnsw`` without the optional extra installed must not silently
|
|
10
|
-
mean "you have ANN".
|
|
11
|
-
|
|
12
|
-
Both resolve to the exact brute-force scan and carry a ``detail`` string
|
|
13
|
-
naming the cause, which ``index_status()`` and the search result surface.
|
|
14
|
-
|
|
15
|
-
The backend is a *choice*, not a switch, so it cannot ride a
|
|
16
|
-
:class:`~lattice_brain.gates.FeatureGate` (which answers booleans). It gets the
|
|
17
|
-
same shape of seam instead: :func:`bind_vector_index_resolver` installs a
|
|
18
|
-
caller-supplied resolver that is consulted ahead of the environment, which is
|
|
19
|
-
what lets the settings panel change the backend without a restart. With nothing
|
|
20
|
-
bound — the default — the environment variable is still the whole control
|
|
21
|
-
surface, exactly as before.
|
|
22
|
-
"""
|
|
23
|
-
|
|
24
|
-
from __future__ import annotations
|
|
25
|
-
|
|
26
|
-
import os
|
|
27
|
-
from dataclasses import dataclass
|
|
28
|
-
from typing import Any, Callable, Dict, Optional
|
|
29
|
-
|
|
30
|
-
from .base import Similarity, VectorIndex
|
|
31
|
-
from .brute_force import BRUTE_FORCE_BACKEND, BruteForceIndex
|
|
32
|
-
from .hnsw import HNSW_BACKEND, HnswIndex, load_hnswlib
|
|
33
|
-
from .quantized import QUANTIZED_BACKEND, QuantizedIndex
|
|
34
|
-
|
|
35
|
-
VECTOR_INDEX_ENV = "LATTICEAI_VECTOR_INDEX"
|
|
36
|
-
DEFAULT_VECTOR_INDEX = "brute"
|
|
37
|
-
#: Accepted values of ``LATTICEAI_VECTOR_INDEX``.
|
|
38
|
-
VECTOR_INDEX_CHOICES = ("brute", "quantized", "hnsw")
|
|
39
|
-
|
|
40
|
-
_BACKEND_LABELS = {
|
|
41
|
-
"brute": BRUTE_FORCE_BACKEND,
|
|
42
|
-
"quantized": QUANTIZED_BACKEND,
|
|
43
|
-
"hnsw": HNSW_BACKEND,
|
|
44
|
-
}
|
|
45
|
-
_APPROX = {"brute": False, "quantized": True, "hnsw": True}
|
|
46
|
-
_EXHAUSTIVE = {"brute": True, "quantized": True, "hnsw": False}
|
|
47
|
-
|
|
48
|
-
#: App-layer resolver, consulted before the environment. ``None`` (the default)
|
|
49
|
-
#: leaves this module reading exactly the env var it always read.
|
|
50
|
-
_RESOLVER: Optional[Callable[[], Optional[str]]] = None
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def bind_vector_index_resolver(
|
|
54
|
-
resolver: Optional[Callable[[], Optional[str]]],
|
|
55
|
-
) -> None:
|
|
56
|
-
"""Delegate backend selection to a callable (``None`` hands it back to env).
|
|
57
|
-
|
|
58
|
-
A resolver that returns ``None`` also falls through to the environment, so
|
|
59
|
-
"the settings service has no opinion" and "there is no settings service"
|
|
60
|
-
reach the same answer instead of two.
|
|
61
|
-
"""
|
|
62
|
-
global _RESOLVER
|
|
63
|
-
_RESOLVER = resolver
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
@dataclass(frozen=True)
|
|
67
|
-
class BackendSelection:
|
|
68
|
-
"""The resolved backend plus an honest account of any substitution."""
|
|
69
|
-
|
|
70
|
-
requested: str
|
|
71
|
-
name: str
|
|
72
|
-
backend: str
|
|
73
|
-
approx: bool
|
|
74
|
-
exhaustive: bool
|
|
75
|
-
detail: Optional[str] = None
|
|
76
|
-
|
|
77
|
-
@property
|
|
78
|
-
def honored(self) -> bool:
|
|
79
|
-
"""True when the caller got the backend they asked for."""
|
|
80
|
-
return self.requested == self.name
|
|
81
|
-
|
|
82
|
-
def as_dict(self) -> Dict[str, Any]:
|
|
83
|
-
return {
|
|
84
|
-
"requested": self.requested,
|
|
85
|
-
"backend": self.backend,
|
|
86
|
-
"name": self.name,
|
|
87
|
-
"approx": self.approx,
|
|
88
|
-
"exhaustive": self.exhaustive,
|
|
89
|
-
"honored": self.honored,
|
|
90
|
-
"detail": self.detail,
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
def _selection(name: str, *, requested: str, detail: Optional[str]) -> BackendSelection:
|
|
95
|
-
return BackendSelection(
|
|
96
|
-
requested=requested,
|
|
97
|
-
name=name,
|
|
98
|
-
backend=_BACKEND_LABELS[name],
|
|
99
|
-
approx=_APPROX[name],
|
|
100
|
-
exhaustive=_EXHAUSTIVE[name],
|
|
101
|
-
detail=detail,
|
|
102
|
-
)
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
def resolve_vector_index(requested: Optional[str] = None) -> BackendSelection:
|
|
106
|
-
"""Resolve the configured backend (never raises, always falls back safe)."""
|
|
107
|
-
raw = requested
|
|
108
|
-
if raw is None and _RESOLVER is not None:
|
|
109
|
-
raw = _RESOLVER()
|
|
110
|
-
if raw is None:
|
|
111
|
-
raw = os.getenv(VECTOR_INDEX_ENV, "")
|
|
112
|
-
name = str(raw or "").strip().lower() or DEFAULT_VECTOR_INDEX
|
|
113
|
-
if name not in VECTOR_INDEX_CHOICES:
|
|
114
|
-
return _selection(
|
|
115
|
-
DEFAULT_VECTOR_INDEX,
|
|
116
|
-
requested=name,
|
|
117
|
-
detail=(
|
|
118
|
-
f"unknown vector index backend {name!r}; expected one of "
|
|
119
|
-
f"{', '.join(VECTOR_INDEX_CHOICES)} — using the exact "
|
|
120
|
-
"brute-force scan"
|
|
121
|
-
),
|
|
122
|
-
)
|
|
123
|
-
if name == "hnsw":
|
|
124
|
-
module, reason = load_hnswlib()
|
|
125
|
-
if module is None:
|
|
126
|
-
return _selection(
|
|
127
|
-
DEFAULT_VECTOR_INDEX,
|
|
128
|
-
requested=name,
|
|
129
|
-
detail=f"{reason} — using the exact brute-force scan",
|
|
130
|
-
)
|
|
131
|
-
return _selection(name, requested=name, detail=None)
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
def build_index(
|
|
135
|
-
selection: BackendSelection,
|
|
136
|
-
*,
|
|
137
|
-
dim: int = 0,
|
|
138
|
-
similarity: Optional[Similarity] = None,
|
|
139
|
-
) -> VectorIndex:
|
|
140
|
-
"""Instantiate the backend named by ``selection``.
|
|
141
|
-
|
|
142
|
-
``similarity`` is threaded into the exact backend only: quantized and
|
|
143
|
-
HNSW score in their own representation, and pretending otherwise would
|
|
144
|
-
make an injected function look respected when it is not.
|
|
145
|
-
"""
|
|
146
|
-
if selection.name == "quantized":
|
|
147
|
-
return QuantizedIndex(dim=dim)
|
|
148
|
-
if selection.name == HNSW_BACKEND:
|
|
149
|
-
return HnswIndex(dim=dim)
|
|
150
|
-
return BruteForceIndex(dim=dim, similarity=similarity)
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
__all__ = [
|
|
154
|
-
"DEFAULT_VECTOR_INDEX",
|
|
155
|
-
"VECTOR_INDEX_CHOICES",
|
|
156
|
-
"VECTOR_INDEX_ENV",
|
|
157
|
-
"BackendSelection",
|
|
158
|
-
"bind_vector_index_resolver",
|
|
159
|
-
"build_index",
|
|
160
|
-
"resolve_vector_index",
|
|
161
|
-
]
|
|
@@ -1,308 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from typing import TYPE_CHECKING
|
|
4
|
-
|
|
5
|
-
# ruff: noqa: F403,F405
|
|
6
|
-
from ._kg_common import * # noqa: F403,F401
|
|
7
|
-
|
|
8
|
-
# The cross-mixin surface (`_connect`, `_upsert_node`, …) is declared in
|
|
9
|
-
# `_kg_contract.KnowledgeGraphCore`. It is a typing-only base: at runtime this
|
|
10
|
-
# is `object`, so the MRO of `KnowledgeGraphStore` is unchanged.
|
|
11
|
-
if TYPE_CHECKING:
|
|
12
|
-
from ._kg_contract import KnowledgeGraphCore as _Core
|
|
13
|
-
else:
|
|
14
|
-
_Core = object
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class KnowledgeGraphWriteMixin(_Core):
|
|
19
|
-
def _upsert_node(
|
|
20
|
-
self,
|
|
21
|
-
conn: sqlite3.Connection,
|
|
22
|
-
node_id: str,
|
|
23
|
-
node_type: str,
|
|
24
|
-
title: str,
|
|
25
|
-
summary: str = "",
|
|
26
|
-
metadata: Optional[Dict[str, Any]] = None,
|
|
27
|
-
raw: Optional[Dict[str, Any]] = None,
|
|
28
|
-
owner: Optional[str] = None,
|
|
29
|
-
workspace_id: Optional[str] = None,
|
|
30
|
-
visibility: Optional[str] = None,
|
|
31
|
-
) -> str:
|
|
32
|
-
now = _now()
|
|
33
|
-
# v4 write-mastering: nodes_v2 is authoritative; the legacy nodes
|
|
34
|
-
# table is maintained as the compatibility projection.
|
|
35
|
-
title_s = title[:240]
|
|
36
|
-
summary_s = summary[:1000]
|
|
37
|
-
meta_json = _json(metadata)
|
|
38
|
-
self._v2_project_node(
|
|
39
|
-
conn,
|
|
40
|
-
node_id,
|
|
41
|
-
node_type,
|
|
42
|
-
title_s,
|
|
43
|
-
summary_s,
|
|
44
|
-
meta_json,
|
|
45
|
-
created_at=now,
|
|
46
|
-
updated_at=now,
|
|
47
|
-
owner=owner,
|
|
48
|
-
workspace_id=workspace_id,
|
|
49
|
-
visibility=visibility,
|
|
50
|
-
strict=True,
|
|
51
|
-
)
|
|
52
|
-
conn.execute(
|
|
53
|
-
"""
|
|
54
|
-
INSERT INTO nodes(id, type, title, summary, metadata_json, raw_json, created_at, updated_at)
|
|
55
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
56
|
-
ON CONFLICT(id) DO UPDATE SET
|
|
57
|
-
title=excluded.title,
|
|
58
|
-
summary=excluded.summary,
|
|
59
|
-
metadata_json=excluded.metadata_json,
|
|
60
|
-
raw_json=excluded.raw_json,
|
|
61
|
-
updated_at=excluded.updated_at
|
|
62
|
-
""",
|
|
63
|
-
(node_id, node_type, title_s, summary_s, meta_json, _json(raw), now, now),
|
|
64
|
-
)
|
|
65
|
-
if node_type != "Chunk":
|
|
66
|
-
self._upsert_vector_item(
|
|
67
|
-
conn,
|
|
68
|
-
item_id=node_id,
|
|
69
|
-
item_type="node",
|
|
70
|
-
source_node=node_id,
|
|
71
|
-
text=self._vector_text_for_node(
|
|
72
|
-
title=title_s, summary=summary_s, metadata=metadata
|
|
73
|
-
),
|
|
74
|
-
metadata={"node_type": node_type, **(metadata or {})},
|
|
75
|
-
)
|
|
76
|
-
return node_id
|
|
77
|
-
|
|
78
|
-
def _upsert_edge(
|
|
79
|
-
self,
|
|
80
|
-
conn: sqlite3.Connection,
|
|
81
|
-
from_node: str,
|
|
82
|
-
to_node: str,
|
|
83
|
-
edge_type: str,
|
|
84
|
-
weight: float = 1.0,
|
|
85
|
-
metadata: Optional[Dict[str, Any]] = None,
|
|
86
|
-
*,
|
|
87
|
-
legacy_label: Optional[str] = None,
|
|
88
|
-
) -> str:
|
|
89
|
-
# v4 write door: every new edge stores the canonical EdgeType value —
|
|
90
|
-
# free-string types (e.g. '포함함', '언급함') are normalized here, so no
|
|
91
|
-
# caller can mint new legacy taxonomy. The original label survives in
|
|
92
|
-
# metadata.legacy_label for traceability.
|
|
93
|
-
#
|
|
94
|
-
# legacy_type in edges_v2:
|
|
95
|
-
# - normal write door: synonyms always dedupe to ONE row with legacy_type=''
|
|
96
|
-
# - import (passes legacy_label=) : preserves distinct legacy labels as
|
|
97
|
-
# separate v2 rows (lossless collision for old artifacts / backfill)
|
|
98
|
-
passed_for_legacy = legacy_label or edge_type
|
|
99
|
-
if EdgeType is not None:
|
|
100
|
-
canonical = EdgeType.from_legacy(edge_type).value
|
|
101
|
-
if canonical != edge_type or (legacy_label and legacy_label != canonical):
|
|
102
|
-
metadata = dict(metadata or {})
|
|
103
|
-
ll = legacy_label or edge_type
|
|
104
|
-
if ll != canonical:
|
|
105
|
-
metadata.setdefault("legacy_label", ll)
|
|
106
|
-
edge_type = canonical
|
|
107
|
-
edge_id = f"edge:{_sha256_text(f'{from_node}|{edge_type}|{to_node}')[:24]}"
|
|
108
|
-
now = _now()
|
|
109
|
-
meta_json = _json(metadata) # canonical string shared with the projection
|
|
110
|
-
v2_legacy = ""
|
|
111
|
-
if passed_for_legacy and passed_for_legacy != edge_type:
|
|
112
|
-
if legacy_label is not None:
|
|
113
|
-
# explicit legacy_label from import path forces distinct legacy_type
|
|
114
|
-
# rows in v2 for collision preservation
|
|
115
|
-
v2_legacy = passed_for_legacy
|
|
116
|
-
else:
|
|
117
|
-
# normal write-door dedupes even for synonym labels (legacy_type='')
|
|
118
|
-
v2_legacy = ""
|
|
119
|
-
# v2 may use distinct eid when preserving legacy collisions (different from
|
|
120
|
-
# the canon-based edge_id used for legacy edges table which always dedupes)
|
|
121
|
-
v2_eid = (
|
|
122
|
-
f"edge:{_sha256_text(f'{from_node}|{edge_type}|{to_node}|{v2_legacy}')[:24]}"
|
|
123
|
-
if v2_legacy
|
|
124
|
-
else edge_id
|
|
125
|
-
)
|
|
126
|
-
self._v2_project_edge(
|
|
127
|
-
conn,
|
|
128
|
-
from_node,
|
|
129
|
-
to_node,
|
|
130
|
-
edge_type,
|
|
131
|
-
float(weight),
|
|
132
|
-
meta_json,
|
|
133
|
-
edge_id=v2_eid,
|
|
134
|
-
created_at=now,
|
|
135
|
-
strict=True,
|
|
136
|
-
legacy_type=v2_legacy,
|
|
137
|
-
)
|
|
138
|
-
conn.execute(
|
|
139
|
-
"""
|
|
140
|
-
INSERT INTO edges(id, from_node, to_node, type, weight, metadata_json, created_at)
|
|
141
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
142
|
-
ON CONFLICT(from_node, to_node, type) DO UPDATE SET
|
|
143
|
-
weight=max(edges.weight, excluded.weight),
|
|
144
|
-
metadata_json=excluded.metadata_json
|
|
145
|
-
""",
|
|
146
|
-
(edge_id, from_node, to_node, edge_type, float(weight), meta_json, now),
|
|
147
|
-
)
|
|
148
|
-
return edge_id
|
|
149
|
-
|
|
150
|
-
def _vector_text_for_node(
|
|
151
|
-
self,
|
|
152
|
-
*,
|
|
153
|
-
title: str,
|
|
154
|
-
summary: str = "",
|
|
155
|
-
metadata: Optional[Dict[str, Any]] = None,
|
|
156
|
-
) -> str:
|
|
157
|
-
metadata = metadata or {}
|
|
158
|
-
meta_parts = []
|
|
159
|
-
for key in (
|
|
160
|
-
"filename",
|
|
161
|
-
"relative_path",
|
|
162
|
-
"file_path",
|
|
163
|
-
"conversation_id",
|
|
164
|
-
"source",
|
|
165
|
-
"category",
|
|
166
|
-
"ext",
|
|
167
|
-
"role",
|
|
168
|
-
):
|
|
169
|
-
value = metadata.get(key)
|
|
170
|
-
if value:
|
|
171
|
-
meta_parts.append(str(value))
|
|
172
|
-
return _clean_text(
|
|
173
|
-
"\n".join([str(title or ""), str(summary or ""), " ".join(meta_parts)])
|
|
174
|
-
)
|
|
175
|
-
|
|
176
|
-
def _upsert_vector_item(
|
|
177
|
-
self,
|
|
178
|
-
conn: sqlite3.Connection,
|
|
179
|
-
*,
|
|
180
|
-
item_id: str,
|
|
181
|
-
item_type: str,
|
|
182
|
-
source_node: str,
|
|
183
|
-
text: str,
|
|
184
|
-
metadata: Optional[Dict[str, Any]] = None,
|
|
185
|
-
) -> bool:
|
|
186
|
-
text = _clean_text(text)
|
|
187
|
-
if len(text) < 2:
|
|
188
|
-
conn.execute("DELETE FROM vector_embeddings WHERE item_id=?", (item_id,))
|
|
189
|
-
return False
|
|
190
|
-
text_hash = _sha256_text(text)
|
|
191
|
-
existing = conn.execute(
|
|
192
|
-
"""
|
|
193
|
-
SELECT text_hash, embedding_dim, embedding_model
|
|
194
|
-
FROM vector_embeddings
|
|
195
|
-
WHERE item_id=?
|
|
196
|
-
""",
|
|
197
|
-
(item_id,),
|
|
198
|
-
).fetchone()
|
|
199
|
-
if (
|
|
200
|
-
existing
|
|
201
|
-
and existing["text_hash"] == text_hash
|
|
202
|
-
and existing["embedding_dim"] == self._embedding_model.dim
|
|
203
|
-
and existing["embedding_model"] == self._embedding_model.model_id
|
|
204
|
-
):
|
|
205
|
-
return False
|
|
206
|
-
embedding = self._embedding_model.encode(
|
|
207
|
-
self._embedding_model.embed(text[:50_000])
|
|
208
|
-
)
|
|
209
|
-
conn.execute(
|
|
210
|
-
"""
|
|
211
|
-
INSERT INTO vector_embeddings(
|
|
212
|
-
item_id, item_type, source_node, text_hash, embedding,
|
|
213
|
-
embedding_dim, embedding_model, metadata_json, indexed_at
|
|
214
|
-
)
|
|
215
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
216
|
-
ON CONFLICT(item_id) DO UPDATE SET
|
|
217
|
-
item_type=excluded.item_type,
|
|
218
|
-
source_node=excluded.source_node,
|
|
219
|
-
text_hash=excluded.text_hash,
|
|
220
|
-
embedding=excluded.embedding,
|
|
221
|
-
embedding_dim=excluded.embedding_dim,
|
|
222
|
-
embedding_model=excluded.embedding_model,
|
|
223
|
-
metadata_json=excluded.metadata_json,
|
|
224
|
-
indexed_at=excluded.indexed_at
|
|
225
|
-
""",
|
|
226
|
-
(
|
|
227
|
-
item_id,
|
|
228
|
-
item_type,
|
|
229
|
-
source_node,
|
|
230
|
-
text_hash,
|
|
231
|
-
embedding,
|
|
232
|
-
self._embedding_model.dim,
|
|
233
|
-
self._embedding_model.model_id,
|
|
234
|
-
_json(metadata),
|
|
235
|
-
_now(),
|
|
236
|
-
),
|
|
237
|
-
)
|
|
238
|
-
return True
|
|
239
|
-
|
|
240
|
-
def _upsert_chunk(
|
|
241
|
-
self,
|
|
242
|
-
conn: sqlite3.Connection,
|
|
243
|
-
*,
|
|
244
|
-
chunk_id: str,
|
|
245
|
-
source_node: str,
|
|
246
|
-
text: str,
|
|
247
|
-
metadata: Optional[Dict[str, Any]] = None,
|
|
248
|
-
) -> None:
|
|
249
|
-
metadata = metadata or {}
|
|
250
|
-
conn.execute(
|
|
251
|
-
"INSERT OR REPLACE INTO chunks(id, source_node, text, metadata_json, created_at) "
|
|
252
|
-
"VALUES (?, ?, ?, ?, ?)",
|
|
253
|
-
(chunk_id, source_node, text, _json(metadata), _now()),
|
|
254
|
-
)
|
|
255
|
-
self._upsert_vector_item(
|
|
256
|
-
conn,
|
|
257
|
-
item_id=chunk_id,
|
|
258
|
-
item_type="chunk",
|
|
259
|
-
source_node=chunk_id,
|
|
260
|
-
text=text,
|
|
261
|
-
metadata={**metadata, "parent_source_node": source_node},
|
|
262
|
-
)
|
|
263
|
-
|
|
264
|
-
def set_node_sensitivity(
|
|
265
|
-
self,
|
|
266
|
-
node_id: str,
|
|
267
|
-
*,
|
|
268
|
-
local_only: bool,
|
|
269
|
-
reason: Optional[str] = None,
|
|
270
|
-
) -> Dict[str, Any]:
|
|
271
|
-
"""Mark (or unmark) one node as never-leaving.
|
|
272
|
-
|
|
273
|
-
The cloud filter has always looked for this flag; until 10.2.0 nothing
|
|
274
|
-
could set it, so the guard was unreachable. This is the user-driven
|
|
275
|
-
half — ingestion stamps secret-bearing paths automatically, and this
|
|
276
|
-
covers everything a path cannot tell you, like a note whose *content*
|
|
277
|
-
is private.
|
|
278
|
-
|
|
279
|
-
Unmarking is allowed and audited by the caller: a user who flagged
|
|
280
|
-
something by mistake must be able to undo it, but the reason is cleared
|
|
281
|
-
with the flag so a stale justification cannot linger.
|
|
282
|
-
"""
|
|
283
|
-
with self._connect() as conn:
|
|
284
|
-
row = conn.execute(
|
|
285
|
-
"SELECT metadata_json FROM nodes WHERE id=?", (node_id,)
|
|
286
|
-
).fetchone()
|
|
287
|
-
if row is None:
|
|
288
|
-
return {"ok": False, "node_id": node_id, "reason": "node not found"}
|
|
289
|
-
try:
|
|
290
|
-
metadata = json.loads(row[0] or "{}")
|
|
291
|
-
except Exception:
|
|
292
|
-
metadata = {}
|
|
293
|
-
if local_only:
|
|
294
|
-
metadata["local_only"] = True
|
|
295
|
-
metadata["local_only_reason"] = reason or "marked by the user"
|
|
296
|
-
else:
|
|
297
|
-
metadata.pop("local_only", None)
|
|
298
|
-
metadata.pop("local_only_reason", None)
|
|
299
|
-
conn.execute(
|
|
300
|
-
"UPDATE nodes SET metadata_json=?, updated_at=? WHERE id=?",
|
|
301
|
-
(json.dumps(metadata, ensure_ascii=False), _now(), node_id),
|
|
302
|
-
)
|
|
303
|
-
return {
|
|
304
|
-
"ok": True,
|
|
305
|
-
"node_id": node_id,
|
|
306
|
-
"local_only": bool(local_only),
|
|
307
|
-
"reason": metadata.get("local_only_reason"),
|
|
308
|
-
}
|