ltcai 8.8.0 → 9.0.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 +33 -37
- package/auto_setup.py +84 -70
- package/docs/CHANGELOG.md +113 -237
- package/docs/CODE_REVIEW_2026-07-06.md +764 -0
- package/docs/COMMUNITY_AND_PLUGINS.md +3 -3
- package/docs/DEVELOPMENT.md +10 -10
- package/docs/LEGACY_COMPATIBILITY.md +1 -1
- package/docs/ONBOARDING.md +2 -2
- package/docs/PRODUCT_DIRECTION_REVIEW.md +3 -2
- package/docs/ROADMAP_RECOMMENDATIONS.md +61 -36
- package/docs/TRUST_MODEL.md +5 -1
- package/docs/WHY_LATTICE.md +4 -3
- package/docs/architecture.md +4 -0
- package/docs/kg-schema.md +1 -1
- package/lattice_brain/__init__.py +1 -1
- package/lattice_brain/archive.py +4 -9
- package/lattice_brain/conversations.py +156 -21
- package/lattice_brain/embeddings.py +38 -2
- package/lattice_brain/graph/_kg_common.py +39 -496
- package/lattice_brain/graph/_kg_constants.py +243 -0
- package/lattice_brain/graph/_kg_fsutil.py +297 -0
- package/lattice_brain/graph/discovery.py +0 -948
- package/lattice_brain/graph/discovery_index.py +972 -0
- package/lattice_brain/graph/json_utils.py +25 -0
- package/lattice_brain/graph/retrieval.py +66 -597
- package/lattice_brain/graph/retrieval_docgen.py +210 -0
- package/lattice_brain/graph/retrieval_vector.py +460 -0
- package/lattice_brain/graph/runtime.py +16 -0
- package/lattice_brain/graph/store.py +6 -0
- package/lattice_brain/ingestion.py +68 -0
- package/lattice_brain/portability.py +1 -9
- package/lattice_brain/quality.py +98 -4
- package/lattice_brain/runtime/agent_runtime.py +166 -0
- package/lattice_brain/runtime/multi_agent.py +1 -1
- package/lattice_brain/utils.py +28 -0
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/chat.py +368 -418
- package/latticeai/api/chat_helpers.py +227 -0
- package/latticeai/api/computer_use.py +149 -31
- package/latticeai/api/marketplace.py +11 -0
- package/latticeai/api/mcp.py +3 -2
- package/latticeai/api/models.py +4 -1
- package/latticeai/api/permissions.py +72 -33
- package/latticeai/api/setup.py +17 -2
- package/latticeai/api/tools.py +105 -62
- package/latticeai/app_factory.py +101 -296
- package/latticeai/core/agent.py +25 -7
- package/latticeai/core/io_utils.py +37 -0
- package/latticeai/core/legacy_compatibility.py +1 -1
- package/latticeai/core/local_embeddings.py +2 -4
- package/latticeai/core/marketplace.py +33 -2
- package/latticeai/core/mcp_catalog.py +450 -0
- package/latticeai/core/mcp_registry.py +2 -441
- package/latticeai/core/sessions.py +11 -3
- package/latticeai/core/tool_registry.py +15 -4
- package/latticeai/core/users.py +4 -9
- package/latticeai/core/workspace_os.py +1 -1
- package/latticeai/core/workspace_os_utils.py +3 -17
- package/latticeai/integrations/telegram_bot.py +7 -2
- package/latticeai/models/model_providers.py +111 -0
- package/latticeai/models/router.py +58 -136
- package/latticeai/runtime/audit_runtime.py +27 -16
- package/latticeai/runtime/automation_runtime.py +9 -0
- package/latticeai/runtime/bootstrap.py +1 -1
- package/latticeai/runtime/history_runtime.py +163 -0
- package/latticeai/runtime/namespace_runtime.py +173 -0
- package/latticeai/runtime/network_config_runtime.py +56 -0
- package/latticeai/runtime/sso_config_runtime.py +128 -0
- package/latticeai/runtime/user_key_runtime.py +106 -0
- package/latticeai/services/app_context.py +1 -0
- package/latticeai/services/architecture_readiness.py +2 -2
- package/latticeai/services/memory_service.py +213 -0
- package/latticeai/services/model_engines.py +79 -12
- package/latticeai/services/model_runtime.py +24 -4
- package/latticeai/services/platform_runtime.py +9 -1
- package/latticeai/services/process_audit.py +208 -0
- package/latticeai/services/product_readiness.py +11 -11
- package/latticeai/services/review_queue.py +64 -11
- package/latticeai/services/run_executor.py +21 -0
- package/latticeai/services/search_service.py +106 -30
- package/latticeai/services/setup_detection.py +80 -0
- package/latticeai/services/tool_dispatch.py +66 -0
- package/latticeai/services/workspace_service.py +15 -0
- package/package.json +1 -1
- package/scripts/check_i18n_literals.mjs +20 -8
- package/scripts/i18n_literal_allowlist.json +34 -0
- package/scripts/lint_frontend.mjs +6 -2
- package/setup_wizard.py +196 -74
- package/src-tauri/Cargo.lock +1 -1
- package/src-tauri/Cargo.toml +1 -1
- package/src-tauri/tauri.conf.json +1 -1
- package/static/app/asset-manifest.json +11 -11
- package/static/app/assets/{Act-C7K9wsO9.js → Act-21lIXx2E.js} +1 -1
- package/static/app/assets/Brain-BqUd5UJJ.js +321 -0
- package/static/app/assets/{Capture-B3V4_5Xp.js → Capture-BA7Z2Q1u.js} +1 -1
- package/static/app/assets/{Library-Cgj-EF50.js → Library-bFMtyni3.js} +1 -1
- package/static/app/assets/System-K6krGCqn.js +1 -0
- package/static/app/assets/index-C4R3ws30.js +17 -0
- package/static/app/assets/index-ChSeOB02.css +2 -0
- package/static/app/assets/primitives-sQU3it5I.js +1 -0
- package/static/app/assets/{textarea-CVQkN2Tk.js → textarea-DK3Fd_lR.js} +1 -1
- package/static/app/index.html +2 -2
- package/static/css/tokens.css +4 -2
- package/static/sw.js +1 -1
- package/tools/local_files.py +6 -0
- package/latticeai/runtime/sso_runtime.py +0 -52
- package/static/app/assets/Brain-I1OSzxJu.js +0 -321
- package/static/app/assets/System-D1Lkei3I.js +0 -1
- package/static/app/assets/index--P0ksosz.js +0 -17
- package/static/app/assets/index-DCh5AoXt.css +0 -2
- package/static/app/assets/primitives-BLqaKk5g.js +0 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# Community And Plugins
|
|
2
2
|
|
|
3
|
-
Current release: **
|
|
3
|
+
Current release: **9.0.0 — Code Review Closure & Runtime Cleanup**.
|
|
4
4
|
|
|
5
5
|
LatticeAI defines the path from a strong local-first framework (8.4.0
|
|
6
6
|
action-aware baseline, 8.5.0 registry+DI hardening, 8.6.0 capture/navigation
|
|
7
|
-
reliability, 8.7.0 runtime-state hygiene,
|
|
8
|
-
readiness) to a product ecosystem. The
|
|
7
|
+
reliability, 8.7.0 runtime-state hygiene, 8.8.0 Brain Core extraction
|
|
8
|
+
readiness, 8.9.0 scoped Tool API hardening, and 9.0.0 cleanup closure) to a product ecosystem. The
|
|
9
9
|
immediate goal is small and practical: make it clear how
|
|
10
10
|
contributors can extend the Brain without weakening local-first trust,
|
|
11
11
|
workspace scoping, or release quality.
|
package/docs/DEVELOPMENT.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# Lattice AI Development
|
|
2
2
|
|
|
3
|
-
Current release: **
|
|
3
|
+
Current release: **9.0.0 — Code Review Closure & Runtime Cleanup**.
|
|
4
4
|
|
|
5
5
|
This document is for contributors working on the local-first Digital Brain
|
|
6
6
|
codebase. Product positioning and quick start stay in `README.md`; release
|
|
7
|
-
history is intentionally limited to
|
|
7
|
+
history is intentionally limited to 8.0.0-9.0.0 in `docs/CHANGELOG.md` and
|
|
8
8
|
`RELEASE.md`.
|
|
9
9
|
|
|
10
10
|
## Product Contract
|
|
@@ -16,8 +16,8 @@ Engineering work should preserve these boundaries:
|
|
|
16
16
|
|
|
17
17
|
- the Brain is the durable asset;
|
|
18
18
|
- models are replaceable voices;
|
|
19
|
-
- SQLite is the
|
|
20
|
-
- PostgreSQL, Docker, cloud models, downloads, update checks, Telegram, and
|
|
19
|
+
- SQLite is the live local Brain store;
|
|
20
|
+
- PostgreSQL scale/migration tooling, Docker, cloud models, downloads, update checks, Telegram, and
|
|
21
21
|
Brain Network are opt-in;
|
|
22
22
|
- import-only paths must not initialize MLX/GPU, write files, or make network
|
|
23
23
|
calls;
|
|
@@ -114,10 +114,10 @@ For user-facing, API, runtime, release, or packaging changes, check:
|
|
|
114
114
|
Release/publish examples must use exact target-version filenames. Do not
|
|
115
115
|
document wildcard artifact upload commands.
|
|
116
116
|
|
|
117
|
-
For
|
|
117
|
+
For 9.0.0 release work, exact artifacts are:
|
|
118
118
|
|
|
119
|
-
- `dist/ltcai-
|
|
120
|
-
- `dist/ltcai-
|
|
121
|
-
- `ltcai-
|
|
122
|
-
- `dist/ltcai-
|
|
123
|
-
- `src-tauri/target/release/bundle/dmg/Lattice
|
|
119
|
+
- `dist/ltcai-9.0.0-py3-none-any.whl`
|
|
120
|
+
- `dist/ltcai-9.0.0.tar.gz`
|
|
121
|
+
- `ltcai-9.0.0.tgz`
|
|
122
|
+
- `dist/ltcai-9.0.0.vsix`
|
|
123
|
+
- `src-tauri/target/release/bundle/dmg/Lattice AI_9.0.0_aarch64.dmg`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Legacy Compatibility Map
|
|
2
2
|
|
|
3
|
-
Current target: **
|
|
3
|
+
Current target: **9.0.0 — Code Review Closure & Runtime Cleanup**.
|
|
4
4
|
|
|
5
5
|
Lattice AI is moving toward a smaller, modular architecture centered on
|
|
6
6
|
`lattice_brain`, `latticeai.services`, `latticeai.api`, and `latticeai.runtime`.
|
package/docs/ONBOARDING.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Lattice AI Onboarding
|
|
2
2
|
|
|
3
|
-
Current release: **
|
|
3
|
+
Current release: **9.0.0 — Code Review Closure & Runtime Cleanup**.
|
|
4
4
|
|
|
5
5
|
The first-run goal is a five-minute path from "I opened the app" to "my Brain
|
|
6
6
|
has a source, a question, and proof." This page is the product contract behind
|
|
@@ -32,7 +32,7 @@ read the docs first.
|
|
|
32
32
|
|
|
33
33
|
## Release Gate
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
9.0.0 treats onboarding as a release gate, not marketing copy. The current
|
|
36
36
|
machine-checkable product readiness report requires this five-minute contract,
|
|
37
37
|
the Brain Home surface, setup helpers, graph ingestion tests, and exact release
|
|
38
38
|
artifact documentation before the release can be called complete.
|
|
@@ -23,8 +23,9 @@ Already aligned:
|
|
|
23
23
|
- The post-setup home is the living Brain plus conversation.
|
|
24
24
|
- The graph appears only after progressive depth: Brain -> Memories ->
|
|
25
25
|
Knowledge -> Relationships -> Graph.
|
|
26
|
-
- Brain Core, SQLite
|
|
27
|
-
backup/restore, and encrypted `.latticebrain`
|
|
26
|
+
- Brain Core, SQLite live local storage, optional PostgreSQL/pgvector
|
|
27
|
+
scale/migration tooling, backup/restore, and encrypted `.latticebrain`
|
|
28
|
+
archives support user ownership.
|
|
28
29
|
- Models are treated as swappable runtime workers rather than the durable asset.
|
|
29
30
|
|
|
30
31
|
Gaps corrected in this review:
|
|
@@ -5,43 +5,12 @@ small release-sized work. The operating principle stays unchanged:
|
|
|
5
5
|
|
|
6
6
|
> Models are temporary. Knowledge is durable. The Brain is the product.
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## Applied Release History
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
-
|
|
13
|
-
|
|
14
|
-
- Hybrid search optimization: the Brain quality gate now includes deterministic
|
|
15
|
-
recall and ranking regression thresholds.
|
|
16
|
-
- Security and trust: run contracts distinguish runtime type and execution mode
|
|
17
|
-
so simulated output is not presented as real execution.
|
|
18
|
-
|
|
19
|
-
## 7.4.0 Applied Slice
|
|
20
|
-
|
|
21
|
-
7.4.0 completes the next roadmap slice without deferring it:
|
|
22
|
-
|
|
23
|
-
- Runtime convergence: agent runs, workflow runs, audit events, and realtime
|
|
24
|
-
events all expose the `agent-run-contract/v1` family envelope while keeping
|
|
25
|
-
legacy top-level fields for compatibility.
|
|
26
|
-
- Trust and operations: persisted run rows refresh their contract through
|
|
27
|
-
queued, running, terminal, cancelled, and interrupted states; audit events are
|
|
28
|
-
contracted only after secret redaction.
|
|
29
|
-
- Retrieval quality and scale: the CI quality gate now seeds a real local
|
|
30
|
-
Knowledge Graph corpus and scores SearchService hybrid retrieval with judged
|
|
31
|
-
queries, recall, precision, NDCG, and must-include hit-rate thresholds.
|
|
32
|
-
|
|
33
|
-
## 7.5.0 Applied Slice
|
|
34
|
-
|
|
35
|
-
7.5.0 burns down the remaining 7.4.0 risk instead of deferring it:
|
|
36
|
-
|
|
37
|
-
- Contract consumption: AgentRuntime and realtime feed APIs now emit compact
|
|
38
|
-
`contracts` views so UI, replay, admin, and export consumers can depend on the
|
|
39
|
-
shared family envelope directly.
|
|
40
|
-
- Retrieval scale: the corpus fixture now runs against 250+ local records while
|
|
41
|
-
keeping judged queries, graded relevance, and must-include expectations.
|
|
42
|
-
- Release trust: stale artifact mixing is handled through clean exact-version
|
|
43
|
-
artifact generation, npm audit findings are cleared, and the Tauri 2 stack is
|
|
44
|
-
updated past the old `block v0.1.6` future-incompatibility warning.
|
|
10
|
+
Release-specific applied-slice history is now kept only for 8.0.0 and newer in
|
|
11
|
+
[docs/CHANGELOG.md](CHANGELOG.md) and [RELEASE.md](../RELEASE.md). Pre-8.0.0
|
|
12
|
+
release-history notes were removed from the tracked documentation surface during
|
|
13
|
+
the 9.0.0 documentation cleanup.
|
|
45
14
|
|
|
46
15
|
## Near-Term Tracks
|
|
47
16
|
|
|
@@ -79,3 +48,59 @@ small release-sized work. The operating principle stays unchanged:
|
|
|
79
48
|
- Interoperability with Obsidian, Notion, Email, Calendar, Git, Slack, and Teams.
|
|
80
49
|
- Encrypted Brain Network sharing.
|
|
81
50
|
- Plugin marketplace and public benchmarks.
|
|
51
|
+
|
|
52
|
+
## Large-Scale Work Feasibility Assessment (2026-07-07)
|
|
53
|
+
|
|
54
|
+
2026-07-07 코드베이스 검사 + 테스트 실행 결과 기반. (app_factory 1214줄, workspace_os 1391줄, 최근 AgentRuntime facade + KG mixin 분해 완료, 833 unit tests passed)
|
|
55
|
+
|
|
56
|
+
### 1. Knowledge Graph / Retrieval at Large Corpus Scale (가장 추천, 기반 탄탄)
|
|
57
|
+
- Incremental/background ingestion + advanced dedup(콘텐츠해시 이상), conflict resolution, merge.
|
|
58
|
+
- Larger corpus benchmarks (현재 250+ → 수천~수만 아이템), latency budget, channel별 진단.
|
|
59
|
+
- pgvector 풀 프로덕션 경로 + 마이그레이션.
|
|
60
|
+
- **실행 가능성**: IngestionPipeline + provenance + vector mixin + benchmark fixtures 이미 존재. discovery/retrieval 분해 완료. hooks lifecycle 통합됨. 대형 작업으로 적합. 테스트: test_ingestion_pipeline.py 14/14 green.
|
|
61
|
+
|
|
62
|
+
### 2. Multi-Modal Brain (스키마 준비됨, 구현 공백 큼)
|
|
63
|
+
- 이미지: vision LLM describe + 임베딩, IMAGE/IMAGE_TEXT 노드 + CONTAINS_IMAGE 엣지 완성, UI 증거 표시.
|
|
64
|
+
- 오디오(전사), 비디오(키프레임).
|
|
65
|
+
- **실행 가능성**: schema.py에 IMAGE/IMAGE_TEXT/CONTAINS_IMAGE 정의됨. discovery_index.py에 PIL + pytesseract OCR (include_ocr) 부분 구현. embeddings.py / LLMRouter에 vision 지원 부재. retrieval도 이미지 노드 미처리. ingestion 라우팅 확장 필요. 대형 + 차별화 포인트.
|
|
66
|
+
|
|
67
|
+
### 3. Server / Runtime Composition 완전화 (구조 부채 해소)
|
|
68
|
+
- app_factory.py 잔여 로직 추가 추출 (더 많은 wiring → runtime/* 전용 모듈).
|
|
69
|
+
- dict(locals()) 레거시 제거, 강력한 RuntimeContext / DI.
|
|
70
|
+
- Config 중앙화 감사 및 강제 (core/config.py 이미 중앙).
|
|
71
|
+
- **실행 가능성**: 최근 review0707 + decomp (1214줄로 감소, runtime/ 디렉토리 풍부) 후에도 여전히 1.2k 라인. AGENTS.md 우선순위 #3,4 정확히 매치. AgentRuntime은 lattice_brain/runtime/agent_runtime.py 로 facade 추출 완료. ToolRegistry도 core/ + services 분리 진행.
|
|
72
|
+
|
|
73
|
+
### 4. Proactive Synthesis + Temporal / Contradiction (최근 작업 연장)
|
|
74
|
+
- 백그라운드 합성 잡, 그래프 이력 기반 모순 검출, temporal 쿼리.
|
|
75
|
+
- 최근 커밋(Brain synthesis memories, follow-up) 을 스케줄 + 자동화로 확장.
|
|
76
|
+
- **실행 가능성**: MultiAgentOrchestrator + workflow_engine + IngestionPipeline + Brain Brief 이미 wired. hooks/audit 있음. agent synthesis UI 표면 최근 추가. 대형 기능으로 자연스러운 확장.
|
|
77
|
+
|
|
78
|
+
### 5. Ecosystem / Interop + Plugin Marketplace (외부 연동 대형)
|
|
79
|
+
- Obsidian/Notion/Email/Calendar import bridge (MCP 또는 전용 ingestion).
|
|
80
|
+
- 서명된 플러그인, 원격 카탈로그, marketplace UI.
|
|
81
|
+
- Brain Network (암호화 공유).
|
|
82
|
+
- **실행 가능성**: plugins/ (hello-world, git-insights), mcp_registry, marketplace.py, core/plugins.py 존재. ingestion이 단일 관문. 하지만 실제 외부 브릿지/마켓플레이스 UI/보안 스캔은 대형.
|
|
83
|
+
|
|
84
|
+
기타: Tauri auto-update + native scale, Workflow Designer 프론트엔드 완성, VSCode 확장 심화 통합 등.
|
|
85
|
+
|
|
86
|
+
**검증 결과 (2026-07-07)**:
|
|
87
|
+
- Core imports (Config, IngestionPipeline, AgentRuntime, ToolRegistry): OK
|
|
88
|
+
- Unit tests: 833 passed (tests/unit/)
|
|
89
|
+
- Ingestion specific: 14 passed
|
|
90
|
+
- Agent runtime tests: 29 passed
|
|
91
|
+
- Frontend lint: typecheck + privacy + OpenAPI 335 paths + lint all pass
|
|
92
|
+
- Git: clean (inspection 시점)
|
|
93
|
+
|
|
94
|
+
**2026-07-07 pts_grok 대형 후보 1~5 슬라이스 실행 결과 (자율 진행)**:
|
|
95
|
+
- 1. KG/Retrieval: background ingestion queue seam, schedule_background, vector index scale diagnostics, backlog reasons/samples, coverage ratio, and rebuild latency budget.
|
|
96
|
+
- 2. Multimodal: offline VisionStub describe/embed path and discovery_index vision_caption fallback so image files have searchable evidence without remote calls.
|
|
97
|
+
- 3. Server decomp: explicit _RUNTIME_BUNDLE in app_factory as the migration contract toward DI while dict(locals()) compatibility remains.
|
|
98
|
+
- 3a. Server decomp follow-up: filtered app_factory runtime namespace so internal scratch imports/runtime dicts no longer leak through lazy server_app compatibility; namespace reduced from 300 to 253 keys while legacy helpers remain, with typed RuntimeBundle added behind legacy _RUNTIME_BUNDLE.
|
|
99
|
+
- 4. Proactive/Temporal: stronger MemoryQualityManager conflict detection, including pairwise opposite-preference and temporal-negation flags.
|
|
100
|
+
- 4a. Proactive UX follow-up: Brain Brief now emits proactive_actions, and BrainHome renders one-click ask/delegate/review/graph action cards.
|
|
101
|
+
- 4b. Proactive UX completion: BrainHome now keeps a local activity trail for suggested actions, showing whether Brain ask, Agent delegation, Review draft, or graph navigation actions are running, completed, or failed.
|
|
102
|
+
- 5. Interop/Marketplace: ingestion_bridge marketplace templates and /marketplace/interop/bridges exposure for future Obsidian/Calendar-style connector imports through unified-ingestion.
|
|
103
|
+
- 총 affected tests: 45 passed in the first targeted gate. Broader lint/static/doc checks are run before commit.
|
|
104
|
+
- 남은: 각 슬라이스는 기반 확장. full background workers, real local VLM/image embeddings, full locals() removal, graph-level temporal queries, and production connector installs remain follow-up work.
|
|
105
|
+
|
|
106
|
+
이 항목들은 "대형사이즈" 에도 할 만하며, mission (local-first, KG, AgentRuntime, security) 과 AGENTS 우선 리팩토링 순서에 부합. 작은 슬라이스로 시작 추천.
|
package/docs/TRUST_MODEL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Lattice AI Trust Model
|
|
2
2
|
|
|
3
|
-
Current release: **
|
|
3
|
+
Current release: **9.0.0 — Code Review Closure & Runtime Cleanup**.
|
|
4
4
|
|
|
5
5
|
Lattice AI is local-first, explicit about external communication, and honest
|
|
6
6
|
when a capability is unavailable.
|
|
@@ -34,6 +34,10 @@ External communication requires configuration plus a user/admin action:
|
|
|
34
34
|
|
|
35
35
|
Token presence alone must not start external communication.
|
|
36
36
|
|
|
37
|
+
Authenticated history, Knowledge Graph reads, and Tool API calls must also stay
|
|
38
|
+
inside the caller's user/workspace scope. Direct HTTP/MCP tool routes run
|
|
39
|
+
ToolRegistry policy before hooks or handlers execute.
|
|
40
|
+
|
|
37
41
|
## Consent And Honesty Gates
|
|
38
42
|
|
|
39
43
|
Lattice AI should fail closed or show an unavailable state for:
|
package/docs/WHY_LATTICE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Why Lattice AI Exists
|
|
2
2
|
|
|
3
|
-
Current release: **
|
|
3
|
+
Current release: **9.0.0 — Code Review Closure & Runtime Cleanup**.
|
|
4
4
|
|
|
5
5
|
**Lattice AI is a local-first Digital Brain that keeps your knowledge durable
|
|
6
6
|
across any AI model.**
|
|
@@ -32,8 +32,9 @@ The graph matters, but it is not the product identity. The product identity is a
|
|
|
32
32
|
local-first Digital Brain: a place where the user can talk, add sources, watch
|
|
33
33
|
memory grow, and inspect the Knowledge Graph when they need proof.
|
|
34
34
|
|
|
35
|
-
In
|
|
36
|
-
conversation composer, evidence-backed Brain Brief,
|
|
35
|
+
In 9.0.0 the first screen is intentionally not a dashboard. The living Brain,
|
|
36
|
+
conversation composer, evidence-backed Brain Brief, scoped memory isolation,
|
|
37
|
+
and five-minute onboarding
|
|
37
38
|
path appear together so the user immediately knows what matters, what to add,
|
|
38
39
|
and why the answer is grounded in local memory.
|
|
39
40
|
|
package/docs/architecture.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Lattice AI Architecture
|
|
2
2
|
|
|
3
|
+
> Historical subsystem note. The canonical current architecture document is
|
|
4
|
+
> [`ARCHITECTURE.md`](../ARCHITECTURE.md). This file preserves the v3.6.0
|
|
5
|
+
> Knowledge Graph First architecture detail for background.
|
|
6
|
+
|
|
3
7
|
> v3.6.0 — **Knowledge Graph First.** Every data source converges into the graph
|
|
4
8
|
> through one unified ingestion pipeline (`latticeai/services/ingestion.py`), with
|
|
5
9
|
> formalized entities/relationships (`docs/kg-schema.md`), browser/web inputs,
|
package/docs/kg-schema.md
CHANGED
package/lattice_brain/archive.py
CHANGED
|
@@ -9,7 +9,6 @@ restore on another machine without contacting a service.
|
|
|
9
9
|
from __future__ import annotations
|
|
10
10
|
|
|
11
11
|
import base64
|
|
12
|
-
import hashlib
|
|
13
12
|
import io
|
|
14
13
|
import json
|
|
15
14
|
import os
|
|
@@ -27,6 +26,8 @@ from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
|
|
27
26
|
from cryptography.exceptions import InvalidTag
|
|
28
27
|
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
|
29
28
|
|
|
29
|
+
from .utils import sha256_file as _sha256_file
|
|
30
|
+
|
|
30
31
|
|
|
31
32
|
ARCHIVE_FORMAT = "latticebrain.encrypted"
|
|
32
33
|
ARCHIVE_VERSION = 2
|
|
@@ -68,15 +69,9 @@ def _derive_key(passphrase: str, salt: bytes) -> bytes:
|
|
|
68
69
|
|
|
69
70
|
|
|
70
71
|
def _sha256_bytes(data: bytes) -> str:
|
|
71
|
-
|
|
72
|
+
import hashlib
|
|
72
73
|
|
|
73
|
-
|
|
74
|
-
def _sha256_file(path: Path) -> str:
|
|
75
|
-
h = hashlib.sha256()
|
|
76
|
-
with open(path, "rb") as fh:
|
|
77
|
-
for block in iter(lambda: fh.read(65536), b""):
|
|
78
|
-
h.update(block)
|
|
79
|
-
return h.hexdigest()
|
|
74
|
+
return hashlib.sha256(data).hexdigest()
|
|
80
75
|
|
|
81
76
|
|
|
82
77
|
def _safe_json(value: Any) -> Any:
|
|
@@ -60,27 +60,76 @@ class ConversationStore:
|
|
|
60
60
|
user_nickname TEXT,
|
|
61
61
|
source TEXT,
|
|
62
62
|
timestamp TEXT NOT NULL,
|
|
63
|
-
metadata_json TEXT NOT NULL DEFAULT '{}'
|
|
63
|
+
metadata_json TEXT NOT NULL DEFAULT '{}',
|
|
64
|
+
workspace_id TEXT,
|
|
65
|
+
organization_id TEXT
|
|
64
66
|
);
|
|
65
67
|
CREATE INDEX IF NOT EXISTS idx_conv_messages_conv
|
|
66
68
|
ON conversation_messages(conversation_id);
|
|
67
69
|
CREATE INDEX IF NOT EXISTS idx_conv_messages_time
|
|
68
70
|
ON conversation_messages(timestamp);
|
|
71
|
+
CREATE INDEX IF NOT EXISTS idx_conv_messages_user
|
|
72
|
+
ON conversation_messages(user_email);
|
|
69
73
|
"""
|
|
70
74
|
)
|
|
75
|
+
columns = {row["name"] for row in conn.execute("PRAGMA table_info(conversation_messages)").fetchall()}
|
|
76
|
+
if "workspace_id" not in columns:
|
|
77
|
+
conn.execute("ALTER TABLE conversation_messages ADD COLUMN workspace_id TEXT")
|
|
78
|
+
if "organization_id" not in columns:
|
|
79
|
+
conn.execute("ALTER TABLE conversation_messages ADD COLUMN organization_id TEXT")
|
|
80
|
+
conn.execute(
|
|
81
|
+
"""
|
|
82
|
+
CREATE INDEX IF NOT EXISTS idx_conv_messages_workspace
|
|
83
|
+
ON conversation_messages(workspace_id)
|
|
84
|
+
"""
|
|
85
|
+
)
|
|
86
|
+
rows = conn.execute(
|
|
87
|
+
"""
|
|
88
|
+
SELECT id, metadata_json FROM conversation_messages
|
|
89
|
+
WHERE workspace_id IS NULL OR organization_id IS NULL
|
|
90
|
+
"""
|
|
91
|
+
).fetchall()
|
|
92
|
+
for row in rows:
|
|
93
|
+
try:
|
|
94
|
+
meta = json.loads(row["metadata_json"] or "{}")
|
|
95
|
+
except Exception:
|
|
96
|
+
meta = {}
|
|
97
|
+
workspace_id = meta.get("workspace_id")
|
|
98
|
+
organization_id = meta.get("organization_id")
|
|
99
|
+
if workspace_id or organization_id:
|
|
100
|
+
conn.execute(
|
|
101
|
+
"""
|
|
102
|
+
UPDATE conversation_messages
|
|
103
|
+
SET workspace_id=COALESCE(workspace_id, ?),
|
|
104
|
+
organization_id=COALESCE(organization_id, ?)
|
|
105
|
+
WHERE id=?
|
|
106
|
+
""",
|
|
107
|
+
(workspace_id, organization_id, row["id"]),
|
|
108
|
+
)
|
|
71
109
|
|
|
72
110
|
# ── writes ────────────────────────────────────────────────────────────
|
|
73
111
|
def append(self, item: Dict[str, Any]) -> Dict[str, Any]:
|
|
74
112
|
"""Persist one chat item (the legacy chat_history.json entry shape)."""
|
|
75
|
-
known = {
|
|
113
|
+
known = {
|
|
114
|
+
"role",
|
|
115
|
+
"content",
|
|
116
|
+
"timestamp",
|
|
117
|
+
"user_email",
|
|
118
|
+
"user_nickname",
|
|
119
|
+
"source",
|
|
120
|
+
"conversation_id",
|
|
121
|
+
"workspace_id",
|
|
122
|
+
"organization_id",
|
|
123
|
+
}
|
|
76
124
|
extra = {k: v for k, v in item.items() if k not in known}
|
|
77
125
|
with self._lock, self._connect() as conn:
|
|
78
126
|
conn.execute(
|
|
79
127
|
"""
|
|
80
128
|
INSERT OR IGNORE INTO conversation_messages
|
|
81
129
|
(message_hash, conversation_id, role, content, user_email,
|
|
82
|
-
user_nickname, source, timestamp, metadata_json
|
|
83
|
-
|
|
130
|
+
user_nickname, source, timestamp, metadata_json, workspace_id,
|
|
131
|
+
organization_id)
|
|
132
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
84
133
|
""",
|
|
85
134
|
(
|
|
86
135
|
_message_hash(item),
|
|
@@ -92,6 +141,8 @@ class ConversationStore:
|
|
|
92
141
|
item.get("source"),
|
|
93
142
|
str(item.get("timestamp") or ""),
|
|
94
143
|
json.dumps(extra, ensure_ascii=False) if extra else "{}",
|
|
144
|
+
item.get("workspace_id"),
|
|
145
|
+
item.get("organization_id"),
|
|
95
146
|
),
|
|
96
147
|
)
|
|
97
148
|
return item
|
|
@@ -122,8 +173,9 @@ class ConversationStore:
|
|
|
122
173
|
"""
|
|
123
174
|
INSERT OR IGNORE INTO conversation_messages
|
|
124
175
|
(message_hash, conversation_id, role, content, user_email,
|
|
125
|
-
user_nickname, source, timestamp, metadata_json
|
|
126
|
-
|
|
176
|
+
user_nickname, source, timestamp, metadata_json, workspace_id,
|
|
177
|
+
organization_id)
|
|
178
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, '{}', ?, ?)
|
|
127
179
|
""",
|
|
128
180
|
(
|
|
129
181
|
_message_hash(item),
|
|
@@ -134,6 +186,8 @@ class ConversationStore:
|
|
|
134
186
|
item.get("user_nickname"),
|
|
135
187
|
item.get("source"),
|
|
136
188
|
str(item.get("timestamp") or ""),
|
|
189
|
+
item.get("workspace_id"),
|
|
190
|
+
item.get("organization_id"),
|
|
137
191
|
),
|
|
138
192
|
)
|
|
139
193
|
imported += cur.rowcount if cur.rowcount > 0 else 0
|
|
@@ -149,7 +203,7 @@ class ConversationStore:
|
|
|
149
203
|
"content": row["content"],
|
|
150
204
|
"timestamp": row["timestamp"],
|
|
151
205
|
}
|
|
152
|
-
for key in ("user_email", "user_nickname", "source", "conversation_id"):
|
|
206
|
+
for key in ("user_email", "user_nickname", "source", "conversation_id", "workspace_id", "organization_id"):
|
|
153
207
|
if row[key]:
|
|
154
208
|
item[key] = row[key]
|
|
155
209
|
try:
|
|
@@ -159,13 +213,62 @@ class ConversationStore:
|
|
|
159
213
|
item.update(extra)
|
|
160
214
|
return item
|
|
161
215
|
|
|
162
|
-
|
|
216
|
+
@staticmethod
|
|
217
|
+
def _scope_sql(
|
|
218
|
+
*,
|
|
219
|
+
user_email: Optional[str] = None,
|
|
220
|
+
allowed_workspaces: Optional[Any] = None,
|
|
221
|
+
include_legacy_global: bool = True,
|
|
222
|
+
) -> tuple[str, List[Any]]:
|
|
223
|
+
clauses: List[str] = []
|
|
224
|
+
params: List[Any] = []
|
|
225
|
+
if user_email:
|
|
226
|
+
if include_legacy_global:
|
|
227
|
+
clauses.append("(user_email = ? OR user_email IS NULL OR user_email = '')")
|
|
228
|
+
else:
|
|
229
|
+
clauses.append("user_email = ?")
|
|
230
|
+
params.append(user_email)
|
|
231
|
+
if allowed_workspaces is not None:
|
|
232
|
+
allowed = [str(item) for item in allowed_workspaces if item]
|
|
233
|
+
if allowed:
|
|
234
|
+
placeholders = ",".join("?" for _ in allowed)
|
|
235
|
+
if include_legacy_global:
|
|
236
|
+
clauses.append(f"(workspace_id IN ({placeholders}) OR workspace_id IS NULL OR workspace_id = '')")
|
|
237
|
+
else:
|
|
238
|
+
clauses.append(f"workspace_id IN ({placeholders})")
|
|
239
|
+
params.extend(allowed)
|
|
240
|
+
elif include_legacy_global:
|
|
241
|
+
clauses.append("(workspace_id IS NULL OR workspace_id = '')")
|
|
242
|
+
else:
|
|
243
|
+
clauses.append("1=0")
|
|
244
|
+
return " AND ".join(clauses), params
|
|
245
|
+
|
|
246
|
+
def history(
|
|
247
|
+
self,
|
|
248
|
+
*,
|
|
249
|
+
conversation_id: Optional[str] = None,
|
|
250
|
+
limit: Optional[int] = None,
|
|
251
|
+
user_email: Optional[str] = None,
|
|
252
|
+
allowed_workspaces: Optional[Any] = None,
|
|
253
|
+
include_legacy_global: bool = True,
|
|
254
|
+
) -> List[Dict[str, Any]]:
|
|
163
255
|
"""Chronological items; the unbounded successor of get_history()."""
|
|
164
256
|
query = "SELECT * FROM conversation_messages"
|
|
165
257
|
params: List[Any] = []
|
|
258
|
+
where: List[str] = []
|
|
166
259
|
if conversation_id is not None:
|
|
167
|
-
|
|
260
|
+
where.append("conversation_id IS ?" if conversation_id == "" else "conversation_id = ?")
|
|
168
261
|
params.append(None if conversation_id == "" else conversation_id)
|
|
262
|
+
scope_sql, scope_params = self._scope_sql(
|
|
263
|
+
user_email=user_email,
|
|
264
|
+
allowed_workspaces=allowed_workspaces,
|
|
265
|
+
include_legacy_global=include_legacy_global,
|
|
266
|
+
)
|
|
267
|
+
if scope_sql:
|
|
268
|
+
where.append(scope_sql)
|
|
269
|
+
params.extend(scope_params)
|
|
270
|
+
if where:
|
|
271
|
+
query += " WHERE " + " AND ".join(where)
|
|
169
272
|
query += " ORDER BY id ASC"
|
|
170
273
|
if limit is not None:
|
|
171
274
|
query += " LIMIT ?"
|
|
@@ -185,25 +288,51 @@ class ConversationStore:
|
|
|
185
288
|
return 0
|
|
186
289
|
|
|
187
290
|
# ── clears (legacy semantics preserved) ───────────────────────────────
|
|
188
|
-
def clear_all(
|
|
291
|
+
def clear_all(
|
|
292
|
+
self,
|
|
293
|
+
keep_last: int = 0,
|
|
294
|
+
*,
|
|
295
|
+
user_email: Optional[str] = None,
|
|
296
|
+
allowed_workspaces: Optional[Any] = None,
|
|
297
|
+
include_legacy_global: bool = True,
|
|
298
|
+
) -> Dict[str, Any]:
|
|
189
299
|
keep_last = max(0, min(int(keep_last or 0), 20))
|
|
190
300
|
with self._lock, self._connect() as conn:
|
|
191
301
|
total = conn.execute("SELECT COUNT(*) FROM conversation_messages").fetchone()[0]
|
|
302
|
+
scope_sql, scope_params = self._scope_sql(
|
|
303
|
+
user_email=user_email,
|
|
304
|
+
allowed_workspaces=allowed_workspaces,
|
|
305
|
+
include_legacy_global=include_legacy_global,
|
|
306
|
+
)
|
|
307
|
+
scope_where = f" WHERE {scope_sql}" if scope_sql else ""
|
|
192
308
|
if keep_last:
|
|
193
309
|
conn.execute(
|
|
194
|
-
"""
|
|
195
|
-
DELETE FROM conversation_messages
|
|
196
|
-
|
|
310
|
+
f"""
|
|
311
|
+
DELETE FROM conversation_messages
|
|
312
|
+
WHERE id IN (
|
|
313
|
+
SELECT id FROM conversation_messages{scope_where}
|
|
314
|
+
)
|
|
315
|
+
AND id NOT IN (
|
|
316
|
+
SELECT id FROM conversation_messages{scope_where}
|
|
317
|
+
ORDER BY id DESC LIMIT ?
|
|
197
318
|
)
|
|
198
319
|
""",
|
|
199
|
-
(keep_last
|
|
320
|
+
(*scope_params, *scope_params, keep_last),
|
|
200
321
|
)
|
|
201
322
|
else:
|
|
202
|
-
conn.execute("DELETE FROM conversation_messages")
|
|
323
|
+
conn.execute(f"DELETE FROM conversation_messages{scope_where}", scope_params)
|
|
203
324
|
kept = conn.execute("SELECT COUNT(*) FROM conversation_messages").fetchone()[0]
|
|
204
325
|
return {"status": "cleared", "removed": max(0, total - kept), "kept": kept}
|
|
205
326
|
|
|
206
|
-
def clear_conversation(
|
|
327
|
+
def clear_conversation(
|
|
328
|
+
self,
|
|
329
|
+
conversation_id: str,
|
|
330
|
+
started_at: Optional[str] = None,
|
|
331
|
+
*,
|
|
332
|
+
user_email: Optional[str] = None,
|
|
333
|
+
allowed_workspaces: Optional[Any] = None,
|
|
334
|
+
include_legacy_global: bool = True,
|
|
335
|
+
) -> Dict[str, Any]:
|
|
207
336
|
"""Remove one conversation.
|
|
208
337
|
|
|
209
338
|
``legacy-previous-history`` targets unattributed messages; when
|
|
@@ -212,17 +341,23 @@ class ConversationStore:
|
|
|
212
341
|
"""
|
|
213
342
|
with self._lock, self._connect() as conn:
|
|
214
343
|
total = conn.execute("SELECT COUNT(*) FROM conversation_messages").fetchone()[0]
|
|
344
|
+
scope_sql, scope_params = self._scope_sql(
|
|
345
|
+
user_email=user_email,
|
|
346
|
+
allowed_workspaces=allowed_workspaces,
|
|
347
|
+
include_legacy_global=include_legacy_global,
|
|
348
|
+
)
|
|
349
|
+
scoped = f" AND {scope_sql}" if scope_sql else ""
|
|
215
350
|
if conversation_id == "legacy-previous-history":
|
|
216
|
-
conn.execute("DELETE FROM conversation_messages WHERE conversation_id IS NULL")
|
|
351
|
+
conn.execute(f"DELETE FROM conversation_messages WHERE conversation_id IS NULL{scoped}", scope_params)
|
|
217
352
|
else:
|
|
218
353
|
conn.execute(
|
|
219
|
-
"DELETE FROM conversation_messages WHERE conversation_id = ?",
|
|
220
|
-
(conversation_id,),
|
|
354
|
+
f"DELETE FROM conversation_messages WHERE conversation_id = ?{scoped}",
|
|
355
|
+
(conversation_id, *scope_params),
|
|
221
356
|
)
|
|
222
357
|
if started_at:
|
|
223
358
|
conn.execute(
|
|
224
|
-
"DELETE FROM conversation_messages WHERE conversation_id IS NULL AND timestamp >= ?",
|
|
225
|
-
(str(started_at),),
|
|
359
|
+
f"DELETE FROM conversation_messages WHERE conversation_id IS NULL AND timestamp >= ?{scoped}",
|
|
360
|
+
(str(started_at), *scope_params),
|
|
226
361
|
)
|
|
227
362
|
kept = conn.execute("SELECT COUNT(*) FROM conversation_messages").fetchone()[0]
|
|
228
363
|
return {
|
|
@@ -8,7 +8,7 @@ import os
|
|
|
8
8
|
import re
|
|
9
9
|
import struct
|
|
10
10
|
from dataclasses import dataclass
|
|
11
|
-
from typing import Iterable, List
|
|
11
|
+
from typing import Any, Dict, Iterable, List, Optional
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
DEFAULT_EMBEDDING_DIM = int(os.getenv("LATTICEAI_VECTOR_DIM", "384"))
|
|
@@ -87,4 +87,40 @@ class LocalEmbeddingModel:
|
|
|
87
87
|
return list(struct.unpack(f"<{count}f", payload[: count * 4]))
|
|
88
88
|
|
|
89
89
|
|
|
90
|
-
|
|
90
|
+
# --- Large candidate #2 slice: multimodal (vision) stubs ---
|
|
91
|
+
# Schema already defines IMAGE / IMAGE_TEXT / CONTAINS_IMAGE.
|
|
92
|
+
# These stubs allow ingestion + retrieval paths to carry image signals without
|
|
93
|
+
# requiring heavy deps at core. Real impl can swap in local vision (e.g. via
|
|
94
|
+
# ollama vision or onnx CLIP) behind the same interface.
|
|
95
|
+
@dataclass(frozen=True)
|
|
96
|
+
class VisionStub:
|
|
97
|
+
"""Offline vision describe + embed stubs for multimodal Brain.
|
|
98
|
+
|
|
99
|
+
describe: returns a short textual caption derived from metadata/filename.
|
|
100
|
+
embed: produces a vector from image meta (size+format) + optional caption hash.
|
|
101
|
+
Later: replace with real embedding model that accepts image bytes/path.
|
|
102
|
+
"""
|
|
103
|
+
dim: int = DEFAULT_EMBEDDING_DIM
|
|
104
|
+
|
|
105
|
+
def describe(self, path: str | None = None, meta: Optional[Dict[str, Any]] = None) -> str:
|
|
106
|
+
meta = meta or {}
|
|
107
|
+
w = meta.get("width") or meta.get("w") or "?"
|
|
108
|
+
h = meta.get("height") or meta.get("h") or "?"
|
|
109
|
+
fmt = meta.get("format") or meta.get("ext") or "img"
|
|
110
|
+
name = (path or "").split("/")[-1] or "image"
|
|
111
|
+
# deterministic caption stub (no external call)
|
|
112
|
+
return f"Image {name} ({fmt} {w}x{h})"
|
|
113
|
+
|
|
114
|
+
def embed_image(self, path: str | None = None, meta: Optional[Dict[str, Any]] = None, caption: str = "") -> List[float]:
|
|
115
|
+
meta = meta or {}
|
|
116
|
+
basis = f"{path or ''}|{meta.get('width',0)}x{meta.get('height',0)}|{meta.get('format','')}|{caption[:120]}"
|
|
117
|
+
# reuse text embedder for determinism (image content hash would be better with real vision)
|
|
118
|
+
model = LocalEmbeddingModel(dim=self.dim)
|
|
119
|
+
return model.embed(basis)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def get_vision_embedder(dim: int | None = None) -> VisionStub:
|
|
123
|
+
return VisionStub(dim=dim or DEFAULT_EMBEDDING_DIM)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
__all__ = ["DEFAULT_EMBEDDING_DIM", "EMBEDDING_MODEL_ID", "LocalEmbeddingModel", "embedding_model_id", "VisionStub", "get_vision_embedder"]
|