ltcai 11.0.1 → 11.1.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.
Files changed (95) hide show
  1. package/README.md +55 -43
  2. package/docs/CHANGELOG.md +28 -0
  3. package/docs/COMMUNITY_AND_PLUGINS.md +1 -1
  4. package/docs/DEVELOPMENT.md +1 -1
  5. package/docs/ONBOARDING.md +1 -1
  6. package/docs/OPERATIONS.md +1 -1
  7. package/docs/PERFORMANCE.md +71 -18
  8. package/docs/TRUST_MODEL.md +1 -1
  9. package/docs/WHY_LATTICE.md +1 -1
  10. package/docs/kg-schema.md +1 -1
  11. package/lattice_brain/__init__.py +1 -1
  12. package/lattice_brain/embeddings.py +12 -37
  13. package/lattice_brain/graph/discovery_index.py +30 -32
  14. package/lattice_brain/graph/image_vectors.py +230 -0
  15. package/lattice_brain/graph/ingest.py +11 -5
  16. package/lattice_brain/graph/provenance.py +27 -2
  17. package/lattice_brain/graph/retrieval.py +113 -2
  18. package/lattice_brain/graph/retrieval_docgen.py +6 -6
  19. package/lattice_brain/graph/schema.py +9 -0
  20. package/lattice_brain/ingestion.py +209 -4
  21. package/lattice_brain/multimodal.py +738 -0
  22. package/lattice_brain/runtime/multi_agent.py +1 -1
  23. package/lattice_brain/self_model.py +77 -22
  24. package/latticeai/__init__.py +1 -1
  25. package/latticeai/api/memory.py +128 -1
  26. package/latticeai/core/agent.py +5 -1
  27. package/latticeai/core/agent_prompts.py +66 -0
  28. package/latticeai/core/context_builder.py +92 -8
  29. package/latticeai/core/embedding_providers.py +528 -0
  30. package/latticeai/core/legacy_compatibility.py +1 -1
  31. package/latticeai/core/marketplace.py +1 -1
  32. package/latticeai/core/messages.py +37 -0
  33. package/latticeai/core/workspace_os.py +43 -0
  34. package/latticeai/core/workspace_os_constants.py +1 -1
  35. package/latticeai/core/workspace_reorganization.py +335 -0
  36. package/latticeai/runtime/build_phases.py +5 -2
  37. package/latticeai/runtime/persistence_runtime.py +41 -4
  38. package/latticeai/runtime/runtime_context.py +1 -0
  39. package/latticeai/services/architecture_readiness.py +1 -1
  40. package/latticeai/services/change_proposals.py +50 -10
  41. package/latticeai/services/memory_service.py +34 -0
  42. package/latticeai/services/multimodal_ports.py +87 -0
  43. package/latticeai/services/product_readiness.py +1 -1
  44. package/latticeai/services/self_model_service.py +171 -0
  45. package/latticeai/services/voice_capture.py +27 -1
  46. package/package.json +1 -1
  47. package/scripts/check_current_release_docs.mjs +1 -1
  48. package/scripts/release_screen_claims.json +9 -0
  49. package/src-tauri/Cargo.lock +1 -1
  50. package/src-tauri/Cargo.toml +1 -1
  51. package/src-tauri/tauri.conf.json +1 -1
  52. package/static/app/asset-manifest.json +37 -37
  53. package/static/app/assets/{Act-D4zSxFR-.js → Act-D0HWqtn0.js} +1 -1
  54. package/static/app/assets/{AdminConsole-w5jBfPt2.js → AdminConsole-D-QDW-A4.js} +1 -1
  55. package/static/app/assets/{Brain-C2EqQg74.js → Brain-CzCsI1mi.js} +1 -1
  56. package/static/app/assets/BrainHome-Btns-_TA.js +2 -0
  57. package/static/app/assets/BrainSignals-2dHQNkns.js +1 -0
  58. package/static/app/assets/{Capture-DPqpGK8d.js → Capture-CT8v1StE.js} +1 -1
  59. package/static/app/assets/{CommandPalette-CNf7h5fp.js → CommandPalette-DoLXC2KH.js} +1 -1
  60. package/static/app/assets/{Library-BN0HYOfc.js → Library-DDoxFE5c.js} +1 -1
  61. package/static/app/assets/{LivingBrain-Dfq_wEDI.js → LivingBrain-BXMWIK_2.js} +1 -1
  62. package/static/app/assets/{ProductFlow-B-3O0rNV.js → ProductFlow-DOYf7JIs.js} +1 -1
  63. package/static/app/assets/{ReviewCard-gZ-tdqFM.js → ReviewCard-COQsqidK.js} +1 -1
  64. package/static/app/assets/{System-BElUcSSw.js → System-BRllvYXd.js} +1 -1
  65. package/static/app/assets/arrow-left-DnyMzss-.js +1 -0
  66. package/static/app/assets/{bot--qYHMtkP.js → bot-4BvN07ux.js} +1 -1
  67. package/static/app/assets/brain-uMb_5hnO.js +1 -0
  68. package/static/app/assets/{button-51Z3rsuv.js → button-CDjtnAoU.js} +1 -1
  69. package/static/app/assets/{circle-pause-CMIiMaQl.js → circle-pause-D_RMn7tp.js} +1 -1
  70. package/static/app/assets/{circle-play-DZoO_cfG.js → circle-play-B5OpB8ae.js} +1 -1
  71. package/static/app/assets/{cpu-Bs6uc9W9.js → cpu-BIlWInHf.js} +1 -1
  72. package/static/app/assets/{download-G-2olkWz.js → download-BtjXfL3z.js} +1 -1
  73. package/static/app/assets/{folder-open-CTOspnmb.js → folder-open-DefMpxI2.js} +1 -1
  74. package/static/app/assets/{hard-drive-CewHWJhn.js → hard-drive-BQ8NZVkw.js} +1 -1
  75. package/static/app/assets/{index-D7Rr-J2Y.js → index-0AvoEBzJ.js} +3 -3
  76. package/static/app/assets/index-vtEfYvQY.css +2 -0
  77. package/static/app/assets/{input-D4w_BZWl.js → input-B_5ZJ9oy.js} +1 -1
  78. package/static/app/assets/{permissionCopy-CosBEXAZ.js → permissionCopy-BqZ5tsgL.js} +1 -1
  79. package/static/app/assets/{primitives-d0g9pvzS.js → primitives-CVwew78r.js} +1 -1
  80. package/static/app/assets/search-DkhnOKZt.js +1 -0
  81. package/static/app/assets/{share-2-NmD7e_oV.js → share-2-D5zg_0fY.js} +1 -1
  82. package/static/app/assets/{shield-alert-CcQeMuju.js → shield-alert-B5pZzkUb.js} +1 -1
  83. package/static/app/assets/{textarea-BPAJDc-0.js → textarea-nEVIweKY.js} +1 -1
  84. package/static/app/assets/{useFocusTrap-C7YLdTBC.js → useFocusTrap-Cm99AHlz.js} +1 -1
  85. package/static/app/assets/{useQuery-DRyD9opW.js → useQuery-Dm__N6bL.js} +1 -1
  86. package/static/app/assets/{utils-DG1_ExrP.js → utils-DcDMoZIe.js} +1 -1
  87. package/static/app/assets/{workspace-CWVf3gsI.js → workspace-LtRRSKTf.js} +1 -1
  88. package/static/app/index.html +4 -4
  89. package/static/sw.js +1 -1
  90. package/static/app/assets/BrainHome-CvXS6XiQ.js +0 -2
  91. package/static/app/assets/BrainSignals-DOE_KhOU.js +0 -1
  92. package/static/app/assets/arrow-left-CFNIMjhv.js +0 -1
  93. package/static/app/assets/brain-DDCLjRqO.js +0 -1
  94. package/static/app/assets/index-CkzokZAj.css +0 -2
  95. package/static/app/assets/search-BLCYt75v.js +0 -1
