ltcai 11.5.2 → 11.7.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 +93 -148
- package/bin/ltcai.js +234 -24
- package/docs/CHANGELOG.md +119 -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 +54 -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-BPcVAbOL.js} +1 -1
- package/static/app/assets/AdminConsole-Bw1ATQL0.js +1 -0
- package/static/app/assets/{Brain-3VCSHFcn.js → Brain-CT92Kos0.js} +2 -2
- package/static/app/assets/{BrainHome-Qm8eaztx.js → BrainHome-CFBkt1K_.js} +1 -1
- package/static/app/assets/{BrainSignals-DS9BtKOW.js → BrainSignals-ReLWF2H8.js} +1 -1
- package/static/app/assets/Capture-BsTokYkk.js +1 -0
- package/static/app/assets/{Chronicle-BGvuAchH.js → Chronicle-B6f0T9id.js} +1 -1
- package/static/app/assets/{CommandPalette-Bqhm0Urn.js → CommandPalette-CuvjTv1u.js} +1 -1
- package/static/app/assets/{Library-BV6NnF0a.js → Library-BGJbG9Hd.js} +1 -1
- package/static/app/assets/{LivingBrain-GzenJchP.js → LivingBrain-DGYK_Jsa.js} +1 -1
- package/static/app/assets/{ProductFlow-DEP6-vML.js → ProductFlow-DXBC6brE.js} +1 -1
- package/static/app/assets/{ReviewCard-CNZ7XjWG.js → ReviewCard-HXRle3qq.js} +2 -2
- package/static/app/assets/System-CMHSO9qM.js +1 -0
- package/static/app/assets/arrow-left-BfmkskWx.js +1 -0
- package/static/app/assets/{bot-B_K1Tdmw.js → bot-Cn8bWRuq.js} +1 -1
- package/static/app/assets/{brain-DWyaV1L1.js → brain-CQJberbE.js} +1 -1
- package/static/app/assets/{button-aTn4s84A.js → button-Ct9f2_oT.js} +1 -1
- package/static/app/assets/circle-check-DruOxB-4.js +1 -0
- package/static/app/assets/{circle-pause-xKgeGXkT.js → circle-pause-CmzC_apg.js} +1 -1
- package/static/app/assets/{circle-play-DkT6tYPX.js → circle-play-D8mW2aQ7.js} +1 -1
- package/static/app/assets/{cpu-85xYObUC.js → cpu-DZcdd0PZ.js} +1 -1
- package/static/app/assets/{download-B5Fm7YXo.js → download-bv1KEPGQ.js} +1 -1
- package/static/app/assets/{folder-open-kk2Xa52u.js → folder-open-d-Pip5gr.js} +1 -1
- package/static/app/assets/{hard-drive-DkA3zBW_.js → hard-drive-D20iavUb.js} +1 -1
- package/static/app/assets/index-D9x-kSNy.css +2 -0
- package/static/app/assets/{index-BMPdTmlY.js → index-Do83hDzJ.js} +3 -3
- package/static/app/assets/{input-B0nRf2jO.js → input-BLXVNmj1.js} +1 -1
- package/static/app/assets/{link-2-Dwb4gnTc.js → link-2-BPJOFlAy.js} +1 -1
- package/static/app/assets/{permissionCopy-CQDUBrOZ.js → permissionCopy-ChdJd493.js} +1 -1
- package/static/app/assets/primitives-Cv5tbZBY.js +1 -0
- package/static/app/assets/search-CT9aho2j.js +1 -0
- package/static/app/assets/{share-2-BsrxFglO.js → share-2-YNX_NtMU.js} +1 -1
- package/static/app/assets/{shield-alert-5BStfp2_.js → shield-alert-DuQ3zrVL.js} +1 -1
- package/static/app/assets/{textarea-Cg8IUA-k.js → textarea-DqwLnli4.js} +1 -1
- package/static/app/assets/{useFocusTrap-CYKvE46M.js → useFocusTrap-ZVI98jaW.js} +1 -1
- package/static/app/assets/{useMutation-CSn9t1op.js → useMutation-CVC4qv_D.js} +1 -1
- package/static/app/assets/{useQuery-CY2OI2uy.js → useQuery-C7BeG4HU.js} +1 -1
- package/static/app/assets/{utils-Ddol2RWD.js → utils-CiFtIdZq.js} +1 -1
- package/static/app/assets/{workspace-BqDwOz_p.js → workspace-DQz9vIId.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/AdminConsole-cf4npybT.js +0 -1
- package/static/app/assets/Capture-DiQ219jW.js +0 -1
- package/static/app/assets/System-CieofHQa.js +0 -1
- 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/primitives-SNp0LRJz.js +0 -1
- package/static/app/assets/search-BcHqkjoy.js +0 -1
|
@@ -1,105 +1,58 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""What this machine will do with a picture, a recording, or a video.
|
|
2
2
|
|
|
3
3
|
Source → normalize → content hash → (file | text) ingest → provenance
|
|
4
4
|
|
|
5
|
-
:meth:`IngestionPipeline.ingest`
|
|
6
|
-
reporting, provenance capture, the advisory quality
|
|
7
|
-
every ingest through the shared ``dispatch_tool``
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
That was this module: :meth:`IngestionPipeline.ingest` owned normalization, the
|
|
6
|
+
gate reads, idempotency reporting, provenance capture, the advisory quality
|
|
7
|
+
annotation, and routing every ingest through the shared ``dispatch_tool``
|
|
8
|
+
lifecycle. The per-source doors lived in ``routing.py``, the folder walk in
|
|
9
|
+
``folders.py`` and background scheduling in ``jobs_api.py``.
|
|
10
|
+
|
|
11
|
+
v11.6.0 §Wave 2.5 made Rust the single writer of the Brain, so the whole write
|
|
12
|
+
side moved to ``lattice-core``'s graph write engine and ``lattice-jobs``. What
|
|
13
|
+
a stateless worker still owns is the *capability question* behind
|
|
14
|
+
``GET /api/ingestion/multimodal``: which gates are on, which model-backed ports
|
|
15
|
+
actually resolved, and — if a video would be refused — which of the three
|
|
16
|
+
reasons applies. The answer is derived from this process's environment and its
|
|
17
|
+
injected ports, which is why it is still answered here and not natively.
|
|
12
18
|
"""
|
|
13
19
|
|
|
14
20
|
from __future__ import annotations
|
|
15
21
|
|
|
16
|
-
from typing import Any, Dict,
|
|
22
|
+
from typing import Any, Dict, Optional
|
|
17
23
|
|
|
18
|
-
from ..graph.vector_index import DEFAULT_TICK_LIMIT as VECTOR_TICK_LIMIT
|
|
19
|
-
from ..ingestion_jobs import BackgroundIngestionQueue
|
|
20
24
|
from ..multimodal import (
|
|
21
25
|
DEFAULT_KEYFRAMES,
|
|
22
|
-
MODALITY_AUDIO,
|
|
23
|
-
MODALITY_IMAGE,
|
|
24
|
-
MODALITY_VIDEO,
|
|
25
26
|
VIDEO_UNAVAILABLE_DETAIL,
|
|
26
27
|
MultimodalPorts,
|
|
27
28
|
ffmpeg_available,
|
|
28
29
|
)
|
|
29
|
-
from ..quiet import quiet
|
|
30
|
-
from ..runtime.hooks import dispatch_tool
|
|
31
|
-
from ..utils import utc_now_iso
|
|
32
30
|
from .constants import (
|
|
33
31
|
ALLOW_MULTIMODAL_ENV,
|
|
34
32
|
ALLOW_VIDEO_ENV,
|
|
35
|
-
AUTO_VECTOR_INDEX_GATE,
|
|
36
|
-
CHAT_SOURCE_TYPES,
|
|
37
|
-
DEFAULT_MAX_TEXT_BYTES,
|
|
38
|
-
FILE_SOURCE_TYPES,
|
|
39
|
-
MEMORY_SOURCE_TYPES,
|
|
40
33
|
MULTIMODAL_GATE,
|
|
41
34
|
VIDEO_GATE,
|
|
42
35
|
)
|
|
43
|
-
from .folders import IngestionFolderMixin
|
|
44
|
-
from .jobs_api import IngestionJobsMixin
|
|
45
|
-
from .models import IngestionItem, IngestionResult
|
|
46
|
-
from .quality import QUALITY_LOW_WARNING, assess_extraction_quality
|
|
47
|
-
from .routing import IngestionRoutingMixin
|
|
48
36
|
|
|
49
37
|
|
|
50
|
-
class IngestionPipeline
|
|
51
|
-
|
|
52
|
-
):
|
|
53
|
-
"""Single normalized entrypoint that feeds every source into the graph."""
|
|
38
|
+
class IngestionPipeline:
|
|
39
|
+
"""The multi-modal capability probe. Writes nothing, holds no store."""
|
|
54
40
|
|
|
55
41
|
def __init__(
|
|
56
42
|
self,
|
|
57
|
-
knowledge_graph: Any,
|
|
58
43
|
*,
|
|
59
|
-
hooks: Any = None,
|
|
60
|
-
enable_graph: bool = True,
|
|
61
|
-
audit: Optional[Any] = None,
|
|
62
|
-
max_text_bytes: int = DEFAULT_MAX_TEXT_BYTES,
|
|
63
|
-
pipeline_name: str = "unified-ingestion",
|
|
64
|
-
bg_queue: Optional[BackgroundIngestionQueue] = None,
|
|
65
|
-
auto_vector_index: bool = True,
|
|
66
44
|
allow_multimodal: bool = False,
|
|
67
45
|
multimodal: Optional[MultimodalPorts] = None,
|
|
68
46
|
) -> None:
|
|
69
|
-
self._kg = knowledge_graph
|
|
70
|
-
self._hooks = hooks
|
|
71
|
-
self._enable = bool(enable_graph)
|
|
72
|
-
self._audit = audit
|
|
73
|
-
self._max_text_bytes = int(max_text_bytes)
|
|
74
|
-
self._pipeline_name = pipeline_name
|
|
75
|
-
# Background job state lives in the graph database by default, so a
|
|
76
|
-
# restart resumes from the last completed item instead of replaying the
|
|
77
|
-
# whole corpus. A store without a usable ``db_path`` (mocks, disabled
|
|
78
|
-
# graph) degrades to the historical in-memory queue, which reports
|
|
79
|
-
# itself as non-durable through ``BackgroundIngestionQueue.describe()``.
|
|
80
|
-
self._bg_queue = bg_queue or BackgroundIngestionQueue(
|
|
81
|
-
db_path=getattr(knowledge_graph, "db_path", None)
|
|
82
|
-
)
|
|
83
|
-
# Incremental vector sync after each successful non-duplicate ingest.
|
|
84
|
-
# Constructor opt-out AND gate opt-out (LATTICEAI_AUTO_VECTOR_INDEX=0,
|
|
85
|
-
# or the settings toggle bound to it) both disable it; a vector failure
|
|
86
|
-
# never fails the ingest. The gate half is asked per ingest rather than
|
|
87
|
-
# frozen here, so turning it off takes effect on the next item.
|
|
88
|
-
self._auto_vector_index_opt_in = bool(auto_vector_index)
|
|
89
47
|
# Multi-modal routing. Off unless the caller asks for it *or* the gate
|
|
90
48
|
# says yes — the env behind that gate is the escape hatch for an
|
|
91
49
|
# install with no code path to the constructor (CLI, background
|
|
92
|
-
# worker), and the gate is
|
|
93
|
-
#
|
|
50
|
+
# worker), and the gate is asked per call so a runtime toggle can reach
|
|
51
|
+
# it. A constructor ``True`` is still a permanent yes.
|
|
94
52
|
self._multimodal_opt_in = bool(allow_multimodal)
|
|
95
53
|
self._multimodal = multimodal or MultimodalPorts()
|
|
96
54
|
self._keyframes = DEFAULT_KEYFRAMES
|
|
97
55
|
|
|
98
|
-
@property
|
|
99
|
-
def _auto_vector_index(self) -> bool:
|
|
100
|
-
"""Whether a landed ingest also syncs its vector, asked *now*."""
|
|
101
|
-
return self._auto_vector_index_opt_in and AUTO_VECTOR_INDEX_GATE.enabled()
|
|
102
|
-
|
|
103
56
|
@property
|
|
104
57
|
def _allow_multimodal(self) -> bool:
|
|
105
58
|
"""Whether pictures and recordings route by modality, asked *now*."""
|
|
@@ -114,9 +67,6 @@ class IngestionPipeline(
|
|
|
114
67
|
"""An injected keyframe port counts as a decoder; otherwise, ffmpeg."""
|
|
115
68
|
return self._multimodal.keyframe_extractor is not None or ffmpeg_available()
|
|
116
69
|
|
|
117
|
-
def available(self) -> bool:
|
|
118
|
-
return self._enable and self._kg is not None
|
|
119
|
-
|
|
120
70
|
def multimodal_status(self) -> Dict[str, Any]:
|
|
121
71
|
"""What this pipeline will do with a picture or a recording, honestly.
|
|
122
72
|
|
|
@@ -154,333 +104,5 @@ class IngestionPipeline(
|
|
|
154
104
|
)
|
|
155
105
|
return VIDEO_UNAVAILABLE_DETAIL
|
|
156
106
|
|
|
157
|
-
# ── public API ───────────────────────────────────────────────────────────
|
|
158
|
-
def ingest(self, item: IngestionItem, *, user_email: Optional[str] = None) -> IngestionResult:
|
|
159
|
-
"""Normalize, hash, route through dispatch_tool, and record provenance."""
|
|
160
|
-
source_type = str(item.source_type or "text").strip().lower()
|
|
161
|
-
if not self.available():
|
|
162
|
-
return IngestionResult(
|
|
163
|
-
status="unavailable", source_type=source_type,
|
|
164
|
-
indexing_status="skipped",
|
|
165
|
-
detail="Knowledge Graph is disabled (LATTICEAI_ENABLE_GRAPH).",
|
|
166
|
-
)
|
|
167
|
-
|
|
168
|
-
# Modality routing is a no-op while the flag is off: ``modality`` stays
|
|
169
|
-
# "text" and every branch below behaves exactly as it did before.
|
|
170
|
-
modality = self._modality_for(item, source_type)
|
|
171
|
-
if modality == MODALITY_VIDEO and not self._allow_video:
|
|
172
|
-
# Recognized and refused, with the reason that actually applies
|
|
173
|
-
# right now — a missing decoder is not the same answer as a
|
|
174
|
-
# switched-off feature, and the caller can act on the difference.
|
|
175
|
-
return IngestionResult(
|
|
176
|
-
status="unavailable", source_type=source_type,
|
|
177
|
-
indexing_status="skipped", detail=self._video_refusal(),
|
|
178
|
-
)
|
|
179
|
-
|
|
180
|
-
captured_at = item.captured_at or utc_now_iso()
|
|
181
|
-
owner = item.owner or user_email
|
|
182
|
-
tool_name = f"kg_ingest.{source_type}"
|
|
183
|
-
# Only the keys are read by the hook payload, so this dict is safe/cheap.
|
|
184
|
-
args = {
|
|
185
|
-
"source_type": source_type,
|
|
186
|
-
"source_uri": item.source_uri,
|
|
187
|
-
"owner": owner,
|
|
188
|
-
"workspace_id": item.workspace_id,
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
def _run() -> Dict[str, Any]:
|
|
192
|
-
if source_type in CHAT_SOURCE_TYPES:
|
|
193
|
-
return self._ingest_chat(item, source_type=source_type, owner=owner)
|
|
194
|
-
if source_type in MEMORY_SOURCE_TYPES:
|
|
195
|
-
return self._ingest_memory_record(item, source_type=source_type, owner=owner)
|
|
196
|
-
if modality == MODALITY_IMAGE:
|
|
197
|
-
return self._ingest_image(item, source_type=source_type, owner=owner, captured_at=captured_at)
|
|
198
|
-
if modality == MODALITY_AUDIO:
|
|
199
|
-
return self._ingest_audio(item, source_type=source_type, owner=owner, captured_at=captured_at)
|
|
200
|
-
if modality == MODALITY_VIDEO:
|
|
201
|
-
return self._ingest_video(item, source_type=source_type, owner=owner, captured_at=captured_at)
|
|
202
|
-
if source_type in FILE_SOURCE_TYPES or (item.path and not item.text):
|
|
203
|
-
return self._ingest_file(item, source_type=source_type, owner=owner, captured_at=captured_at)
|
|
204
|
-
return self._ingest_text(item, source_type=source_type, owner=owner, captured_at=captured_at)
|
|
205
|
-
|
|
206
|
-
# v9.8.0 observation-only quality gate: computed *before* the write so
|
|
207
|
-
# the search never matches the node we are about to create. It is
|
|
208
|
-
# recorded on the result and never skips an ingest (behavior unchanged).
|
|
209
|
-
quality_text = self._extractable_text(item)
|
|
210
|
-
quality_gate = self._observe_quality_gate(
|
|
211
|
-
item, source_type=source_type, text=quality_text,
|
|
212
|
-
)
|
|
213
|
-
|
|
214
|
-
try:
|
|
215
|
-
raw = dispatch_tool(
|
|
216
|
-
self._hooks, tool_name, args, _run,
|
|
217
|
-
user_email=user_email, workspace_id=item.workspace_id, source="ingestion",
|
|
218
|
-
)
|
|
219
|
-
except PermissionError as exc:
|
|
220
|
-
return IngestionResult(
|
|
221
|
-
status="blocked", source_type=source_type,
|
|
222
|
-
indexing_status="skipped", detail=str(exc),
|
|
223
|
-
)
|
|
224
|
-
except FileNotFoundError as exc:
|
|
225
|
-
return IngestionResult(
|
|
226
|
-
status="failed", source_type=source_type,
|
|
227
|
-
indexing_status="failed", detail=str(exc),
|
|
228
|
-
)
|
|
229
|
-
except Exception as exc: # noqa: BLE001 — surface as a failed result, never crash the caller
|
|
230
|
-
return IngestionResult(
|
|
231
|
-
status="failed", source_type=source_type,
|
|
232
|
-
indexing_status="failed", detail=str(exc),
|
|
233
|
-
)
|
|
234
|
-
|
|
235
|
-
node_id = raw.get("node_id")
|
|
236
|
-
content_hash = raw.get("content_hash") or raw.get("sha256")
|
|
237
|
-
chunk_ids = list(raw.get("chunk_ids") or [])
|
|
238
|
-
title = raw.get("title") or item.title
|
|
239
|
-
|
|
240
|
-
# Incremental vector-index sync (opt-in via auto_vector_index +
|
|
241
|
-
# LATTICEAI_AUTO_VECTOR_INDEX). Exception-safe by contract: the graph
|
|
242
|
-
# write above already landed, so a vector failure only downgrades
|
|
243
|
-
# indexing_status to "pending" — index_status()/rebuild_vector_index()
|
|
244
|
-
# discover the same node as backlog and pick it up later.
|
|
245
|
-
indexing_status = "indexed"
|
|
246
|
-
vector_detail: Optional[str] = None
|
|
247
|
-
if node_id and self._auto_vector_index and not bool(raw.get("duplicate")):
|
|
248
|
-
indexing_status, vector_detail = self._sync_vector_index(node_id)
|
|
249
|
-
embedded = bool(self._kg.node_is_embedded(node_id)) if node_id else False
|
|
250
|
-
|
|
251
|
-
# Provenance capture must never turn an already-persisted ingest into a
|
|
252
|
-
# caller-visible failure: the graph write above succeeded, so a broken
|
|
253
|
-
# provenance table degrades the result instead of raising.
|
|
254
|
-
provenance_detail: Optional[str] = None
|
|
255
|
-
try:
|
|
256
|
-
prov = self._kg.record_provenance(
|
|
257
|
-
node_id=node_id,
|
|
258
|
-
source_type=source_type,
|
|
259
|
-
pipeline=self._pipeline_name,
|
|
260
|
-
source_uri=item.source_uri,
|
|
261
|
-
content_hash=content_hash,
|
|
262
|
-
title=title,
|
|
263
|
-
owner=owner,
|
|
264
|
-
workspace_id=item.workspace_id,
|
|
265
|
-
captured_at=captured_at,
|
|
266
|
-
modified_at=item.modified_at,
|
|
267
|
-
embedded=embedded,
|
|
268
|
-
linked=bool(raw.get("source_node_id")),
|
|
269
|
-
duplicate=bool(raw.get("duplicate")),
|
|
270
|
-
agent_used=item.agent_used,
|
|
271
|
-
chunk_count=len(chunk_ids),
|
|
272
|
-
permissions=item.permissions,
|
|
273
|
-
metadata=item.metadata,
|
|
274
|
-
)
|
|
275
|
-
except Exception as exc: # noqa: BLE001 — the ingest itself already landed
|
|
276
|
-
prov = {}
|
|
277
|
-
provenance_detail = f"provenance capture failed: {exc}"
|
|
278
|
-
if self._audit is not None:
|
|
279
|
-
try:
|
|
280
|
-
self._audit(
|
|
281
|
-
"kg_ingest",
|
|
282
|
-
{
|
|
283
|
-
"source_type": source_type, "node_id": node_id,
|
|
284
|
-
"content_hash": content_hash, "duplicate": bool(raw.get("duplicate")),
|
|
285
|
-
},
|
|
286
|
-
user_email,
|
|
287
|
-
)
|
|
288
|
-
except Exception: # noqa: BLE001 — audit must never break ingestion
|
|
289
|
-
quiet()
|
|
290
|
-
|
|
291
|
-
# A modality-aware door scores its own extraction (a picture's quality
|
|
292
|
-
# is "how much of it can be retrieved", not "does the text read well"),
|
|
293
|
-
# so its verdict wins. Text/file doors never set the key and keep the
|
|
294
|
-
# historical scoring untouched.
|
|
295
|
-
extraction_quality = raw.get("extraction_quality") or self._assess_item_quality(
|
|
296
|
-
item, source_type=source_type, text=quality_text, chunk_ids=chunk_ids,
|
|
297
|
-
)
|
|
298
|
-
warnings: List[str] = []
|
|
299
|
-
if extraction_quality is not None and extraction_quality.get("level") == "low":
|
|
300
|
-
warnings.append(QUALITY_LOW_WARNING)
|
|
301
|
-
|
|
302
|
-
details = [d for d in (provenance_detail, vector_detail) if d]
|
|
303
|
-
return IngestionResult(
|
|
304
|
-
status="ok",
|
|
305
|
-
source_type=source_type,
|
|
306
|
-
node_id=node_id,
|
|
307
|
-
source_node_id=raw.get("source_node_id"),
|
|
308
|
-
content_hash=content_hash,
|
|
309
|
-
title=title,
|
|
310
|
-
chunk_ids=chunk_ids,
|
|
311
|
-
chunk_count=len(chunk_ids),
|
|
312
|
-
duplicate=bool(raw.get("duplicate")),
|
|
313
|
-
embedded=embedded,
|
|
314
|
-
indexing_status=indexing_status,
|
|
315
|
-
provenance_id=prov.get("id"),
|
|
316
|
-
detail="; ".join(details) if details else None,
|
|
317
|
-
extraction_quality=extraction_quality,
|
|
318
|
-
warnings=warnings,
|
|
319
|
-
quality_gate=quality_gate,
|
|
320
|
-
)
|
|
321
|
-
|
|
322
|
-
# ── extraction quality (v9.8.0 A1 — advisory, never gates) ───────────────
|
|
323
|
-
@staticmethod
|
|
324
|
-
def _extractable_text(item: IngestionItem) -> Optional[str]:
|
|
325
|
-
"""Best available extracted text for quality scoring/gating."""
|
|
326
|
-
if item.text is not None:
|
|
327
|
-
return item.text
|
|
328
|
-
extracted = (item.metadata or {}).get("extracted")
|
|
329
|
-
if isinstance(extracted, dict):
|
|
330
|
-
content = extracted.get("content") or extracted.get("text")
|
|
331
|
-
if content is not None:
|
|
332
|
-
return str(content)
|
|
333
|
-
return None
|
|
334
|
-
|
|
335
|
-
@staticmethod
|
|
336
|
-
def _upstream_confidence(item: IngestionItem) -> Optional[Any]:
|
|
337
|
-
"""Upstream extractor confidence, if the capture surface supplied one."""
|
|
338
|
-
meta = item.metadata or {}
|
|
339
|
-
extracted = meta.get("extracted")
|
|
340
|
-
if isinstance(extracted, dict) and extracted.get("confidence") is not None:
|
|
341
|
-
return extracted.get("confidence")
|
|
342
|
-
if meta.get("extraction_confidence") is not None:
|
|
343
|
-
return meta.get("extraction_confidence")
|
|
344
|
-
return None
|
|
345
107
|
|
|
346
|
-
|
|
347
|
-
self,
|
|
348
|
-
item: IngestionItem,
|
|
349
|
-
*,
|
|
350
|
-
source_type: str,
|
|
351
|
-
text: Optional[str],
|
|
352
|
-
chunk_ids: List[str],
|
|
353
|
-
) -> Optional[Dict[str, Any]]:
|
|
354
|
-
"""Quality annotation for document-like sources (not chat/memory)."""
|
|
355
|
-
if source_type in CHAT_SOURCE_TYPES or source_type in MEMORY_SOURCE_TYPES:
|
|
356
|
-
return None
|
|
357
|
-
confidence = self._upstream_confidence(item)
|
|
358
|
-
if text is not None or confidence is not None:
|
|
359
|
-
return assess_extraction_quality(
|
|
360
|
-
text, source_type=source_type, upstream_confidence=confidence,
|
|
361
|
-
)
|
|
362
|
-
# File door without inline extraction (e.g. PDF): the pipeline never saw
|
|
363
|
-
# the text, so score honestly from the chunk output instead of guessing.
|
|
364
|
-
if chunk_ids:
|
|
365
|
-
return {
|
|
366
|
-
"score": 0.5,
|
|
367
|
-
"level": "medium",
|
|
368
|
-
"reasons": ["content_extracted_upstream_not_scored"],
|
|
369
|
-
}
|
|
370
|
-
return {"score": 0.0, "level": "low", "reasons": ["no_extracted_text"]}
|
|
371
|
-
|
|
372
|
-
def _observe_quality_gate(
|
|
373
|
-
self,
|
|
374
|
-
item: IngestionItem,
|
|
375
|
-
*,
|
|
376
|
-
source_type: str,
|
|
377
|
-
text: Optional[str],
|
|
378
|
-
) -> Optional[Dict[str, Any]]:
|
|
379
|
-
"""Observation-mode ``gate_ingest_candidate`` wiring.
|
|
380
|
-
|
|
381
|
-
Records what the proactive gate *would* decide (ingest /
|
|
382
|
-
skip_duplicate / review) without ever acting on it. Any failure —
|
|
383
|
-
import, search, gate — yields ``None``; the ingest proceeds untouched.
|
|
384
|
-
"""
|
|
385
|
-
if source_type in CHAT_SOURCE_TYPES or source_type in MEMORY_SOURCE_TYPES:
|
|
386
|
-
return None
|
|
387
|
-
body = str(text or "").strip()
|
|
388
|
-
if not body:
|
|
389
|
-
return None
|
|
390
|
-
try:
|
|
391
|
-
from ..graph.proactive import gate_ingest_candidate
|
|
392
|
-
except Exception: # noqa: BLE001 — optional observation, never required
|
|
393
|
-
return None
|
|
394
|
-
|
|
395
|
-
def _search(query: str) -> Any:
|
|
396
|
-
snippet = str(query or "")[:400]
|
|
397
|
-
try:
|
|
398
|
-
if item.workspace_id:
|
|
399
|
-
return self._kg.search(
|
|
400
|
-
snippet, 20, allowed_workspaces={item.workspace_id},
|
|
401
|
-
)
|
|
402
|
-
return self._kg.search(snippet, 20)
|
|
403
|
-
except TypeError:
|
|
404
|
-
# Older store without workspace-scoped search.
|
|
405
|
-
return self._kg.search(snippet, 20)
|
|
406
|
-
|
|
407
|
-
try:
|
|
408
|
-
gate = gate_ingest_candidate(body, _search)
|
|
409
|
-
except Exception: # noqa: BLE001 — observation must never fail the ingest
|
|
410
|
-
return None
|
|
411
|
-
parts = [str(gate.get("reason") or "")]
|
|
412
|
-
if gate.get("similarity") is not None:
|
|
413
|
-
parts.append(f"similarity={gate.get('similarity')}")
|
|
414
|
-
if gate.get("match_id"):
|
|
415
|
-
parts.append(f"match={gate.get('match_id')}")
|
|
416
|
-
return {
|
|
417
|
-
"action": str(gate.get("action") or "review"),
|
|
418
|
-
"detail": "; ".join(p for p in parts if p),
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
def _queue_pending_embed(self, node_id: str, detail: str) -> bool:
|
|
422
|
-
"""Hand a node the inline sync could not embed to the background queue.
|
|
423
|
-
|
|
424
|
-
Before v11.1.0 ``indexing_status="pending"`` was the end of the story:
|
|
425
|
-
honest, but nobody was coming back for it, so the node stayed
|
|
426
|
-
unsearchable until a human ran a rebuild. The durable queue is who
|
|
427
|
-
comes back. A store without one (older stores, mocks) just keeps the
|
|
428
|
-
old behaviour — the node is still visible as ``index_status`` backlog.
|
|
429
|
-
"""
|
|
430
|
-
queue = getattr(self._kg, "vector_queue", None)
|
|
431
|
-
if queue is None:
|
|
432
|
-
return False
|
|
433
|
-
try:
|
|
434
|
-
return bool(queue.schedule(node_id, detail=detail))
|
|
435
|
-
except Exception: # noqa: BLE001 — queueing must never fail an ingest
|
|
436
|
-
quiet()
|
|
437
|
-
return False
|
|
438
|
-
|
|
439
|
-
def _sync_vector_index(self, node_id: str) -> Tuple[str, Optional[str]]:
|
|
440
|
-
"""Best-effort incremental vector sync → (indexing_status, detail).
|
|
441
|
-
|
|
442
|
-
Any failure — missing method on older stores, embedding provider down,
|
|
443
|
-
storage error — yields ``("pending", detail)`` so a later
|
|
444
|
-
``rebuild_vector_index`` run picks the node up from the backlog, and
|
|
445
|
-
the node is queued for background embedding so that pickup happens on
|
|
446
|
-
its own.
|
|
447
|
-
"""
|
|
448
|
-
sync = getattr(self._kg, "index_node_incremental", None)
|
|
449
|
-
if not callable(sync):
|
|
450
|
-
# Older store without the incremental path: the write-side already
|
|
451
|
-
# embeds inline, so nothing extra to do.
|
|
452
|
-
return "indexed", None
|
|
453
|
-
try:
|
|
454
|
-
outcome = sync(node_id) or {}
|
|
455
|
-
except Exception as exc: # noqa: BLE001 — vector sync must never fail the ingest
|
|
456
|
-
return "pending", self._pending_detail(node_id, f"vector index sync failed: {exc}")
|
|
457
|
-
if str(outcome.get("status") or "") == "failed":
|
|
458
|
-
reason = outcome.get("detail") or "unknown error"
|
|
459
|
-
return "pending", self._pending_detail(
|
|
460
|
-
node_id, f"vector index sync failed: {reason}"
|
|
461
|
-
)
|
|
462
|
-
return "indexed", None
|
|
463
|
-
|
|
464
|
-
def _pending_detail(self, node_id: str, reason: str) -> str:
|
|
465
|
-
"""``reason``, plus whether a background retry was actually scheduled."""
|
|
466
|
-
if self._queue_pending_embed(node_id, reason):
|
|
467
|
-
return f"{reason}; queued for background embedding"
|
|
468
|
-
return reason
|
|
469
|
-
|
|
470
|
-
def drain_vector_queue(self, limit: int = VECTOR_TICK_LIMIT) -> Dict[str, Any]:
|
|
471
|
-
"""Run one background-embedding tick over the store's pending backlog.
|
|
472
|
-
|
|
473
|
-
Deliberately caller-driven (a scheduler, a CLI, a test) rather than a
|
|
474
|
-
thread this pipeline owns: the queue is durable, so "who runs it" is a
|
|
475
|
-
deployment decision, not a property of having ingested something.
|
|
476
|
-
"""
|
|
477
|
-
queue = getattr(self._kg, "vector_queue", None)
|
|
478
|
-
if queue is None:
|
|
479
|
-
return {
|
|
480
|
-
"claimed": 0,
|
|
481
|
-
"indexed": 0,
|
|
482
|
-
"retried": 0,
|
|
483
|
-
"failed": 0,
|
|
484
|
-
"detail": "this store has no background vector queue",
|
|
485
|
-
}
|
|
486
|
-
return dict(queue.tick(limit))
|
|
108
|
+
__all__ = ["IngestionPipeline"]
|
|
@@ -34,6 +34,12 @@ Brain Core owns none of those models. Every heavy dependency arrives as an
|
|
|
34
34
|
injected callable (:class:`MultimodalPorts`), which is also why this module
|
|
35
35
|
imports nothing from ``latticeai``.
|
|
36
36
|
|
|
37
|
+
v11.6.0 removed the *writing* half — ``write_image_memory``,
|
|
38
|
+
``write_video_memory``, the keyframe writer and the node-id helpers. Extraction
|
|
39
|
+
returns facts; ``lattice-core``'s graph write engine turns them into nodes. What
|
|
40
|
+
is left here is exactly what ``POST /worker/multimodal/describe`` and
|
|
41
|
+
``POST /worker/asr`` answer with.
|
|
42
|
+
|
|
37
43
|
Split into cohesive submodules in v11.3.0 (no behaviour change): ``common``
|
|
38
44
|
(taxonomy + shared helpers), ``ports`` (injected capabilities + the ffmpeg
|
|
39
45
|
fallback), ``images``, ``audio``, ``video``. This module re-exports every name
|
|
@@ -74,16 +80,8 @@ from .common import (
|
|
|
74
80
|
from .common import _sha256_file as _sha256_file
|
|
75
81
|
from .common import _sha256_text as _sha256_text
|
|
76
82
|
from .common import _split_index_text as _split_index_text
|
|
77
|
-
from .images import
|
|
78
|
-
ImageFacts,
|
|
79
|
-
extract_image_facts,
|
|
80
|
-
image_node_id,
|
|
81
|
-
image_quality_score,
|
|
82
|
-
write_image_memory,
|
|
83
|
-
)
|
|
83
|
+
from .images import ImageFacts, extract_image_facts, image_quality_score
|
|
84
84
|
from .images import _apply_vision_embedding as _apply_vision_embedding
|
|
85
|
-
from .images import _attach_concepts as _attach_concepts
|
|
86
|
-
from .images import _attach_source as _attach_source
|
|
87
85
|
from .images import _open_image as _open_image
|
|
88
86
|
from .images import _run_ocr as _run_ocr
|
|
89
87
|
from .images import _safe_caption as _safe_caption
|
|
@@ -112,12 +110,8 @@ from .video import (
|
|
|
112
110
|
find_subtitle,
|
|
113
111
|
parse_subtitles,
|
|
114
112
|
read_video_facts,
|
|
115
|
-
video_frame_dir,
|
|
116
|
-
video_node_id,
|
|
117
113
|
video_quality_score,
|
|
118
|
-
write_video_memory,
|
|
119
114
|
)
|
|
120
|
-
from .video import _write_keyframes as _write_keyframes
|
|
121
115
|
|
|
122
116
|
__all__ = [
|
|
123
117
|
"AUDIO_EXTENSIONS",
|
|
@@ -151,14 +145,9 @@ __all__ = [
|
|
|
151
145
|
"extract_keyframes",
|
|
152
146
|
"ffmpeg_available",
|
|
153
147
|
"find_subtitle",
|
|
154
|
-
"image_node_id",
|
|
155
148
|
"image_quality_score",
|
|
156
149
|
"parse_subtitles",
|
|
157
150
|
"read_video_facts",
|
|
158
151
|
"transcribe_audio",
|
|
159
|
-
"video_frame_dir",
|
|
160
|
-
"video_node_id",
|
|
161
152
|
"video_quality_score",
|
|
162
|
-
"write_image_memory",
|
|
163
|
-
"write_video_memory",
|
|
164
153
|
]
|