ltcai 11.1.0 → 11.4.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 +47 -54
- package/docs/CHANGELOG.md +94 -0
- package/docs/COMMUNITY_AND_PLUGINS.md +1 -1
- package/docs/DEVELOPMENT.md +1 -1
- package/docs/FEATURE_AUDIT_v11.2.0.md +393 -0
- package/docs/LAYOUT_REBUILD_SPEC.md +9 -1
- package/docs/MULTI_AGENT_RUNTIME.md +1 -1
- package/docs/ONBOARDING.md +1 -1
- package/docs/OPERATIONS.md +6 -2
- package/docs/PERMISSION_MODE.md +1 -1
- package/docs/TRUST_MODEL.md +1 -1
- package/docs/WHY_LATTICE.md +1 -1
- package/docs/architecture.md +6 -2
- package/docs/kg-schema.md +2 -2
- package/docs/v11.3.0_PLAN.md +202 -0
- package/docs/v11.4.0_RUST_FOUNDATION_PLAN.md +176 -0
- package/lattice_brain/__init__.py +1 -1
- package/lattice_brain/gates.py +125 -0
- package/lattice_brain/graph/_kg_common/__init__.py +287 -0
- package/lattice_brain/graph/_kg_common/extraction.py +516 -0
- package/lattice_brain/graph/_kg_common/relations.py +161 -0
- package/lattice_brain/graph/_kg_common/text.py +479 -0
- package/lattice_brain/graph/discovery_index/__init__.py +35 -0
- package/lattice_brain/graph/discovery_index/cleanup.py +182 -0
- package/lattice_brain/graph/discovery_index/extract.py +137 -0
- package/lattice_brain/graph/discovery_index/scan.py +411 -0
- package/lattice_brain/graph/discovery_index/upsert.py +495 -0
- package/lattice_brain/graph/fusion.py +35 -4
- package/lattice_brain/graph/projection/__init__.py +42 -0
- package/lattice_brain/graph/projection/curation.py +500 -0
- package/lattice_brain/graph/{projection.py → projection/v2_schema.py} +81 -485
- package/lattice_brain/graph/retrieval/__init__.py +54 -0
- package/lattice_brain/graph/retrieval/context.py +197 -0
- package/lattice_brain/graph/retrieval/graph_view.py +319 -0
- package/lattice_brain/graph/retrieval/hybrid.py +488 -0
- package/lattice_brain/graph/retrieval/maintenance.py +121 -0
- package/lattice_brain/graph/retrieval/signals.py +95 -0
- package/lattice_brain/graph/retrieval_vector/__init__.py +42 -0
- package/lattice_brain/graph/retrieval_vector/fingerprint.py +97 -0
- package/lattice_brain/graph/retrieval_vector/indexing.py +347 -0
- package/lattice_brain/graph/retrieval_vector/search.py +560 -0
- package/lattice_brain/graph/retrieval_vector/status.py +374 -0
- package/lattice_brain/graph/schema.py +9 -0
- package/lattice_brain/graph/store.py +9 -0
- package/lattice_brain/graph/vector_index/selector.py +32 -2
- package/lattice_brain/ingestion/__init__.py +130 -0
- package/lattice_brain/ingestion/_contract.py +90 -0
- package/lattice_brain/ingestion/constants.py +127 -0
- package/lattice_brain/ingestion/folder_scan.py +57 -0
- package/lattice_brain/ingestion/folders.py +258 -0
- package/lattice_brain/ingestion/hashing.py +26 -0
- package/lattice_brain/ingestion/jobs_api.py +107 -0
- package/lattice_brain/ingestion/models.py +80 -0
- package/lattice_brain/ingestion/pipeline.py +486 -0
- package/lattice_brain/ingestion/quality.py +209 -0
- package/lattice_brain/ingestion/routing.py +295 -0
- package/lattice_brain/multimodal/__init__.py +164 -0
- package/lattice_brain/multimodal/audio.py +77 -0
- package/lattice_brain/multimodal/common.py +118 -0
- package/lattice_brain/{multimodal.py → multimodal/images.py} +22 -262
- package/lattice_brain/multimodal/ports.py +169 -0
- package/lattice_brain/multimodal/video.py +410 -0
- package/lattice_brain/portability/__init__.py +90 -0
- package/lattice_brain/portability/_contract.py +42 -0
- package/lattice_brain/portability/backups.py +338 -0
- package/lattice_brain/portability/bundles.py +136 -0
- package/lattice_brain/portability/constants.py +93 -0
- package/lattice_brain/portability/fsops.py +138 -0
- package/lattice_brain/portability/service.py +41 -0
- package/lattice_brain/portability/sharing.py +714 -0
- package/lattice_brain/runtime/__init__.py +1 -1
- package/lattice_brain/runtime/multi_agent.py +1 -1
- package/lattice_brain/sealed_box.py +244 -0
- package/lattice_brain/synthesis.py +24 -1
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/brain_intelligence.py +4 -0
- package/latticeai/api/chat.py +11 -0
- package/latticeai/api/chat_helpers.py +16 -3
- package/latticeai/api/chat_hybrid.py +32 -1
- package/latticeai/api/chronicle.py +63 -0
- package/latticeai/api/features.py +70 -0
- package/latticeai/api/local_files.py +102 -0
- package/latticeai/api/portability.py +39 -4
- package/latticeai/api/review_queue.py +126 -0
- package/latticeai/api/search.py +16 -2
- package/latticeai/core/agent/__init__.py +93 -0
- package/latticeai/core/agent/_contract.py +79 -0
- package/latticeai/core/agent/context.py +57 -0
- package/latticeai/core/agent/deps.py +125 -0
- package/latticeai/core/agent/execution.py +622 -0
- package/latticeai/core/agent/planning.py +145 -0
- package/latticeai/core/agent/recovery.py +157 -0
- package/latticeai/core/agent/runtime.py +210 -0
- package/latticeai/core/agent/verification.py +231 -0
- package/latticeai/core/config.py +4 -1
- package/latticeai/core/context_builder.py +6 -3
- package/latticeai/core/embedding_providers/__init__.py +151 -0
- package/latticeai/core/embedding_providers/base.py +199 -0
- package/latticeai/core/embedding_providers/captions.py +162 -0
- package/latticeai/core/embedding_providers/profiles.py +126 -0
- package/latticeai/core/embedding_providers/text.py +350 -0
- package/latticeai/core/embedding_providers/vision.py +352 -0
- package/latticeai/core/file_generation/__init__.py +115 -0
- package/latticeai/core/file_generation/bundles.py +76 -0
- package/latticeai/core/file_generation/extraction.py +154 -0
- package/latticeai/core/file_generation/inference.py +235 -0
- package/latticeai/core/file_generation/orchestration.py +152 -0
- package/latticeai/core/file_generation/prompting.py +117 -0
- package/latticeai/core/file_generation/repair.py +114 -0
- package/latticeai/core/file_generation/sanitize.py +61 -0
- package/latticeai/core/file_generation/validation.py +201 -0
- package/latticeai/core/legacy_compatibility.py +1 -1
- package/latticeai/core/marketplace.py +1 -1
- package/latticeai/core/messages.py +152 -0
- package/latticeai/core/model_compat.py +73 -2
- package/latticeai/core/workspace_os_constants.py +1 -1
- package/latticeai/integrations/telegram_bot/__init__.py +123 -0
- package/latticeai/integrations/telegram_bot/__main__.py +17 -0
- package/latticeai/integrations/telegram_bot/config.py +86 -0
- package/latticeai/integrations/telegram_bot/dispatch.py +311 -0
- package/latticeai/integrations/telegram_bot/flows.py +478 -0
- package/latticeai/integrations/telegram_bot/helpers.py +322 -0
- package/latticeai/integrations/telegram_bot/screens.py +394 -0
- package/latticeai/models/model_providers.py +12 -4
- package/latticeai/models/router/__init__.py +88 -0
- package/latticeai/models/router/_contract.py +66 -0
- package/latticeai/models/router/branding.py +56 -0
- package/latticeai/models/router/catalog.py +69 -0
- package/latticeai/models/router/documents.py +199 -0
- package/latticeai/models/router/errors.py +37 -0
- package/latticeai/models/router/generation.py +258 -0
- package/latticeai/models/router/loading.py +291 -0
- package/latticeai/models/router/local_models.py +85 -0
- package/latticeai/models/router/registry.py +147 -0
- package/latticeai/runtime/build_phases/__init__.py +82 -0
- package/latticeai/runtime/build_phases/features.py +407 -0
- package/latticeai/runtime/build_phases/foundation.py +555 -0
- package/latticeai/runtime/build_phases/web.py +492 -0
- package/latticeai/runtime/chat_wiring.py +4 -0
- package/latticeai/runtime/feature_toggle_wiring.py +163 -0
- package/latticeai/runtime/router_registration.py +11 -0
- package/latticeai/runtime/runtime_context.py +1 -0
- package/latticeai/services/app_context.py +8 -0
- package/latticeai/services/architecture_readiness.py +48 -19
- package/latticeai/services/automation_intelligence.py +22 -2
- package/latticeai/services/brain_intelligence/__init__.py +58 -0
- package/latticeai/services/brain_intelligence/_contract.py +71 -0
- package/latticeai/services/brain_intelligence/consistency.py +193 -0
- package/latticeai/services/brain_intelligence/constants.py +47 -0
- package/latticeai/services/brain_intelligence/digest.py +258 -0
- package/latticeai/services/brain_intelligence/health.py +331 -0
- package/latticeai/services/brain_intelligence/proposals.py +264 -0
- package/latticeai/services/brain_intelligence/sampling.py +84 -0
- package/latticeai/services/brain_intelligence/service.py +48 -0
- package/latticeai/services/chronicle.py +557 -0
- package/latticeai/services/command_center.py +10 -4
- package/latticeai/services/feature_toggles.py +502 -0
- package/latticeai/services/folder_watch.py +122 -1
- package/latticeai/services/hybrid_chat.py +56 -5
- package/latticeai/services/interop_bridges.py +978 -0
- package/latticeai/services/memory_service/__init__.py +52 -0
- package/latticeai/services/memory_service/_contract.py +100 -0
- package/latticeai/services/memory_service/brief.py +431 -0
- package/latticeai/services/memory_service/constants.py +57 -0
- package/latticeai/services/memory_service/maintenance.py +138 -0
- package/latticeai/services/memory_service/manager.py +186 -0
- package/latticeai/services/memory_service/proof.py +136 -0
- package/latticeai/services/memory_service/recall.py +225 -0
- package/latticeai/services/memory_service/service.py +48 -0
- package/latticeai/services/memory_service/stores.py +110 -0
- package/latticeai/services/model_capability_registry.py +434 -261
- package/latticeai/services/model_catalog.py +95 -61
- package/latticeai/services/model_recommendation.py +18 -11
- package/latticeai/services/model_runtime/__init__.py +322 -0
- package/latticeai/services/model_runtime/cloud.py +87 -0
- package/latticeai/services/model_runtime/download.py +282 -0
- package/latticeai/services/model_runtime/engines.py +341 -0
- package/latticeai/services/model_runtime/loading.py +178 -0
- package/latticeai/services/model_runtime/service.py +129 -0
- package/latticeai/services/model_runtime/state.py +131 -0
- package/latticeai/services/model_runtime/status.py +255 -0
- package/latticeai/services/multimodal_ports.py +26 -1
- package/latticeai/services/obsidian_bridge.py +16 -25
- package/latticeai/services/product_readiness.py +15 -7
- package/latticeai/services/search_service.py +149 -2
- package/latticeai/services/tool_dispatch.py +4 -0
- package/latticeai/setup/auto_setup.py +27 -30
- package/latticeai/setup/wizard/__init__.py +126 -0
- package/latticeai/setup/wizard/catalog.py +172 -0
- package/latticeai/setup/wizard/detect.py +323 -0
- package/latticeai/setup/wizard/install.py +348 -0
- package/latticeai/setup/wizard/paths.py +168 -0
- package/latticeai/setup/wizard/plans.py +74 -0
- package/latticeai/setup/wizard/recommend.py +320 -0
- package/package.json +6 -2
- package/scripts/bump_version.py +14 -0
- package/scripts/capture_release_evidence.mjs +33 -21
- package/scripts/check_current_release_docs.mjs +1 -1
- package/scripts/check_i18n_namespace_coverage.mjs +41 -4
- package/scripts/check_max_file_lines.mjs +102 -0
- package/scripts/check_release_evidence_bound.mjs +30 -15
- package/scripts/check_screenshot_pixel_delta.py +34 -4
- package/scripts/check_server_i18n.mjs +2 -0
- package/scripts/generate_rust_parity_fixtures.py +562 -0
- package/scripts/lib/mock_server_fingerprint.mjs +94 -0
- package/scripts/release_screen_claims.json +44 -2
- package/scripts/verify_hf_model_registry.py +253 -218
- package/src-tauri/Cargo.lock +361 -3
- package/src-tauri/Cargo.toml +6 -1
- package/src-tauri/src/backend.rs +349 -0
- package/src-tauri/src/folder.rs +33 -0
- package/src-tauri/src/main.rs +97 -399
- package/src-tauri/tauri.conf.json +1 -1
- package/static/app/asset-manifest.json +41 -37
- package/static/app/assets/Act-yYpYnn0v.js +1 -0
- package/static/app/assets/AdminConsole-DL3Cr5pL.js +1 -0
- package/static/app/assets/{Brain-CzCsI1mi.js → Brain-C1HBN0Wf.js} +2 -2
- package/static/app/assets/BrainHome-DoXRhUUC.js +2 -0
- package/static/app/assets/BrainSignals-6yR6ir5t.js +1 -0
- package/static/app/assets/Capture-CFIRsFNE.js +1 -0
- package/static/app/assets/Chronicle-BZbEgiwN.js +1 -0
- package/static/app/assets/CommandPalette-D2pMxC2I.js +1 -0
- package/static/app/assets/Library-DwO3yZST.js +1 -0
- package/static/app/assets/{LivingBrain-BXMWIK_2.js → LivingBrain-Jn1GK0-S.js} +1 -1
- package/static/app/assets/ProductFlow-B-w1R4Oo.js +1 -0
- package/static/app/assets/ReviewCard-6B27X8Vg.js +3 -0
- package/static/app/assets/System-DW8F-2xL.js +1 -0
- package/static/app/assets/arrow-left-DXvKg9U6.js +1 -0
- package/static/app/assets/{bot-4BvN07ux.js → bot-IM_E_Y12.js} +1 -1
- package/static/app/assets/brain-Ci1CkWjM.js +1 -0
- package/static/app/assets/{button-CDjtnAoU.js → button-COwyqfHM.js} +1 -1
- package/static/app/assets/circle-check-DfInj-qD.js +1 -0
- package/static/app/assets/{circle-pause-D_RMn7tp.js → circle-pause-DEM4A1Y5.js} +1 -1
- package/static/app/assets/{circle-play-B5OpB8ae.js → circle-play-C9djDuLd.js} +1 -1
- package/static/app/assets/{cpu-BIlWInHf.js → cpu-DFdo1gw-.js} +1 -1
- package/static/app/assets/{download-BtjXfL3z.js → download-SnJL6oqk.js} +1 -1
- package/static/app/assets/{folder-open-DefMpxI2.js → folder-open-CqZeDkjE.js} +1 -1
- package/static/app/assets/{hard-drive-BQ8NZVkw.js → hard-drive-j1jJXYYf.js} +1 -1
- package/static/app/assets/{index-vtEfYvQY.css → index-BLPb5lmE.css} +1 -1
- package/static/app/assets/index-_u5iUHDr.js +10 -0
- package/static/app/assets/input-B0lPdRQZ.js +1 -0
- package/static/app/assets/link-2-CoFbooHS.js +1 -0
- package/static/app/assets/{permissionCopy-BqZ5tsgL.js → permissionCopy-BsyLxtao.js} +1 -1
- package/static/app/assets/primitives-DEbN-d6p.js +1 -0
- package/static/app/assets/search-BybIWPNd.js +1 -0
- package/static/app/assets/{share-2-D5zg_0fY.js → share-2-CVtZ_ewX.js} +1 -1
- package/static/app/assets/{shield-alert-B5pZzkUb.js → shield-alert-CBi2GNWM.js} +1 -1
- package/static/app/assets/{textarea-nEVIweKY.js → textarea-DNMpB5ih.js} +1 -1
- package/static/app/assets/{useFocusTrap-Cm99AHlz.js → useFocusTrap-C83t3GXF.js} +1 -1
- package/static/app/assets/useMutation-DtbJDoyz.js +1 -0
- package/static/app/assets/{useQuery-Dm__N6bL.js → useQuery-Dcp1OChy.js} +1 -1
- package/static/app/assets/utils-BlZr7Pd4.js +4 -0
- package/static/app/assets/workspace-jJY4RuAV.js +1 -0
- package/static/app/index.html +4 -4
- package/static/sw.js +1 -1
- package/lattice_brain/graph/_kg_common.py +0 -1331
- package/lattice_brain/graph/discovery_index.py +0 -1141
- package/lattice_brain/graph/retrieval.py +0 -1120
- package/lattice_brain/graph/retrieval_vector.py +0 -1293
- package/lattice_brain/ingestion.py +0 -1377
- package/lattice_brain/portability.py +0 -1210
- package/latticeai/core/agent.py +0 -1412
- package/latticeai/core/embedding_providers.py +0 -1196
- package/latticeai/core/file_generation.py +0 -1047
- package/latticeai/integrations/telegram_bot.py +0 -1390
- package/latticeai/models/router.py +0 -1007
- package/latticeai/runtime/build_phases.py +0 -1422
- package/latticeai/services/brain_intelligence.py +0 -967
- package/latticeai/services/memory_service.py +0 -1177
- package/latticeai/services/model_runtime.py +0 -1281
- package/latticeai/setup/wizard.py +0 -1277
- package/static/app/assets/Act-D0HWqtn0.js +0 -1
- package/static/app/assets/AdminConsole-D-QDW-A4.js +0 -1
- package/static/app/assets/BrainHome-Btns-_TA.js +0 -2
- package/static/app/assets/BrainSignals-2dHQNkns.js +0 -1
- package/static/app/assets/Capture-CT8v1StE.js +0 -1
- package/static/app/assets/CommandPalette-DoLXC2KH.js +0 -1
- package/static/app/assets/Library-DDoxFE5c.js +0 -1
- package/static/app/assets/ProductFlow-DOYf7JIs.js +0 -1
- package/static/app/assets/ReviewCard-COQsqidK.js +0 -3
- package/static/app/assets/System-BRllvYXd.js +0 -1
- package/static/app/assets/arrow-left-DnyMzss-.js +0 -1
- package/static/app/assets/brain-uMb_5hnO.js +0 -1
- package/static/app/assets/index-0AvoEBzJ.js +0 -10
- package/static/app/assets/input-B_5ZJ9oy.js +0 -1
- package/static/app/assets/primitives-CVwew78r.js +0 -1
- package/static/app/assets/search-DkhnOKZt.js +0 -1
- package/static/app/assets/utils-DcDMoZIe.js +0 -4
- package/static/app/assets/workspace-LtRRSKTf.js +0 -1
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
"""Phases 5-8: domain singletons, the web app, services, foundation routes.
|
|
2
|
+
|
|
3
|
+
The middle of the build order. ``phase_domain`` runs before ``phase_web``
|
|
4
|
+
because the lifespan, the static status routes and the model runtime service
|
|
5
|
+
are all wired against the router it builds; ``phase_services`` runs after
|
|
6
|
+
``phase_web`` because the AppContext carries handles the web phase produces.
|
|
7
|
+
Split out of the single ``build_phases`` module in v11.3.0.
|
|
8
|
+
|
|
9
|
+
Every heavy import lives *inside* a phase, never at module scope.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import logging
|
|
15
|
+
from typing import Any, Dict, Optional
|
|
16
|
+
|
|
17
|
+
from latticeai.runtime.runtime_context import RuntimeContext
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# ── phase 5: domain singletons (must precede the web app) ────────────────────
|
|
21
|
+
def phase_domain(ctx: RuntimeContext) -> None:
|
|
22
|
+
"""The model router, the garden, and the chat service.
|
|
23
|
+
|
|
24
|
+
Separated from :func:`phase_services` because ``phase_web`` needs the model
|
|
25
|
+
router: the lifespan hooks, the static status routes, and the model runtime
|
|
26
|
+
service are all wired against it.
|
|
27
|
+
"""
|
|
28
|
+
ctx.enter("domain")
|
|
29
|
+
|
|
30
|
+
from lattice_brain.graph.runtime import set_llm_router
|
|
31
|
+
from latticeai.models.router import LLMRouter
|
|
32
|
+
from latticeai.services.chat_service import ChatService
|
|
33
|
+
from latticeai.services.p_reinforce import PReinforceGardener
|
|
34
|
+
from latticeai.services.tool_dispatch import configure_tool_dispatch
|
|
35
|
+
|
|
36
|
+
model_router = LLMRouter()
|
|
37
|
+
set_llm_router(model_router)
|
|
38
|
+
configure_tool_dispatch(load_users=ctx.load_users, get_user_role=ctx.get_user_role)
|
|
39
|
+
ctx.set(model_router=model_router)
|
|
40
|
+
|
|
41
|
+
# v4 garden absorption: the vault is the user-owned markdown mirror and the
|
|
42
|
+
# brain is authoritative. Existing notes import idempotently (content-hash
|
|
43
|
+
# dedup), and garden context queries the brain rather than rescanning.
|
|
44
|
+
gardener = PReinforceGardener(
|
|
45
|
+
ingestion_pipeline=ctx.INGESTION_PIPELINE if ctx.ENABLE_GRAPH else None,
|
|
46
|
+
knowledge_graph=ctx.KNOWLEDGE_GRAPH,
|
|
47
|
+
)
|
|
48
|
+
if ctx.ENABLE_GRAPH:
|
|
49
|
+
try:
|
|
50
|
+
garden_import = gardener.import_vault()
|
|
51
|
+
if garden_import.get("failed"):
|
|
52
|
+
logging.warning(
|
|
53
|
+
"garden vault import: %s notes failed to ingest",
|
|
54
|
+
garden_import["failed"],
|
|
55
|
+
)
|
|
56
|
+
except Exception as exc:
|
|
57
|
+
logging.warning("garden vault import skipped: %s", exc)
|
|
58
|
+
ctx.set(gardener=gardener)
|
|
59
|
+
|
|
60
|
+
# The chat service owns persistence and trace behaviour once its user-key
|
|
61
|
+
# dependencies exist.
|
|
62
|
+
ctx.set(
|
|
63
|
+
CHAT_SERVICE=ChatService(
|
|
64
|
+
store=ctx.WORKSPACE_OS,
|
|
65
|
+
get_history=ctx.get_history,
|
|
66
|
+
save_to_history=ctx.save_to_history,
|
|
67
|
+
get_history_user=ctx.get_history_user,
|
|
68
|
+
)
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def self_model_port(workspace_graph: Any) -> Any:
|
|
73
|
+
"""The agent loop's Self-Model port (v11.2.0).
|
|
74
|
+
|
|
75
|
+
11.1.0 built ``executor_prompt_for(self_model_summary=…)`` and had nothing
|
|
76
|
+
to pass it; this is what the composition root passes. ``summary_for_prompt``
|
|
77
|
+
never raises and answers ``""`` for a Brain that knows nothing about its
|
|
78
|
+
owner — which produces exactly the pre-11.2.0 prompt bytes.
|
|
79
|
+
|
|
80
|
+
A module-level factory rather than a closure inside ``phase_services``
|
|
81
|
+
because a port that cannot be called on its own cannot be tested on its own.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
def _summary(*, user_email: Any = None, workspace_id: Any = None) -> str:
|
|
85
|
+
from lattice_brain.self_model import summary_for_prompt
|
|
86
|
+
|
|
87
|
+
graph = workspace_graph()
|
|
88
|
+
if graph is None:
|
|
89
|
+
return ""
|
|
90
|
+
return summary_for_prompt(graph, workspace_id=workspace_id)
|
|
91
|
+
|
|
92
|
+
return _summary
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
# ── phase 6b: retrieval, agent runtime, and the typed AppContext ─────────────
|
|
96
|
+
def phase_services(ctx: RuntimeContext) -> None:
|
|
97
|
+
"""Retrieval/context assembly, the chat agent runtime, and the AppContext.
|
|
98
|
+
|
|
99
|
+
Runs after ``phase_web`` because the AppContext carries handles the web
|
|
100
|
+
phase produces (``ui_file_response``, ``local_sysinfo``, ``graph_stats``)
|
|
101
|
+
and the telegram mirror needs the lifespan's ``_spawn``.
|
|
102
|
+
"""
|
|
103
|
+
ctx.enter("services")
|
|
104
|
+
|
|
105
|
+
from latticeai.api.chat import build_recent_chat_context
|
|
106
|
+
from latticeai.core.enterprise import capability_registry
|
|
107
|
+
from latticeai.core.mcp_registry import (
|
|
108
|
+
SKILLS_DIR,
|
|
109
|
+
_fetch_skills_marketplace,
|
|
110
|
+
install_skill,
|
|
111
|
+
)
|
|
112
|
+
from latticeai.core.workspace_os import remove_skill_directory
|
|
113
|
+
from latticeai.runtime.chat_wiring import (
|
|
114
|
+
build_chat_agent_runtime_from_context,
|
|
115
|
+
maybe_build_telegram_chat_mirror,
|
|
116
|
+
)
|
|
117
|
+
from latticeai.runtime.context_runtime import build_context_runtime
|
|
118
|
+
from latticeai.services.app_context import AppContext
|
|
119
|
+
from latticeai.services.tool_dispatch import build_agent_runtime
|
|
120
|
+
from latticeai.setup.wizard import get_recommendations, scan_environment
|
|
121
|
+
from latticeai.tools import execute_tool, knowledge_save
|
|
122
|
+
|
|
123
|
+
gardener = ctx.gardener
|
|
124
|
+
model_router = ctx.model_router
|
|
125
|
+
|
|
126
|
+
def _workspace_settings_payload() -> Dict:
|
|
127
|
+
return {
|
|
128
|
+
"mode": ctx.APP_MODE,
|
|
129
|
+
"host": ctx.DEFAULT_HOST,
|
|
130
|
+
"port": ctx.DEFAULT_PORT,
|
|
131
|
+
"require_auth": ctx.REQUIRE_AUTH,
|
|
132
|
+
"enable_graph": ctx.ENABLE_GRAPH,
|
|
133
|
+
"allow_local_models": ctx.ALLOW_LOCAL_MODELS,
|
|
134
|
+
"static_dir": str(ctx.STATIC_DIR),
|
|
135
|
+
"data_dir": str(ctx.DATA_DIR),
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
def _workspace_models_payload() -> Dict:
|
|
139
|
+
return {
|
|
140
|
+
"current_model": ctx.model_router.current_model_id,
|
|
141
|
+
"loaded_models": ctx.model_router.loaded_model_ids,
|
|
142
|
+
"public_model": ctx.PUBLIC_MODEL,
|
|
143
|
+
"local_model": ctx.LOCAL_MODEL,
|
|
144
|
+
"local_draft_model": ctx.LOCAL_DRAFT_MODEL,
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
def _embedding_info() -> dict:
|
|
148
|
+
from latticeai.core.embedding_providers import (
|
|
149
|
+
PROVIDER_TYPES,
|
|
150
|
+
embedding_provider_profiles,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
info = ctx.EMBEDDER.as_dict()
|
|
154
|
+
info["available_providers"] = list(PROVIDER_TYPES)
|
|
155
|
+
info["profile"] = ctx.CONFIG.embedding_profile or ""
|
|
156
|
+
info["profiles"] = embedding_provider_profiles()
|
|
157
|
+
return info
|
|
158
|
+
|
|
159
|
+
def _allowed_workspaces_for(user: Any) -> Any:
|
|
160
|
+
# No-auth local mode is single-user: no scoping. With auth, reads are
|
|
161
|
+
# scoped to the caller's memberships (legacy-global rows need an
|
|
162
|
+
# explicit maintenance-only opt-in).
|
|
163
|
+
if not ctx.REQUIRE_AUTH or not user:
|
|
164
|
+
return None
|
|
165
|
+
return ctx.PLATFORM.allowed_scopes(user)
|
|
166
|
+
|
|
167
|
+
def _recent_chat_context(
|
|
168
|
+
limit: int = 10,
|
|
169
|
+
include_image_missing_replies: bool = True,
|
|
170
|
+
user_email: Optional[str] = None,
|
|
171
|
+
conversation_id: Optional[str] = None,
|
|
172
|
+
workspace_id: Optional[str] = None,
|
|
173
|
+
) -> str:
|
|
174
|
+
return build_recent_chat_context(
|
|
175
|
+
get_history=ctx.get_history,
|
|
176
|
+
limit=limit,
|
|
177
|
+
include_image_missing_replies=include_image_missing_replies,
|
|
178
|
+
user_email=user_email,
|
|
179
|
+
conversation_id=conversation_id,
|
|
180
|
+
workspace_id=workspace_id,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
ctx.set(
|
|
184
|
+
_workspace_settings_payload=_workspace_settings_payload,
|
|
185
|
+
_workspace_models_payload=_workspace_models_payload,
|
|
186
|
+
_embedding_info=_embedding_info,
|
|
187
|
+
_allowed_workspaces_for=_allowed_workspaces_for,
|
|
188
|
+
_recent_chat_context=_recent_chat_context,
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
context_runtime = build_context_runtime(
|
|
192
|
+
graph_store=ctx._workspace_graph(),
|
|
193
|
+
ingestion_pipeline=ctx.INGESTION_PIPELINE,
|
|
194
|
+
memory_service=ctx.MEMORY_SERVICE,
|
|
195
|
+
gardener=gardener,
|
|
196
|
+
require_auth=ctx.REQUIRE_AUTH,
|
|
197
|
+
allowed_scopes_for_user=lambda user_email: ctx.PLATFORM.allowed_scopes(
|
|
198
|
+
user_email
|
|
199
|
+
),
|
|
200
|
+
)
|
|
201
|
+
ctx.adopt(
|
|
202
|
+
context_runtime,
|
|
203
|
+
"SEARCH_SERVICE",
|
|
204
|
+
"BRAIN_MEMORY",
|
|
205
|
+
"CONTEXT_ASSEMBLER",
|
|
206
|
+
"ARTIFACT_LEDGER",
|
|
207
|
+
"_scoped_hybrid_search",
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
# Telegram chat mirror: registered only when ENABLE_TELEGRAM is truthy, so
|
|
211
|
+
# latticeai.api.chat never imports the 45KB telegram module.
|
|
212
|
+
ctx.set(
|
|
213
|
+
on_chat_message=maybe_build_telegram_chat_mirror(
|
|
214
|
+
enable_telegram=ctx.ENABLE_TELEGRAM,
|
|
215
|
+
spawn=ctx._spawn,
|
|
216
|
+
)
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
ctx.set(
|
|
220
|
+
CHAT_AGENT_RUNTIME=build_chat_agent_runtime_from_context(
|
|
221
|
+
build_agent_runtime=build_agent_runtime,
|
|
222
|
+
model_router=model_router,
|
|
223
|
+
execute_tool=execute_tool,
|
|
224
|
+
recent_chat_context=_recent_chat_context,
|
|
225
|
+
clear_history=ctx.clear_history,
|
|
226
|
+
knowledge_save=knowledge_save,
|
|
227
|
+
audit=ctx.append_audit_event,
|
|
228
|
+
hooks=ctx.HOOKS_REGISTRY,
|
|
229
|
+
brain_memory=ctx.BRAIN_MEMORY,
|
|
230
|
+
self_model_summary=self_model_port(
|
|
231
|
+
lambda: ctx.KNOWLEDGE_GRAPH if ctx.ENABLE_GRAPH else None
|
|
232
|
+
),
|
|
233
|
+
)
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
# One typed context object replaces the historical 25-30-kwarg wiring.
|
|
237
|
+
ctx.set(
|
|
238
|
+
app_context=AppContext(
|
|
239
|
+
config=ctx.CONFIG,
|
|
240
|
+
data_dir=ctx.DATA_DIR,
|
|
241
|
+
static_dir=ctx.STATIC_DIR,
|
|
242
|
+
base_dir=ctx.BASE_DIR,
|
|
243
|
+
skills_dir=SKILLS_DIR,
|
|
244
|
+
model_router=model_router,
|
|
245
|
+
workspace_store=ctx.WORKSPACE_OS,
|
|
246
|
+
workspace_service=ctx.WORKSPACE_SERVICE,
|
|
247
|
+
knowledge_graph=ctx.KNOWLEDGE_GRAPH,
|
|
248
|
+
local_kg_watcher=ctx.LOCAL_KG_WATCHER,
|
|
249
|
+
chat_service=ctx.CHAT_SERVICE,
|
|
250
|
+
context_assembler=ctx.CONTEXT_ASSEMBLER,
|
|
251
|
+
artifact_ledger=ctx.ARTIFACT_LEDGER,
|
|
252
|
+
brain_memory=ctx.BRAIN_MEMORY,
|
|
253
|
+
ingestion_pipeline=ctx.INGESTION_PIPELINE if ctx.ENABLE_GRAPH else None,
|
|
254
|
+
chat_agent_runtime=ctx.CHAT_AGENT_RUNTIME,
|
|
255
|
+
gardener=gardener,
|
|
256
|
+
hooks=ctx.HOOKS_REGISTRY,
|
|
257
|
+
realtime_bus=ctx.REALTIME_BUS,
|
|
258
|
+
capability_registry=capability_registry,
|
|
259
|
+
require_user=ctx.require_user,
|
|
260
|
+
require_admin=ctx.require_admin,
|
|
261
|
+
get_current_user=ctx.get_current_user,
|
|
262
|
+
load_users=ctx.load_users,
|
|
263
|
+
get_user_role=ctx.get_user_role,
|
|
264
|
+
enforce_rate_limit=ctx.enforce_rate_limit,
|
|
265
|
+
allowed_workspaces_for=ctx._history_allowed_workspaces_for,
|
|
266
|
+
append_audit_event=ctx.append_audit_event,
|
|
267
|
+
get_audit_log=ctx.get_audit_log,
|
|
268
|
+
get_history=ctx.get_history,
|
|
269
|
+
get_history_user=ctx.get_history_user,
|
|
270
|
+
save_to_history=ctx.save_to_history,
|
|
271
|
+
clear_history=ctx.clear_history,
|
|
272
|
+
clear_conversation=ctx.clear_conversation,
|
|
273
|
+
group_history_conversations=ctx.group_history_conversations,
|
|
274
|
+
get_conversation_messages=ctx.get_conversation_messages,
|
|
275
|
+
conversation_title=ctx.conversation_title,
|
|
276
|
+
enable_graph=ctx.ENABLE_GRAPH,
|
|
277
|
+
require_graph=ctx._require_graph,
|
|
278
|
+
workspace_graph=ctx._workspace_graph,
|
|
279
|
+
graph_stats=ctx._graph_stats_safe,
|
|
280
|
+
# Provider, not a value: REVIEW_QUEUE lands two phases later.
|
|
281
|
+
review_queue=lambda: ctx.REVIEW_QUEUE,
|
|
282
|
+
workspace_models=_workspace_models_payload,
|
|
283
|
+
workspace_settings=_workspace_settings_payload,
|
|
284
|
+
scan_environment=scan_environment,
|
|
285
|
+
local_sysinfo=ctx.local_sysinfo,
|
|
286
|
+
get_recommendations=get_recommendations,
|
|
287
|
+
fetch_skills_marketplace=_fetch_skills_marketplace,
|
|
288
|
+
install_skill=install_skill,
|
|
289
|
+
remove_skill_directory=remove_skill_directory,
|
|
290
|
+
redact_secret_text=ctx.redact_secret_text,
|
|
291
|
+
ui_file_response=ctx.ui_file_response,
|
|
292
|
+
public_model=ctx.PUBLIC_MODEL,
|
|
293
|
+
local_model=ctx.LOCAL_MODEL or "",
|
|
294
|
+
on_chat_message=ctx.on_chat_message,
|
|
295
|
+
funnel_metrics=ctx.FUNNEL_METRICS,
|
|
296
|
+
)
|
|
297
|
+
)
|
|
298
|
+
ctx.app.state.context = ctx.app_context
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
# ── phase 6: web app and foundation routers ──────────────────────────────────
|
|
302
|
+
def phase_web(ctx: RuntimeContext) -> None:
|
|
303
|
+
"""Lifespan, the FastAPI app, model runtime service, foundation routers."""
|
|
304
|
+
ctx.enter("web")
|
|
305
|
+
|
|
306
|
+
from latticeai.api.admin import create_admin_router
|
|
307
|
+
from latticeai.api.auth import create_auth_router
|
|
308
|
+
from latticeai.api.invitations import create_invitations_router
|
|
309
|
+
from latticeai.api.security_dashboard import (
|
|
310
|
+
create_security_router as _create_security_router,
|
|
311
|
+
)
|
|
312
|
+
from latticeai.api.static_routes import create_static_routes_router
|
|
313
|
+
from latticeai.core.policy import policy_matrix
|
|
314
|
+
from latticeai.core.product_hardening import build_product_hardening_status
|
|
315
|
+
from latticeai.core.security import hash_password
|
|
316
|
+
from latticeai.core.users import ensure_user_identity
|
|
317
|
+
from latticeai.runtime.lifespan_runtime import build_lifespan_runtime
|
|
318
|
+
from latticeai.runtime.model_wiring import configure_model_runtime_from_context
|
|
319
|
+
from latticeai.runtime.router_registration import (
|
|
320
|
+
build_auth_admin_security_router_bundle,
|
|
321
|
+
build_static_routes_bundle,
|
|
322
|
+
)
|
|
323
|
+
from latticeai.runtime.web_runtime import build_web_runtime
|
|
324
|
+
from latticeai.services.model_runtime import (
|
|
325
|
+
LOCAL_SERVER_PROCESSES,
|
|
326
|
+
build_model_runtime,
|
|
327
|
+
)
|
|
328
|
+
from latticeai.tools import ensure_agent_root
|
|
329
|
+
|
|
330
|
+
lifespan_runtime = build_lifespan_runtime(
|
|
331
|
+
app_mode=ctx.APP_MODE,
|
|
332
|
+
enable_telegram=ctx.ENABLE_TELEGRAM,
|
|
333
|
+
autoload_models=ctx.AUTOLOAD_MODELS,
|
|
334
|
+
is_public_mode=ctx.IS_PUBLIC_MODE,
|
|
335
|
+
public_model=ctx.PUBLIC_MODEL,
|
|
336
|
+
allow_local_models=ctx.ALLOW_LOCAL_MODELS,
|
|
337
|
+
local_model=ctx.LOCAL_MODEL,
|
|
338
|
+
local_draft_model=ctx.LOCAL_DRAFT_MODEL,
|
|
339
|
+
model_idle_unload_seconds=ctx.MODEL_IDLE_UNLOAD_SECONDS,
|
|
340
|
+
model_router=ctx.model_router,
|
|
341
|
+
local_kg_watcher=ctx.LOCAL_KG_WATCHER,
|
|
342
|
+
local_server_processes=LOCAL_SERVER_PROCESSES,
|
|
343
|
+
logger=logging,
|
|
344
|
+
)
|
|
345
|
+
ctx.adopt(lifespan_runtime, "_spawn", "lifespan")
|
|
346
|
+
|
|
347
|
+
web_runtime = build_web_runtime(
|
|
348
|
+
app_mode=ctx.APP_MODE,
|
|
349
|
+
app_version=ctx.APP_VERSION,
|
|
350
|
+
lifespan=ctx.lifespan,
|
|
351
|
+
default_host=ctx.DEFAULT_HOST,
|
|
352
|
+
default_port=ctx.DEFAULT_PORT,
|
|
353
|
+
cors_extra_origins=ctx.CORS_EXTRA_ORIGINS,
|
|
354
|
+
cors_allow_network=ctx.CORS_ALLOW_NETWORK,
|
|
355
|
+
static_dir=ctx.STATIC_DIR,
|
|
356
|
+
csrf_trusted_origins=ctx.CSRF_TRUSTED_ORIGINS,
|
|
357
|
+
)
|
|
358
|
+
ctx.adopt(web_runtime, "app")
|
|
359
|
+
ensure_agent_root()
|
|
360
|
+
|
|
361
|
+
ctx.set(
|
|
362
|
+
model_runtime_service=configure_model_runtime_from_context(
|
|
363
|
+
build_model_runtime=build_model_runtime,
|
|
364
|
+
router=ctx.model_router,
|
|
365
|
+
APP_MODE=ctx.APP_MODE,
|
|
366
|
+
DEFAULT_HOST=ctx.DEFAULT_HOST,
|
|
367
|
+
DEFAULT_PORT=ctx.DEFAULT_PORT,
|
|
368
|
+
DATA_DIR=ctx.DATA_DIR,
|
|
369
|
+
BASE_DIR=ctx.BASE_DIR,
|
|
370
|
+
ENABLE_TELEGRAM=ctx.ENABLE_TELEGRAM,
|
|
371
|
+
ENABLE_GRAPH=ctx.ENABLE_GRAPH,
|
|
372
|
+
AUTOLOAD_MODELS=ctx.AUTOLOAD_MODELS,
|
|
373
|
+
MODEL_IDLE_UNLOAD_SECONDS=ctx.MODEL_IDLE_UNLOAD_SECONDS,
|
|
374
|
+
ALLOW_MODEL_DOWNLOADS=ctx.ALLOW_MODEL_DOWNLOADS,
|
|
375
|
+
MODEL_DOWNLOAD_TIMEOUT=ctx.MODEL_DOWNLOAD_TIMEOUT,
|
|
376
|
+
ALLOW_LOCAL_MODELS=ctx.ALLOW_LOCAL_MODELS,
|
|
377
|
+
REQUIRE_AUTH=ctx.REQUIRE_AUTH,
|
|
378
|
+
INVITE_GATE_ENABLED=ctx.INVITE_GATE_ENABLED,
|
|
379
|
+
ALLOW_PLAINTEXT_API_KEYS=ctx.ALLOW_PLAINTEXT_API_KEYS,
|
|
380
|
+
CORS_ALLOW_NETWORK=ctx.CORS_ALLOW_NETWORK,
|
|
381
|
+
PUBLIC_MODEL=ctx.PUBLIC_MODEL,
|
|
382
|
+
LOCAL_MODEL=ctx.LOCAL_MODEL,
|
|
383
|
+
IS_PUBLIC_MODE=ctx.IS_PUBLIC_MODE,
|
|
384
|
+
keyring=ctx.keyring,
|
|
385
|
+
get_current_user=ctx.get_current_user,
|
|
386
|
+
get_user_api_key=ctx.get_user_api_key,
|
|
387
|
+
)
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
static_routes_bundle = build_static_routes_bundle(
|
|
391
|
+
create_static_routes_router=create_static_routes_router,
|
|
392
|
+
static_dir=ctx.STATIC_DIR,
|
|
393
|
+
invite_gate_enabled=ctx.INVITE_GATE_ENABLED,
|
|
394
|
+
invite_code=ctx.INVITE_CODE,
|
|
395
|
+
invite_cookie_secret=ctx.INVITE_COOKIE_SECRET,
|
|
396
|
+
secure_cookies=ctx.SECURE_COOKIES,
|
|
397
|
+
app_mode=ctx.APP_MODE,
|
|
398
|
+
model_router=ctx.model_router,
|
|
399
|
+
require_user=ctx.require_user,
|
|
400
|
+
)
|
|
401
|
+
ctx.adopt(
|
|
402
|
+
static_routes_bundle,
|
|
403
|
+
"STATIC_ROUTES",
|
|
404
|
+
"ui_file_response",
|
|
405
|
+
"local_sysinfo",
|
|
406
|
+
"invite_authorized",
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
foundation_router_bundle = build_auth_admin_security_router_bundle(
|
|
410
|
+
create_auth_router=create_auth_router,
|
|
411
|
+
load_users=ctx.load_users,
|
|
412
|
+
save_users=ctx.save_users,
|
|
413
|
+
hash_password=hash_password,
|
|
414
|
+
verify_and_migrate_password=ctx.verify_and_migrate_password,
|
|
415
|
+
create_session=ctx.create_session,
|
|
416
|
+
get_session_email=ctx.get_session_email,
|
|
417
|
+
invalidate_session=ctx.invalidate_session,
|
|
418
|
+
extract_bearer_token=ctx._extract_bearer_token,
|
|
419
|
+
get_user_role=ctx.get_user_role,
|
|
420
|
+
require_user=ctx.require_user,
|
|
421
|
+
check_ip_rate_limit=ctx._check_rate_limit,
|
|
422
|
+
client_ip=ctx._client_ip,
|
|
423
|
+
get_sso_settings=ctx.get_sso_settings,
|
|
424
|
+
get_sso_discovery=ctx._get_sso_discovery,
|
|
425
|
+
public_sso_config=ctx.public_sso_config,
|
|
426
|
+
open_registration=ctx.OPEN_REGISTRATION,
|
|
427
|
+
session_ttl=ctx._SESSION_TTL,
|
|
428
|
+
require_auth=ctx.REQUIRE_AUTH,
|
|
429
|
+
secure_cookies=ctx.SECURE_COOKIES,
|
|
430
|
+
invite_authorized=ctx.invite_authorized,
|
|
431
|
+
ensure_identity=ensure_user_identity,
|
|
432
|
+
create_admin_router=create_admin_router,
|
|
433
|
+
require_admin=ctx.require_admin,
|
|
434
|
+
get_history=ctx.get_history,
|
|
435
|
+
get_audit_log=ctx.get_audit_log,
|
|
436
|
+
audit_file=ctx.AUDIT_FILE,
|
|
437
|
+
public_user=ctx.public_user,
|
|
438
|
+
load_vpc_config=ctx.load_vpc_config,
|
|
439
|
+
save_vpc_config=ctx.save_vpc_config,
|
|
440
|
+
build_admin_audit_report=ctx.build_admin_audit_report,
|
|
441
|
+
build_sensitivity_report=ctx.build_sensitivity_report,
|
|
442
|
+
append_audit_event=ctx.append_audit_event,
|
|
443
|
+
save_sso_config=ctx.save_sso_config,
|
|
444
|
+
knowledge_graph=ctx.KNOWLEDGE_GRAPH,
|
|
445
|
+
enable_graph=ctx.ENABLE_GRAPH,
|
|
446
|
+
logger=logging,
|
|
447
|
+
invite_code=ctx.INVITE_CODE,
|
|
448
|
+
invite_gate_enabled=ctx.INVITE_GATE_ENABLED,
|
|
449
|
+
default_port=ctx.DEFAULT_PORT,
|
|
450
|
+
policy_matrix=policy_matrix,
|
|
451
|
+
build_product_hardening_status=build_product_hardening_status,
|
|
452
|
+
config=ctx.CONFIG,
|
|
453
|
+
kg_portability=ctx.KG_PORTABILITY,
|
|
454
|
+
device_identity=ctx.DEVICE_IDENTITY,
|
|
455
|
+
create_invitations_router=create_invitations_router,
|
|
456
|
+
invitation_store=ctx.INVITATION_STORE,
|
|
457
|
+
workspace_service=ctx.WORKSPACE_SERVICE,
|
|
458
|
+
user_id_for_email=ctx.user_id_for_email,
|
|
459
|
+
create_security_router=_create_security_router,
|
|
460
|
+
classify_sensitive_message=ctx.classify_sensitive_message,
|
|
461
|
+
)
|
|
462
|
+
ctx.adopt(
|
|
463
|
+
foundation_router_bundle,
|
|
464
|
+
"auth_router",
|
|
465
|
+
"admin_router",
|
|
466
|
+
"invitations_router",
|
|
467
|
+
"security_router",
|
|
468
|
+
"_graph_stats_safe",
|
|
469
|
+
"_product_hardening_status",
|
|
470
|
+
"_security_audit_events_safe",
|
|
471
|
+
"_security_list_uploaded_files",
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
def phase_foundation_routes(ctx: RuntimeContext) -> None:
|
|
476
|
+
"""Mount the foundation routers once the AppContext exists."""
|
|
477
|
+
ctx.enter("foundation_routes")
|
|
478
|
+
|
|
479
|
+
from latticeai.api.workspace import create_workspace_router
|
|
480
|
+
from latticeai.runtime.router_registration import register_foundation_routers
|
|
481
|
+
|
|
482
|
+
register_foundation_routers(
|
|
483
|
+
ctx.app,
|
|
484
|
+
static_router=ctx.STATIC_ROUTES.router,
|
|
485
|
+
auth_router=ctx.auth_router,
|
|
486
|
+
admin_router=ctx.admin_router,
|
|
487
|
+
invitations_router=ctx.invitations_router,
|
|
488
|
+
security_router=ctx.security_router,
|
|
489
|
+
create_workspace_router=create_workspace_router,
|
|
490
|
+
context=ctx.app_context,
|
|
491
|
+
)
|
|
492
|
+
|
|
@@ -25,6 +25,7 @@ def build_chat_agent_runtime_from_context(
|
|
|
25
25
|
audit: Any,
|
|
26
26
|
hooks: Any,
|
|
27
27
|
brain_memory: Any,
|
|
28
|
+
self_model_summary: Any = None,
|
|
28
29
|
) -> Any:
|
|
29
30
|
# Ensure dispatch + agent share the same autonomy dial before the runtime
|
|
30
31
|
# is constructed (process-wide service; data_dir refined later at router mount).
|
|
@@ -39,6 +40,9 @@ def build_chat_agent_runtime_from_context(
|
|
|
39
40
|
hooks=hooks,
|
|
40
41
|
brain_memory=brain_memory,
|
|
41
42
|
permission_mode=resolve_active_permission_mode,
|
|
43
|
+
# v11.2.0: a scoped resolver, not a snapshot — the profile a run sees is
|
|
44
|
+
# the one the Brain holds when the run starts, per user and workspace.
|
|
45
|
+
self_model_summary=self_model_summary,
|
|
42
46
|
)
|
|
43
47
|
|
|
44
48
|
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""Wire the feature switchboard into the running app (v11.2.0).
|
|
2
|
+
|
|
3
|
+
Same shape as ``permission_mode_wiring``: one process-wide service, an
|
|
4
|
+
idempotent router mount guarded on ``app.state``, and explicit arguments that
|
|
5
|
+
*rebind* an already-created service rather than being dropped — a lazy first
|
|
6
|
+
caller must not pin the store to a fallback data dir with no audit sink.
|
|
7
|
+
|
|
8
|
+
The one job unique to this module is :func:`bind_feature_gates`: it points each
|
|
9
|
+
opt-in gate's resolver at the service, which is the step that turns a persisted
|
|
10
|
+
preference into behaviour. Every gate is imported inside the function so
|
|
11
|
+
importing this module costs nothing; ``lattice_brain.synthesis`` in particular
|
|
12
|
+
drags in the graph layer.
|
|
13
|
+
|
|
14
|
+
Unbinding is a supported operation (:func:`unbind_feature_gates`) because these
|
|
15
|
+
gates are module-level singletons: a test that bound them must be able to give
|
|
16
|
+
them back, and "the environment answers again" has to be reachable.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import os
|
|
22
|
+
import threading
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from typing import Any, Callable, List, Optional, Tuple
|
|
25
|
+
|
|
26
|
+
from latticeai.services.feature_toggles import FeatureToggleService
|
|
27
|
+
|
|
28
|
+
_LOCK = threading.Lock()
|
|
29
|
+
_SHARED: Optional[FeatureToggleService] = None
|
|
30
|
+
|
|
31
|
+
#: ``feature id -> (module, attribute)`` for every boolean gate. Resolved lazily
|
|
32
|
+
#: by :func:`_boolean_gates` so the table stays readable next to the catalog it
|
|
33
|
+
#: mirrors, and a rename that breaks the pairing fails loudly at wiring time.
|
|
34
|
+
GATE_BINDINGS: Tuple[Tuple[str, str, str], ...] = (
|
|
35
|
+
("allow_multimodal", "lattice_brain.ingestion", "MULTIMODAL_GATE"),
|
|
36
|
+
("video_ingest", "lattice_brain.ingestion", "VIDEO_GATE"),
|
|
37
|
+
("auto_vector_index", "lattice_brain.ingestion", "AUTO_VECTOR_INDEX_GATE"),
|
|
38
|
+
("brain_network", "lattice_brain.portability", "BRAIN_NETWORK_GATE"),
|
|
39
|
+
("synthesis", "lattice_brain.synthesis", "SYNTHESIS_GATE"),
|
|
40
|
+
("fusion_rrf", "lattice_brain.graph.fusion", "FUSION_RRF_GATE"),
|
|
41
|
+
("graph_expansion", "lattice_brain.graph.fusion", "GRAPH_EXPANSION_GATE"),
|
|
42
|
+
("vault_watch", "latticeai.services.folder_watch", "VAULT_WATCH_GATE"),
|
|
43
|
+
("auto_late_fusion", "latticeai.services.search_service", "IMAGE_QUERY_FUSION_GATE"),
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
#: The one non-boolean seam: the vector backend is a pick-one-of-three.
|
|
47
|
+
CHOICE_FEATURE = "vector_backend"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _default_data_dir() -> Path:
|
|
51
|
+
raw = os.environ.get("LATTICEAI_DATA_DIR", "").strip()
|
|
52
|
+
if raw:
|
|
53
|
+
return Path(raw)
|
|
54
|
+
return Path.home() / ".ltcai"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _boolean_gates() -> List[Tuple[str, Any]]:
|
|
58
|
+
"""``[(feature id, gate)]`` — imports are here, not at module import."""
|
|
59
|
+
from importlib import import_module
|
|
60
|
+
|
|
61
|
+
return [
|
|
62
|
+
(feature_id, getattr(import_module(module), attribute))
|
|
63
|
+
for feature_id, module, attribute in GATE_BINDINGS
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _bind_vector_index(resolver: Any) -> None:
|
|
68
|
+
from lattice_brain.graph.vector_index.selector import bind_vector_index_resolver
|
|
69
|
+
|
|
70
|
+
bind_vector_index_resolver(resolver)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def get_feature_toggle_service(
|
|
74
|
+
*,
|
|
75
|
+
data_dir: Optional[Path] = None,
|
|
76
|
+
audit: Optional[Callable[..., None]] = None,
|
|
77
|
+
) -> FeatureToggleService:
|
|
78
|
+
"""Process-wide singleton; explicit arguments rebind an existing one."""
|
|
79
|
+
global _SHARED
|
|
80
|
+
with _LOCK:
|
|
81
|
+
if _SHARED is None:
|
|
82
|
+
_SHARED = FeatureToggleService(
|
|
83
|
+
data_dir=Path(data_dir) if data_dir is not None else _default_data_dir(),
|
|
84
|
+
audit=audit,
|
|
85
|
+
)
|
|
86
|
+
return _SHARED
|
|
87
|
+
if data_dir is not None:
|
|
88
|
+
_SHARED.rebind_data_dir(Path(data_dir))
|
|
89
|
+
if audit is not None:
|
|
90
|
+
_SHARED.rebind_audit(audit)
|
|
91
|
+
return _SHARED
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def bind_feature_gates(service: Optional[FeatureToggleService] = None) -> None:
|
|
95
|
+
"""Point every opt-in gate at the switchboard.
|
|
96
|
+
|
|
97
|
+
After this, a persisted preference beats the environment variable for every
|
|
98
|
+
feature in the catalog — which is the whole point, since the panel is the
|
|
99
|
+
only control surface most people will ever use.
|
|
100
|
+
"""
|
|
101
|
+
resolved = service or get_feature_toggle_service()
|
|
102
|
+
for feature_id, gate in _boolean_gates():
|
|
103
|
+
# ``gate.local`` is the fallback on purpose: the switchboard speaks only
|
|
104
|
+
# for switches this person actually moved. Everything else still answers
|
|
105
|
+
# from the gate's own override → env → default, so binding changes
|
|
106
|
+
# nothing at all for an install that never opened the panel.
|
|
107
|
+
gate.bind(resolved.resolver(feature_id, gate.local))
|
|
108
|
+
_bind_vector_index(resolved.choice_resolver(CHOICE_FEATURE))
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def unbind_feature_gates() -> None:
|
|
112
|
+
"""Hand every gate back to its environment variable."""
|
|
113
|
+
for _feature_id, gate in _boolean_gates():
|
|
114
|
+
gate.bind(None)
|
|
115
|
+
_bind_vector_index(None)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def reset_feature_toggle_service() -> None:
|
|
119
|
+
"""Drop the singleton *and* the bindings that pointed at it (tests)."""
|
|
120
|
+
global _SHARED
|
|
121
|
+
unbind_feature_gates()
|
|
122
|
+
with _LOCK:
|
|
123
|
+
_SHARED = None
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def register_features_router(
|
|
127
|
+
app: Any,
|
|
128
|
+
*,
|
|
129
|
+
require_user: Callable[..., str],
|
|
130
|
+
data_dir: Optional[Path] = None,
|
|
131
|
+
append_audit_event: Optional[Callable[..., None]] = None,
|
|
132
|
+
) -> FeatureToggleService:
|
|
133
|
+
"""Install GET/POST /api/features on ``app`` and bind the gates. Idempotent.
|
|
134
|
+
|
|
135
|
+
The guard is a flag on ``app.state`` rather than a scan of route paths: a
|
|
136
|
+
router included through fastapi >= 0.140 has no flat ``path`` to introspect,
|
|
137
|
+
so path scanning silently stopped seeing the mount (see
|
|
138
|
+
``permission_mode_wiring`` for the same note).
|
|
139
|
+
"""
|
|
140
|
+
from latticeai.api.features import create_features_router
|
|
141
|
+
|
|
142
|
+
service = get_feature_toggle_service(data_dir=data_dir, audit=append_audit_event)
|
|
143
|
+
bind_feature_gates(service)
|
|
144
|
+
state = getattr(app, "state", None)
|
|
145
|
+
if state is not None and getattr(state, "_ltcai_features_mounted", False):
|
|
146
|
+
return service
|
|
147
|
+
app.include_router(
|
|
148
|
+
create_features_router(service=service, require_user=require_user)
|
|
149
|
+
)
|
|
150
|
+
if state is not None:
|
|
151
|
+
state._ltcai_features_mounted = True
|
|
152
|
+
return service
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
__all__ = [
|
|
156
|
+
"CHOICE_FEATURE",
|
|
157
|
+
"GATE_BINDINGS",
|
|
158
|
+
"bind_feature_gates",
|
|
159
|
+
"get_feature_toggle_service",
|
|
160
|
+
"register_features_router",
|
|
161
|
+
"reset_feature_toggle_service",
|
|
162
|
+
"unbind_feature_gates",
|
|
163
|
+
]
|
|
@@ -675,4 +675,15 @@ def register_review_and_brain_tail_routers(
|
|
|
675
675
|
append_audit_event=append_audit_event,
|
|
676
676
|
knowledge_graph=knowledge_graph,
|
|
677
677
|
)
|
|
678
|
+
# The opt-in switchboard. Mounted last on purpose: binding the gates to the
|
|
679
|
+
# service is what makes a stored preference beat an env var, and every
|
|
680
|
+
# module that owns one of those gates has been imported by now.
|
|
681
|
+
from latticeai.runtime.feature_toggle_wiring import register_features_router
|
|
682
|
+
|
|
683
|
+
register_features_router(
|
|
684
|
+
app,
|
|
685
|
+
require_user=require_user,
|
|
686
|
+
data_dir=data_dir,
|
|
687
|
+
append_audit_event=append_audit_event,
|
|
688
|
+
)
|
|
678
689
|
return brain_network
|