package/README.md CHANGED
@@ -11,7 +11,7 @@
11
11
  [![CI Status](https://github.com/TaeSooPark-PTS/LatticeAI/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/TaeSooPark-PTS/LatticeAI/actions/workflows/ci.yml)
12
12
  [![License](https://img.shields.io/badge/license-MIT-green)](LICENSE)
13
13
 
14
- ![v11.0.1 Living Brain walkthrough](output/release/v11.0.1/gifs/v11.0.1-living-brain-walkthrough.gif)
14
+ ![v11.1.0 Living Brain walkthrough](output/release/v11.1.0/gifs/v11.1.0-living-brain-walkthrough.gif)
15
15
 
16
16
  Chat, files, folders, notes, and web pages all flow into one durable knowledge
17
17
  graph on your computer. Any model — local MLX or cloud — can speak with that
@@ -24,10 +24,10 @@ memory. Nothing leaves your machine without explicit consent.
24
24
 
25
25
  | | |
26
26
  | --- | --- |
27
- | **Chat with a Brain that remembers** — every conversation grows durable, source-linked memory ![Brain Chat](output/release/v11.0.1/screenshots/04-brain-chat-home.png) | **See how knowledge connects** — a real relationship graph, not a file list ![Memory Graph](output/release/v11.0.1/screenshots/05-memory-graph.png) |
28
- | **Capture anything** — files, whole folders, notes, screenshots, web pages ![Capture](output/release/v11.0.1/screenshots/06-capture.png) | **Automate with review** — agent changes become proposals you approve first ![Review Center](output/release/v11.0.1/screenshots/12-review-center.png) |
29
- | **Pick a model in one click** — recommended local models for your hardware ![Recommended Models](output/release/v11.0.1/screenshots/02-recommended-models.png) | **Stay in control** — audit, roles, retention in a separate admin surface ![Admin Console](output/release/v11.0.1/screenshots/10-admin-console.png) |
30
- | **Watch a file become memory** — three named steps, not a pipeline diagram ![Material to memory](output/release/v11.0.1/screenshots/11-knowledge-journey.png) | **Say how much it may do alone** — one dial in plain words; dangerous actions stay blocked either way ![Settings](output/release/v11.0.1/screenshots/08-system.png) |
27
+ | **Chat with a Brain that remembers** — every conversation grows durable, source-linked memory ![Brain Chat](output/release/v11.1.0/screenshots/04-brain-chat-home.png) | **See how knowledge connects** — a real relationship graph, not a file list ![Memory Graph](output/release/v11.1.0/screenshots/05-memory-graph.png) |
28
+ | **Capture anything** — files, whole folders, notes, screenshots, web pages ![Capture](output/release/v11.1.0/screenshots/06-capture.png) | **Automate with review** — agent changes become proposals you approve first ![Review Center](output/release/v11.1.0/screenshots/12-review-center.png) |
29
+ | **Pick a model in one click** — recommended local models for your hardware ![Recommended Models](output/release/v11.1.0/screenshots/02-recommended-models.png) | **Stay in control** — audit, roles, retention in a separate admin surface ![Admin Console](output/release/v11.1.0/screenshots/10-admin-console.png) |
30
+ | **Watch a file become memory** — three named steps, not a pipeline diagram ![Material to memory](output/release/v11.1.0/screenshots/11-knowledge-journey.png) | **Say how much it may do alone** — one dial in plain words; dangerous actions stay blocked either way ![Settings](output/release/v11.1.0/screenshots/08-system.png) |
31
31
 
32
32
  ## Why Lattice AI
33
33
 
@@ -58,53 +58,64 @@ First-run flow — wake the Brain, pick the owner, load a recommended model:
58
58
 
59
59
  | | | |
60
60
  | --- | --- | --- |
61
- | ![Login](output/release/v11.0.1/screenshots/01-login.png) | ![Model install](output/release/v11.0.1/screenshots/03-install-load-progress.png) | ![Model library](output/release/v11.0.1/screenshots/07-model-library.png) |
61
+ | ![Login](output/release/v11.1.0/screenshots/01-login.png) | ![Model install](output/release/v11.1.0/screenshots/03-install-load-progress.png) | ![Model library](output/release/v11.1.0/screenshots/07-model-library.png) |
62
62
 
63
63
  Screenshot index and capture notes:
64
- [output/release/v11.0.1/SCREENSHOT_INDEX.md](output/release/v11.0.1/SCREENSHOT_INDEX.md)
64
+ [output/release/v11.1.0/SCREENSHOT_INDEX.md](output/release/v11.1.0/SCREENSHOT_INDEX.md)
65
65
 
66
66
  ## Current Release
67
67
 
68
- The current release is **11.0.1 — Both Branches**:
69
-
70
- 11.0.0 put every line under test and documented, unfixed, the defects that
71
- work surfaced. 11.0.1 is the settling of that account: **all eleven
72
- documented defects are fixed, the code they proved dead is gone, and the CI
73
- floor now holds 100% of branch arcs as well as lines.**
74
-
75
- - **Every 11.0.0 finding is fixed.** The Telegram helper that sent the local
76
- server's auth header to api.telegram.org, the unload-all that fabricated
77
- success, the HTML inspector whose stylesheet collection could never fire,
78
- the review-item id that collided within one second, the vLLM zombie that
79
- read as "already running", the security dashboard's listing/detail
80
- redaction gaps and invalid-JSON exports, the embedding `model_id` frozen at
81
- the wrong dimension, the fast-path model-name mismatch, and the workspace
82
- routes' dead literal and unreachable 404 arms. Each fix flipped the test
83
- that had pinned the broken behaviour and gained a regression test.
84
- - **Branch coverage joined the floor.** `branch = true` is now in the
85
- coverage config: both directions of all 9,828 conditionals execute under
86
- the suite (5,798 tests, up from 5,426), and `fail_under = 100` fails CI on
87
- a single missed arc. Exclusions stay honest: eight reasoned
88
- `pragma: no cover` lines and two reasoned `pragma: no branch` lines.
89
- - **Dead code left the tree.** The unused auto-approved write-policy factory,
90
- an unused snapshot-import normalizer, the workspace 404 arms shadowed by
91
- the anti-enumeration 403, a provably-dead version guard, a provably-dead
92
- MLX condition, and the vLLM silent-success recheck — each removal proved by
93
- AST/reference scan before deletion.
94
- - **Verified where CI runs.** The full suite passes with the branch gate on
95
- macOS 3.14, a fresh-resolve python 3.11 environment (fastapi 0.141), and a
96
- clean linux python:3.14 container — the three environments that caught
97
- 11.0.0's release-day failures.
68
+ The current release is **11.1.0 — Product Intelligence**:
69
+
70
+ The v9–v11.0 line hardened the foundation — proposal-first trust, honest
71
+ signals, a 100% line-and-branch test floor. 11.1.0 builds the intelligence
72
+ layer on top of it: **the Brain gets fast at scale, notices things on its
73
+ own, remembers pictures and recordings, learns who you are, and connects to
74
+ the tools you already use.**
75
+
76
+ - **Fast at scale.** A pluggable vector-index layer (brute-force default,
77
+ int8 quantized and HNSW opt-in via the `hnsw` extra) plus a durable
78
+ background embed queue. Measured on Apple Silicon: hybrid search p50 at
79
+ 10k vectors went from 299 ms to **10.1 ms**, and stays at 43.9 ms at 50k
80
+ (recall@10 0.987) — the plan's <50 ms target met at 5× the target corpus.
81
+ Approximate results say `approx: true`; quantized's honest verdict (no RAM
82
+ win here, ~2.2× slower) is printed, not hidden.
83
+ - **Alive, not just searchable.** Contradiction detection now files
84
+ review-queue proposals with plain-language resolutions; approving one
85
+ stamps the temporal model (`valid_from`/`valid_to`/`superseded_by`, with
86
+ `as_of(timestamp)` slicing). Event-driven synthesis proposes parent
87
+ concepts, missing links and a proactive Brain Brief after every 25th
88
+ ingest — every write goes through the proposal path, asserted by tests.
89
+ - **Pictures and recordings are memories.** Behind `allow_multimodal`
90
+ (default off, off ⇒ byte-identical): images become first-class `Image`
91
+ nodes with OCR text, real captions only when a vision model produced one
92
+ (the caption-fabricating stub was deleted), separate image vectors with
93
+ late fusion, and inline thumbnails in the Evidence panel that never bypass
94
+ the local-file approval gate. Recordings are first-class `Audio` nodes
95
+ with honest transcription degradation.
96
+ - **It knows you — transparently.** A Self-Model subgraph (Self /
97
+ Preference / Decision / Habit / Relationship) built only from proposals
98
+ you approve, injected into answer context under a strict token budget,
99
+ fully listable and deletable. Agents can propose whole-folder
100
+ reorganizations — structurally incapable of proposing deletions.
101
+ - **Connected, selectively.** An approval-gated Obsidian vault bridge
102
+ (wikilinks become edges, idempotent re-runs) and a signed, encrypted
103
+ subgraph-share prototype where received knowledge arrives as proposals —
104
+ off by default behind `LATTICEAI_BRAIN_NETWORK`.
105
+
106
+ All of it lands with the floor intact: **6,261 tests, 100.00% of 37,590
107
+ statements and 10,658 branches**, verified on macOS 3.14, a fresh-resolve
108
+ python 3.11 environment, and a clean linux python:3.14 container.
98
109
 
99
110
  Release notes: [RELEASE.md](RELEASE.md) · Full history: [docs/CHANGELOG.md](docs/CHANGELOG.md)
100
111
 
101
- Expected artifacts for 11.0.1 release must use exact filenames:
112
+ Expected artifacts for 11.1.0 release must use exact filenames:
102
113
 
103
- - `dist/ltcai-11.0.1-py3-none-any.whl`
104
- - `dist/ltcai-11.0.1.tar.gz`
105
- - `ltcai-11.0.1.tgz`
106
- - `dist/ltcai-11.0.1.vsix`
107
- - `src-tauri/target/release/bundle/dmg/Lattice AI_11.0.1_aarch64.dmg`
114
+ - `dist/ltcai-11.1.0-py3-none-any.whl`
115
+ - `dist/ltcai-11.1.0.tar.gz`
116
+ - `ltcai-11.1.0.tgz`
117
+ - `dist/ltcai-11.1.0.vsix`
118
+ - `src-tauri/target/release/bundle/dmg/Lattice AI_11.1.0_aarch64.dmg`
108
119
 
109
120
  Do not use wildcard artifact uploads. Package registry publishing remains owner-run.
110
121
 
@@ -139,6 +150,7 @@ See [ARCHITECTURE.md](ARCHITECTURE.md) for details and
139
150
 
140
151
  | Version | Theme |
141
152
  | --- | --- |
153
+ | 11.1.0 | Product Intelligence |
142
154
  | 11.0.1 | Both Branches |
143
155
  | 11.0.0 | Full Measure |
144
156
  | 10.10.0 | Quiet Station |
package/docs/CHANGELOG.md CHANGED
@@ -4,6 +4,34 @@ The top entry is either the current unreleased main-branch work or the current
4
4
  release line. Older entries are historical and may describe behavior as it
5
5
  existed at that release.
6
6
 
7
+ ## [11.1.0] - 2026-08-10 — Product Intelligence Layer
8
+
9
+ ### Added
10
+ - 플러그형 벡터 인덱스 레이어(`lattice_brain/graph/vector_index/`):
11
+ BruteForce 기본, int8 Quantized·HNSW(`ltcai[hnsw]`) 옵트인, 영속 배경
12
+ 임베딩 큐(`vector_jobs`), `vector_freshness_breakdown()`, RRF 융합·이웃
13
+ 후보 확장 옵션. 하이브리드 p50 10k 299ms → 10.1ms, 50k 43.9ms
14
+ (recall@10 0.987, docs/PERFORMANCE.md).
15
+ - Temporal 지식 모델: `valid_from`/`valid_to`/`superseded_by`(멱등 제자리
16
+ 승급, NULL 규약) + `as_of(timestamp)` 슬라이스. 모순 감지 → 리뷰 제안
17
+ → 승인 시 temporal 스탬프. 이벤트 기반 합성(상위 개념·누락 엣지·
18
+ proactive Brief 제안, 25개 인제스트마다, 격리 배선) + 중요도/정리 제안.
19
+ 새 API: `/api/brain/proactive-brief|importance|synthesize|contradictions/*`.
20
+ - 멀티모달 1등 시민(`allow_multimodal`, 기본 꺼짐 — 꺼짐=바이트 동일):
21
+ Image/Audio 1등 노드, OCR·실캡션만(`caption_status`), 별도 이미지 벡터
22
+ 공간+late fusion, Evidence 인라인 썸네일(승인 게이트 비우회), 비디오는
23
+ 정직 거부.
24
+ - Self-Model 서브그래프(제안-우선 생성, 사용자 직접 소유), 컨텍스트
25
+ 예산 주입, 삭제 불가능 구조의 폴더 재구성 제안,
26
+ `/api/memory/self-model*` 5종.
27
+ - Obsidian vault 브릿지(`POST /api/ingestion/obsidian`, 승인 게이트,
28
+ 위키링크→엣지, 멱등) + 서명·암호화 선택적 서브그래프 공유 프로토타입
29
+ (`LATTICEAI_BRAIN_NETWORK` 기본 꺼짐, 수신=리뷰 제안).
30
+
31
+ ### Removed
32
+ - `VisionStub`/`get_vision_embedder` — 파일명으로 캡션을 합성하고 그
33
+ 문자열의 해시를 이미지 임베딩으로 저장하던 경로(정직성 위반) 삭제.
34
+
7
35
  ## [11.0.1] - 2026-08-10
8
36
 
9
37
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  # Community And Plugins
2
2
 
3
- Current release: **11.0.1 — Both Branches**.
3
+ Current release: **11.1.0 — Product Intelligence**.
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
@@ -3,7 +3,7 @@
3
3
  > **Status: canonical** — current contributor guidance, kept in sync with the
4
4
  > current release.
5
5
 
6
- Current release: **11.0.1 — Both Branches**.
6
+ Current release: **11.1.0 — Product Intelligence**.
7
7
 
8
8
  This document is for contributors working on the local-first Digital Brain
9
9
  codebase. Product positioning and quick start stay in `README.md`; release
@@ -1,6 +1,6 @@
1
1
  # Lattice AI Onboarding
2
2
 
3
- Current release: **11.0.1 — Both Branches**.
3
+ Current release: **11.1.0 — Product Intelligence**.
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
@@ -1,4 +1,4 @@
1
- # Lattice AI — Operations Guide (v11.0.1)
1
+ # Lattice AI — Operations Guide (v11.1.0)
2
2
 
3
3
  > **Status: canonical** — kept in sync with the current release. Storage layout
4
4
  > below reflects the SQLite live Brain store and workspace scoping, not the
@@ -110,6 +110,10 @@ Methodology, and what it does not cover:
110
110
  - Latency and memory are measured in separate passes. tracemalloc roughly
111
111
  triples Python allocation cost, so timing under it would measure the
112
112
  profiler — the `peak MB` column comes from one extra traced query.
113
+ - `peak MB` counts **Python** allocations only. hnswlib keeps its graph in C++
114
+ memory, which tracemalloc cannot see, so that column understates HNSW by the
115
+ size of the graph itself. Read it as "what the query costs the interpreter",
116
+ not as the process's resident size.
113
117
  - `first ms` is the *first* query after the corpus changed. For `hnsw` that is
114
118
  where the graph gets built and the `.hnsw` sidecar written; for the other two
115
119
  it is an ordinary query.
@@ -135,27 +139,27 @@ Run it:
135
139
  ### Measured, 2026-08-10 — 10 000 vectors, 30 queries, top_k 10
136
140
 
137
141
  macOS 27 (arm64, Apple Silicon), Python 3.14.5, SQLite 3.53.1,
138
- hnswlib 0.8.0, uncapped candidates, corpus build 2.12 s.
142
+ hnswlib 0.8.0, uncapped candidates, corpus build 2.19 s.
139
143
 
140
144
  | backend | p50 ms | p95 ms | first ms | peak MB | recall@10 | hybrid p50 ms |
141
145
  |-----------|-------:|-------:|---------:|--------:|----------:|--------------:|
142
- | brute | 288.89 | 291.95 | 290.68 | 39.72 | 1.000 | 296.63 |
143
- | quantized | 639.47 | 645.93 | 653.31 | 38.38 | 0.987 | 639.83 |
144
- | hnsw | 21.53 | 22.11 | 795.84 | 0.74 | 0.953 | 24.37 |
146
+ | brute | 293.25 | 299.31 | 293.91 | 39.72 | 1.000 | 299.17 |
147
+ | quantized | 640.58 | 650.15 | 636.87 | 38.38 | 0.987 | 653.33 |
148
+ | hnsw | 7.01 | 7.50 | 799.95 | 0.05 | 0.953 | 10.07 |
145
149
 
146
150
  What the table says, plainly:
147
151
 
148
- - **The default is exact and slow.** 289 ms per query at 10k, and hybrid
149
- inherits nearly all of it (297 ms) — the vector channel *is* the cost.
150
- - **HNSW meets the 11.1.0 target and prices it.** Hybrid p50 **24.4 ms** at 10k
151
- (target: < 50 ms), a 12x improvement, in exchange for **4.7% of the exact
152
+ - **The default is exact and slow.** 293 ms per query at 10k, and hybrid
153
+ inherits nearly all of it (299 ms) — the vector channel *is* the cost.
154
+ - **HNSW meets the 11.1.0 target and prices it.** Hybrid p50 **10.1 ms** at 10k
155
+ (target: < 50 ms), a 30x improvement, in exchange for **4.7% of the exact
152
156
  top-10 going missing**. That is the trade, stated as a number rather than as
153
- the word "approximate". Its 0.74 MB peak is the two-phase lookup: it reads
154
- only the ten rows it returns, where the exact scan reads all 10 000.
155
- - **The 796 ms first query is the graph build**, paid once per index generation
156
- and then persisted to the `.hnsw` sidecar. Any write to `vector_embeddings`
157
- invalidates the fingerprint and buys that cost again, which is why `brute`
158
- stays the default for a continuously-ingesting brain.
157
+ the word "approximate".
158
+ - **The 800 ms first query is the graph build**, paid once per index generation
159
+ and then persisted to the `.hnsw` sidecar (and held in memory for the rest of
160
+ the process). Any write to `vector_embeddings` invalidates the fingerprint
161
+ and buys that cost again, which is why `brute` stays the default for a
162
+ continuously-ingesting brain.
159
163
  - **Quantized is currently the wrong choice on every axis.** ~2.2x the latency
160
164
  for 0.987 recall, and its RAM advantage does not materialise (38.4 vs
161
165
  39.7 MB): the exact scan already feeds the index in bounded batches, so
@@ -163,16 +167,65 @@ What the table says, plainly:
163
167
  It ships as an honest, exhaustive backend and as the representation a held
164
168
  cross-query index would need. It is not a recommendation.
165
169
 
170
+ ### Measured, 2026-08-10 — 50 000 vectors, 15 queries, top_k 10
171
+
172
+ Same machine and settings; corpus build 11.19 s.
173
+
174
+ | backend | p50 ms | p95 ms | first ms | peak MB | recall@10 | hybrid p50 ms |
175
+ |-----------|--------:|--------:|---------:|--------:|----------:|--------------:|
176
+ | brute | 1515.31 | 1522.94 | 1520.89 | 194.92 | 1.000 | 1514.75 |
177
+ | quantized | 3254.76 | 3275.69 | 3253.54 | 195.05 | 0.967 | 3441.13 |
178
+ | hnsw | 35.58 | 38.19 | 6114.60 | 0.04 | 0.987 | 43.90 |
179
+
180
+ - **The exact scan is linear and unusable at this size**: 1.5 s per query, and
181
+ ~195 MB of Python allocation to score one question.
182
+ - **HNSW still clears the 50 ms budget at 5x the target corpus** — hybrid p50
183
+ 43.9 ms — and its recall here is 0.987, higher than the 10k run's 0.953
184
+ (15 queries is a small sample; treat the two as "around 0.95–0.99", not as a
185
+ trend).
186
+ - **The remaining HNSW cost is not the search.** 7 ms at 10k → 36 ms at 50k is
187
+ suspiciously linear for a graph index, and it is: every query first runs the
188
+ freshness check (`SELECT COUNT(*), MAX(indexed_at) FROM vector_embeddings
189
+ WHERE embedding_model=? AND embedding_dim=?`), which has no covering index
190
+ and walks the table. A named follow-up, not a mystery: an index on
191
+ `(embedding_model, embedding_dim)` would remove it. The budget is met either
192
+ way, so it was not worth a schema change in this release.
193
+ - **The 6.1 s first query is the 50k graph build.** It is paid once per index
194
+ generation, and the sidecar means a restart does not pay it again.
195
+
196
+ ### A measurement mistake worth keeping
197
+
198
+ The first 50k run reported HNSW recall of **0.18** and was wrong. The default
199
+ candidate cap (10 000) was still in force, so the "exact" baseline had scored
200
+ only the newest 10 000 of 50 000 rows while HNSW searched all of them — the
201
+ disagreement was the baseline's blind spot, not the ANN's error. The bench now
202
+ lifts the cap by default and prints a `trunc` column. Recorded here because
203
+ the failure mode is generic: *any* recall number measured against a truncated
204
+ baseline is measuring the truncation.
205
+
206
+ ### Not measured
207
+
208
+ - **100k+ vectors.** Nothing is claimed beyond the 50k run above.
209
+ - **sqlite-vec ANN.** The optional `ann` extra was not installed, so
210
+ `vector_search_backend` reported `bruteforce-cosine` throughout. Its numbers
211
+ are unknown, not zero.
212
+ - **A real embedding provider.** Everything here uses the deterministic hash
213
+ embedder. With a model- or network-backed embedder, query-embedding cost
214
+ moves into the foreground and these ratios change.
215
+ - **Resident process memory.** See the tracemalloc caveat above: the HNSW graph
216
+ lives outside Python's allocator and is not in the `peak MB` column.
217
+
166
218
  ## Observations
167
219
 
168
220
  - Keyword `search()` / `context_for_query()` stay low-millisecond thanks to
169
221
  the FTS5 trigram index; they are not the scaling bottleneck.
170
222
  - `traverse(depth=2)` cost grows with edge fan-out (synthetic corpus creates
171
223
  dense shared-concept hubs); p95 is ~5 ms at 500 sources and ~16 ms at 5000.
172
- - `vector_search()` is a brute-force scan over every stored embedding
173
- (O(index size) per query). It is the dominant cost at scale and the first
174
- candidate for an ANN/pruning optimization if vector recall becomes a hot
175
- path. The profiler caps vector queries at 10 for this reason.
224
+ - `vector_search()` on the default backend is a brute-force scan over every
225
+ stored embedding (O(index size) per query). It is the dominant cost at scale
226
+ — which is what the 11.1.0 backend table above measures, and what
227
+ `LATTICEAI_VECTOR_INDEX=hnsw` addresses. `profile_kg.py` still caps vector
228
+ queries at 10 for this reason.
176
229
  - `rebuild_vector_index(full=True)` embeds documents and chunks with the hash
177
230
  embedder; with a real embedding provider expect this phase to be slower by
178
231
  the provider's per-call latency times the item count.
@@ -1,6 +1,6 @@
1
1
  # Lattice AI Trust Model
2
2
 
3
- Current release: **11.0.1 — Both Branches**.
3
+ Current release: **11.1.0 — Product Intelligence**.
4
4
 
5
5
  Lattice AI is local-first, explicit about external communication, and honest
6
6
  when a capability is unavailable.
@@ -1,6 +1,6 @@
1
1
  # Why Lattice AI Exists
2
2
 
3
- Current release: **11.0.1 — Both Branches**.
3
+ Current release: **11.1.0 — Product Intelligence**.
4
4
 
5
5
  **Lattice AI is a local-first Digital Brain that keeps your knowledge durable
6
6
  across any AI model.**
package/docs/kg-schema.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Knowledge Graph Schema
2
2
 
3
- Current release: **11.0.1 — Both Branches**.
3
+ Current release: **11.1.0 — Product Intelligence**.
4
4
 
5
5
  명세 출처: `lattice_ai_full_spec.pptx` 슬라이드 20·21·22
6
6
  구현: `lattice_brain/graph/schema.py`
@@ -26,7 +26,7 @@ from .storage import (
26
26
  storage_from_env,
27
27
  )
28
28
 
29
- __version__ = "11.0.1"
29
+ __version__ = "11.1.0"
30
30
 
31
31
  __all__ = [
32
32
  "AgentRuntime",
@@ -8,7 +8,7 @@ import os
8
8
  import re
9
9
  import struct
10
10
  from dataclasses import dataclass
11
- from typing import Any, Dict, Iterable, List, Optional
11
+ from typing import Iterable, List
12
12
 
13
13
  DEFAULT_EMBEDDING_DIM = int(os.getenv("LATTICEAI_VECTOR_DIM", "384"))
14
14
  EMBEDDING_MODEL_ID = f"lattice-local-hash-v1:{DEFAULT_EMBEDDING_DIM}"
@@ -97,40 +97,15 @@ class LocalEmbeddingModel:
97
97
  return list(struct.unpack(f"<{count}f", payload[: count * 4]))
98
98
 
99
99
 
100
- # --- Large candidate #2 slice: multimodal (vision) stubs ---
101
- # Schema already defines IMAGE / IMAGE_TEXT / CONTAINS_IMAGE.
102
- # These stubs allow ingestion + retrieval paths to carry image signals without
103
- # requiring heavy deps at core. Real impl can swap in local vision (e.g. via
104
- # ollama vision or onnx CLIP) behind the same interface.
105
- @dataclass(frozen=True)
106
- class VisionStub:
107
- """Offline vision describe + embed stubs for multimodal Brain.
108
-
109
- describe: returns a short textual caption derived from metadata/filename.
110
- embed: produces a vector from image meta (size+format) + optional caption hash.
111
- Later: replace with real embedding model that accepts image bytes/path.
112
- """
113
- dim: int = DEFAULT_EMBEDDING_DIM
114
-
115
- def describe(self, path: str | None = None, meta: Optional[Dict[str, Any]] = None) -> str:
116
- meta = meta or {}
117
- w = meta.get("width") or meta.get("w") or "?"
118
- h = meta.get("height") or meta.get("h") or "?"
119
- fmt = meta.get("format") or meta.get("ext") or "img"
120
- name = (path or "").split("/")[-1] or "image"
121
- # deterministic caption stub (no external call)
122
- return f"Image {name} ({fmt} {w}x{h})"
123
-
124
- def embed_image(self, path: str | None = None, meta: Optional[Dict[str, Any]] = None, caption: str = "") -> List[float]:
125
- meta = meta or {}
126
- basis = f"{path or ''}|{meta.get('width',0)}x{meta.get('height',0)}|{meta.get('format','')}|{caption[:120]}"
127
- # reuse text embedder for determinism (image content hash would be better with real vision)
128
- model = LocalEmbeddingModel(dim=self.dim)
129
- return model.embed(basis)
130
-
131
-
132
- def get_vision_embedder(dim: int | None = None) -> VisionStub:
133
- return VisionStub(dim=dim or DEFAULT_EMBEDDING_DIM)
134
-
100
+ # Removed in v11.1.0: ``VisionStub`` / ``get_vision_embedder``.
101
+ #
102
+ # They produced a "caption" (``Image pic.png (PNG 12x8)``) and an "image
103
+ # embedding" (a hash of the filename and pixel dimensions) with no model
104
+ # involved, and ``discovery_index`` stored both. Once in the graph neither was
105
+ # distinguishable from something a vision model had actually said, which is the
106
+ # one property a caption must have. The honest seam is now
107
+ # :class:`lattice_brain.multimodal.MultimodalPorts`: a caption exists only when
108
+ # a vision-language model produced it, an image vector only when a vision model
109
+ # produced it, and their absence is recorded as an absence.
135
110
 
136
- __all__ = ["DEFAULT_EMBEDDING_DIM", "EMBEDDING_MODEL_ID", "LocalEmbeddingModel", "embedding_model_id", "VisionStub", "get_vision_embedder"]
111
+ __all__ = ["DEFAULT_EMBEDDING_DIM", "EMBEDDING_MODEL_ID", "LocalEmbeddingModel", "embedding_model_id"]
@@ -111,40 +111,38 @@ class KnowledgeGraphLocalIndexMixin(_Core):
111
111
  meta["text_slides"] = len(slides_text)
112
112
  text = "\n\n".join(slides_text)
113
113
  elif category == "image":
114
- from PIL import Image
114
+ text = self._extract_image_signals(path, meta, include_ocr=include_ocr)
115
+ return text[:200_000], meta
115
116
 
116
- with Image.open(str(path)) as image:
117
- meta.update(
118
- {
119
- "width": image.width,
120
- "height": image.height,
121
- "format": image.format,
122
- "mode": image.mode,
123
- "ocr_enabled": bool(include_ocr),
124
- }
125
- )
126
- if include_ocr:
127
- try:
128
- import pytesseract
117
+ def _extract_image_signals(
118
+ self, path: Path, meta: Dict[str, Any], *, include_ocr: bool
119
+ ) -> str:
120
+ """Dimensions, OCR text, and — only if a VLM exists — a caption.
129
121
 
130
- text = pytesseract.image_to_string(image)
131
- meta["ocr_chars"] = len(text)
132
- except (
133
- Exception
134
- ) as exc: # pragma: no cover - depends on local OCR runtime
135
- meta["ocr_error"] = str(exc)
136
- text = ""
137
- # Large candidate #2 slice: always attach vision stub describe for IMAGE node evidence
138
- try:
139
- from ..embeddings import get_vision_embedder
140
- v = get_vision_embedder()
141
- cap = v.describe(str(path), meta)
142
- meta["vision_caption"] = cap
143
- if not text:
144
- text = cap # fallback text signal for retrieval
145
- except Exception:
146
- meta["vision_caption"] = meta.get("vision_caption") or f"image:{path}"
147
- return text[:200_000], meta
122
+ Until v11.1.0 this path always attached a ``vision_caption`` built out
123
+ of the filename and the pixel dimensions (``Image pic.png (PNG 12x8)``)
124
+ and used it as the retrieval text. Nothing downstream could tell that
125
+ string apart from something a vision model had actually said about the
126
+ picture, so every screenshot in the graph carried a fake description.
127
+
128
+ Now the caption comes from the injected port and from nowhere else. A
129
+ picture with no OCR text and no model still gets indexed — under its
130
+ filename, which is a fact — and ``caption_status`` says why there is no
131
+ caption.
132
+ """
133
+ from ..multimodal import MultimodalPorts, extract_image_facts
134
+
135
+ ports = getattr(self, "multimodal_ports", None) or MultimodalPorts()
136
+ facts = extract_image_facts(str(path), ports=ports, ocr=include_ocr)
137
+ meta.update(facts.as_metadata())
138
+ meta["ocr_enabled"] = bool(include_ocr)
139
+ if facts.ocr_text:
140
+ meta["ocr_chars"] = len(facts.ocr_text)
141
+ if facts.ocr_status == "failed":
142
+ meta["ocr_error"] = facts.ocr_detail
143
+ if not facts.readable:
144
+ return ""
145
+ return facts.index_text() or path.name
148
146
 
149
147
  def _ensure_local_hierarchy(
150
148
  self,