ltcai 11.0.1 → 11.2.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 (140) hide show
  1. package/README.md +55 -43
  2. package/docs/CHANGELOG.md +61 -0
  3. package/docs/COMMUNITY_AND_PLUGINS.md +1 -1
  4. package/docs/DEVELOPMENT.md +1 -1
  5. package/docs/FEATURE_AUDIT_v11.2.0.md +393 -0
  6. package/docs/LAYOUT_REBUILD_SPEC.md +9 -1
  7. package/docs/ONBOARDING.md +1 -1
  8. package/docs/OPERATIONS.md +1 -1
  9. package/docs/PERFORMANCE.md +71 -18
  10. package/docs/TRUST_MODEL.md +1 -1
  11. package/docs/WHY_LATTICE.md +1 -1
  12. package/docs/architecture.md +6 -2
  13. package/docs/kg-schema.md +1 -1
  14. package/lattice_brain/__init__.py +1 -1
  15. package/lattice_brain/embeddings.py +12 -37
  16. package/lattice_brain/gates.py +125 -0
  17. package/lattice_brain/graph/discovery_index.py +30 -32
  18. package/lattice_brain/graph/fusion.py +35 -4
  19. package/lattice_brain/graph/image_vectors.py +230 -0
  20. package/lattice_brain/graph/ingest.py +11 -5
  21. package/lattice_brain/graph/projection.py +66 -8
  22. package/lattice_brain/graph/provenance.py +27 -2
  23. package/lattice_brain/graph/retrieval.py +113 -2
  24. package/lattice_brain/graph/retrieval_docgen.py +6 -6
  25. package/lattice_brain/graph/schema.py +18 -0
  26. package/lattice_brain/graph/store.py +9 -0
  27. package/lattice_brain/graph/vector_index/selector.py +32 -2
  28. package/lattice_brain/ingestion.py +363 -10
  29. package/lattice_brain/multimodal.py +1258 -0
  30. package/lattice_brain/portability.py +169 -32
  31. package/lattice_brain/runtime/multi_agent.py +1 -1
  32. package/lattice_brain/sealed_box.py +244 -0
  33. package/lattice_brain/self_model.py +77 -22
  34. package/lattice_brain/synthesis.py +24 -1
  35. package/latticeai/__init__.py +1 -1
  36. package/latticeai/api/brain_intelligence.py +4 -0
  37. package/latticeai/api/chat.py +11 -0
  38. package/latticeai/api/chat_helpers.py +16 -3
  39. package/latticeai/api/chat_hybrid.py +32 -1
  40. package/latticeai/api/features.py +70 -0
  41. package/latticeai/api/local_files.py +102 -0
  42. package/latticeai/api/memory.py +128 -1
  43. package/latticeai/api/portability.py +39 -4
  44. package/latticeai/api/review_queue.py +126 -0
  45. package/latticeai/api/search.py +16 -2
  46. package/latticeai/core/agent.py +59 -2
  47. package/latticeai/core/agent_prompts.py +66 -0
  48. package/latticeai/core/config.py +4 -1
  49. package/latticeai/core/context_builder.py +98 -11
  50. package/latticeai/core/embedding_providers.py +528 -0
  51. package/latticeai/core/legacy_compatibility.py +1 -1
  52. package/latticeai/core/marketplace.py +1 -1
  53. package/latticeai/core/messages.py +180 -0
  54. package/latticeai/core/model_compat.py +73 -2
  55. package/latticeai/core/workspace_os.py +43 -0
  56. package/latticeai/core/workspace_os_constants.py +1 -1
  57. package/latticeai/core/workspace_reorganization.py +335 -0
  58. package/latticeai/models/model_providers.py +12 -4
  59. package/latticeai/runtime/build_phases.py +33 -2
  60. package/latticeai/runtime/chat_wiring.py +4 -0
  61. package/latticeai/runtime/feature_toggle_wiring.py +163 -0
  62. package/latticeai/runtime/persistence_runtime.py +41 -4
  63. package/latticeai/runtime/router_registration.py +11 -0
  64. package/latticeai/runtime/runtime_context.py +1 -0
  65. package/latticeai/services/app_context.py +8 -0
  66. package/latticeai/services/architecture_readiness.py +1 -1
  67. package/latticeai/services/automation_intelligence.py +22 -2
  68. package/latticeai/services/brain_intelligence.py +123 -7
  69. package/latticeai/services/change_proposals.py +50 -10
  70. package/latticeai/services/command_center.py +10 -4
  71. package/latticeai/services/feature_toggles.py +502 -0
  72. package/latticeai/services/folder_watch.py +122 -1
  73. package/latticeai/services/hybrid_chat.py +56 -5
  74. package/latticeai/services/interop_bridges.py +978 -0
  75. package/latticeai/services/memory_service.py +34 -0
  76. package/latticeai/services/model_capability_registry.py +434 -261
  77. package/latticeai/services/model_catalog.py +95 -61
  78. package/latticeai/services/model_recommendation.py +18 -11
  79. package/latticeai/services/model_runtime.py +1 -1
  80. package/latticeai/services/multimodal_ports.py +112 -0
  81. package/latticeai/services/obsidian_bridge.py +16 -25
  82. package/latticeai/services/product_readiness.py +1 -1
  83. package/latticeai/services/search_service.py +149 -2
  84. package/latticeai/services/self_model_service.py +171 -0
  85. package/latticeai/services/tool_dispatch.py +4 -0
  86. package/latticeai/services/voice_capture.py +27 -1
  87. package/latticeai/setup/auto_setup.py +27 -30
  88. package/latticeai/setup/wizard.py +77 -44
  89. package/package.json +1 -1
  90. package/scripts/check_current_release_docs.mjs +1 -1
  91. package/scripts/check_server_i18n.mjs +1 -0
  92. package/scripts/release_screen_claims.json +22 -0
  93. package/scripts/verify_hf_model_registry.py +253 -218
  94. package/src-tauri/Cargo.lock +1 -1
  95. package/src-tauri/Cargo.toml +1 -1
  96. package/src-tauri/tauri.conf.json +1 -1
  97. package/static/app/asset-manifest.json +37 -37
  98. package/static/app/assets/{Act-D4zSxFR-.js → Act-AWf0SAKp.js} +1 -1
  99. package/static/app/assets/{AdminConsole-w5jBfPt2.js → AdminConsole-D0u8Tiyj.js} +1 -1
  100. package/static/app/assets/{Brain-C2EqQg74.js → Brain-tuhI4sOC.js} +1 -1
  101. package/static/app/assets/BrainHome-Ts7G_Ila.js +2 -0
  102. package/static/app/assets/BrainSignals-jMYgQ2Ar.js +1 -0
  103. package/static/app/assets/{Capture-DPqpGK8d.js → Capture-CqOSzyPr.js} +1 -1
  104. package/static/app/assets/{CommandPalette-CNf7h5fp.js → CommandPalette-DC0Bzh-I.js} +1 -1
  105. package/static/app/assets/{Library-BN0HYOfc.js → Library-CX-bbhmK.js} +1 -1
  106. package/static/app/assets/{LivingBrain-Dfq_wEDI.js → LivingBrain-DBwhto14.js} +1 -1
  107. package/static/app/assets/{ProductFlow-B-3O0rNV.js → ProductFlow-BHA2cfKI.js} +1 -1
  108. package/static/app/assets/{ReviewCard-gZ-tdqFM.js → ReviewCard-BUhCKRNM.js} +1 -1
  109. package/static/app/assets/{System-BElUcSSw.js → System-Bu2t5hn1.js} +1 -1
  110. package/static/app/assets/arrow-left-Dzwa5zRb.js +1 -0
  111. package/static/app/assets/{bot--qYHMtkP.js → bot-Cia42c2h.js} +1 -1
  112. package/static/app/assets/brain-DJMoqrwx.js +1 -0
  113. package/static/app/assets/{button-51Z3rsuv.js → button-2j2Ijzgq.js} +1 -1
  114. package/static/app/assets/{circle-pause-CMIiMaQl.js → circle-pause-BEFeWpVW.js} +1 -1
  115. package/static/app/assets/{circle-play-DZoO_cfG.js → circle-play-ujXMcHxl.js} +1 -1
  116. package/static/app/assets/{cpu-Bs6uc9W9.js → cpu-k4awryFq.js} +1 -1
  117. package/static/app/assets/{download-G-2olkWz.js → download-DFbLJ_ig.js} +1 -1
  118. package/static/app/assets/{folder-open-CTOspnmb.js → folder-open-7y_b6xkM.js} +1 -1
  119. package/static/app/assets/{hard-drive-CewHWJhn.js → hard-drive-Bidh02Kr.js} +1 -1
  120. package/static/app/assets/{index-D7Rr-J2Y.js → index-BpYkzcVm.js} +3 -3
  121. package/static/app/assets/index-DwDl9-8Y.css +2 -0
  122. package/static/app/assets/{input-D4w_BZWl.js → input-DSlJJxRs.js} +1 -1
  123. package/static/app/assets/{permissionCopy-CosBEXAZ.js → permissionCopy-Bpb83Hx9.js} +1 -1
  124. package/static/app/assets/{primitives-d0g9pvzS.js → primitives-BCx6TvfG.js} +1 -1
  125. package/static/app/assets/search-Cgy8cCFJ.js +1 -0
  126. package/static/app/assets/{share-2-NmD7e_oV.js → share-2-BH1M-WNi.js} +1 -1
  127. package/static/app/assets/{shield-alert-CcQeMuju.js → shield-alert-BlKdBXcG.js} +1 -1
  128. package/static/app/assets/{textarea-BPAJDc-0.js → textarea-CCWbUfFB.js} +1 -1
  129. package/static/app/assets/{useFocusTrap-C7YLdTBC.js → useFocusTrap-YdHQ7pJ1.js} +1 -1
  130. package/static/app/assets/{useQuery-DRyD9opW.js → useQuery-CXQiwbVT.js} +1 -1
  131. package/static/app/assets/{utils-DG1_ExrP.js → utils-zqPZJxdx.js} +2 -2
  132. package/static/app/assets/{workspace-CWVf3gsI.js → workspace-DXTihhfU.js} +1 -1
  133. package/static/app/index.html +4 -4
  134. package/static/sw.js +1 -1
  135. package/static/app/assets/BrainHome-CvXS6XiQ.js +0 -2
  136. package/static/app/assets/BrainSignals-DOE_KhOU.js +0 -1
  137. package/static/app/assets/arrow-left-CFNIMjhv.js +0 -1
  138. package/static/app/assets/brain-DDCLjRqO.js +0 -1
  139. package/static/app/assets/index-CkzokZAj.css +0 -2
  140. package/static/app/assets/search-BLCYt75v.js +0 -1
@@ -26,7 +26,8 @@ import re
26
26
  from typing import Any, Dict, List
27
27
 
28
28
  from lattice_brain.context import approx_tokens
29
- from lattice_brain.graph.retrieval import context_quality_signal
29
+ from lattice_brain.graph.retrieval import context_quality_signal, multimodal_signal
30
+ from lattice_brain.self_model import DEFAULT_SUMMARY_TOKENS, summary_for_prompt
30
31
 
31
32
  _CLEAN_RE = re.compile(r"\s+")
32
33
 
@@ -35,6 +36,20 @@ _CLEAN_RE = re.compile(r"\s+")
35
36
  # other on the same Brain.
36
37
  DEFAULT_DOCUMENT_CONTEXT_BUDGET = 2000
37
38
 
39
+ # ── Self-Model injection (v11.1.0) ──────────────────────────────────────────
40
+ # What the Brain knows about its owner rides along with the knowledge, so a
41
+ # generated document sounds like the person who asked for it. Three rules keep
42
+ # it from becoming another thing that can go wrong:
43
+ # 1. an empty Self-Model injects *nothing* — no header, no blank section;
44
+ # 2. the block never takes more than half the context budget, so the profile
45
+ # can never crowd out the knowledge the request is actually about;
46
+ # 3. the returned contract is unchanged — same keys, same
47
+ # ``context_quality`` shape, one extra trace section only when a block
48
+ # was really injected.
49
+ SELF_MODEL_SECTION_TITLE = "사용자 프로필"
50
+ SELF_MODEL_TRACE_SOURCE = "self_model"
51
+ SELF_MODEL_SECTION_HEADER = f"### 🙋 {SELF_MODEL_SECTION_TITLE}\n\n"
52
+
38
53
 
39
54
  def _clean(text: str, max_len: int = 700) -> str:
40
55
  return _CLEAN_RE.sub(" ", str(text or "")).strip()[:max_len]
@@ -72,6 +87,55 @@ def _fit_to_budget(context_md: str, budget: int) -> tuple:
72
87
  return head.rstrip(), True
73
88
 
74
89
 
90
+ def _self_model_budget(budget: int, limit_tokens: int) -> int:
91
+ """Tokens the profile block may use — never more than half the budget."""
92
+ if budget <= 0:
93
+ return limit_tokens
94
+ return min(limit_tokens, budget // 2)
95
+
96
+
97
+ def _self_model_block(
98
+ kg_store,
99
+ *,
100
+ enabled: bool,
101
+ budget: int,
102
+ limit_tokens: int,
103
+ allowed_workspaces,
104
+ ) -> str:
105
+ """Rendered profile section, or ``""`` when there is nothing to inject.
106
+
107
+ The section header is charged to the same allowance as the summary, so the
108
+ *rendered block* — not just its text — honours the ceiling.
109
+ """
110
+ allowance = _self_model_budget(budget, limit_tokens) - approx_tokens(
111
+ SELF_MODEL_SECTION_HEADER
112
+ )
113
+ if not enabled or allowance <= 0:
114
+ return ""
115
+ summary = summary_for_prompt(
116
+ kg_store, limit_tokens=allowance, allowed_workspaces=allowed_workspaces
117
+ )
118
+ if not summary:
119
+ return ""
120
+ return f"{SELF_MODEL_SECTION_HEADER}{summary}"
121
+
122
+
123
+ def _with_self_model(block: str, context_md: str) -> str:
124
+ """Prepend the profile block to rendered knowledge (either may be empty)."""
125
+ return "\n\n".join(part for part in (block, context_md) if part)
126
+
127
+
128
+ def _self_model_trace(block: str) -> List[Dict[str, Any]]:
129
+ if not block:
130
+ return []
131
+ return [{
132
+ "name": SELF_MODEL_SECTION_TITLE,
133
+ "source": SELF_MODEL_TRACE_SOURCE,
134
+ "approx_tokens": approx_tokens(block),
135
+ "provenance": [],
136
+ }]
137
+
138
+
75
139
  def retrieve_context_for_generation(
76
140
  kg_store,
77
141
  query: str,
@@ -81,6 +145,8 @@ def retrieve_context_for_generation(
81
145
  allowed_workspaces=None,
82
146
  include_legacy_global: bool = False,
83
147
  budget: int = DEFAULT_DOCUMENT_CONTEXT_BUDGET,
148
+ include_self_model: bool = True,
149
+ self_model_tokens: int = DEFAULT_SUMMARY_TOKENS,
84
150
  ) -> Dict[str, Any]:
85
151
  """Knowledge Graph에서 문서 생성에 필요한 컨텍스트를 검색·조합한다.
86
152
 
@@ -106,6 +172,21 @@ def retrieve_context_for_generation(
106
172
  if allowed_workspaces is not None
107
173
  else {}
108
174
  )
175
+ self_model_md = _self_model_block(
176
+ kg_store,
177
+ enabled=include_self_model,
178
+ budget=budget,
179
+ limit_tokens=self_model_tokens,
180
+ allowed_workspaces=allowed_workspaces,
181
+ )
182
+ # The profile's tokens come out of the same budget, so injecting it can
183
+ # never push the assembled context over the ceiling the caller asked for.
184
+ # The blank line that joins the two blocks is counted too — otherwise the
185
+ # assembled total lands one token over a tight budget.
186
+ knowledge_budget = budget
187
+ if self_model_md:
188
+ knowledge_budget -= approx_tokens(f"{self_model_md}\n\n")
189
+
109
190
  results = kg_store.search_for_document_generation(query, limit=max_results, **scope_kwargs)
110
191
  if not results:
111
192
  fallback_ctx = kg_store.context_for_query(
@@ -113,7 +194,7 @@ def retrieve_context_for_generation(
113
194
  limit=max_results,
114
195
  **scope_kwargs,
115
196
  )
116
- fallback_ctx, trimmed = _fit_to_budget(fallback_ctx or "", budget)
197
+ fallback_ctx, trimmed = _fit_to_budget(fallback_ctx or "", knowledge_budget)
117
198
  # Lexical fallback: the hybrid document search found nothing, so the
118
199
  # signal says lexical_only — exactly what chat reports in the same
119
200
  # situation, never a quiet downgrade.
@@ -121,16 +202,17 @@ def retrieve_context_for_generation(
121
202
  "lexical_only" if fallback_ctx else "none",
122
203
  1 if fallback_ctx else 0,
123
204
  )
205
+ assembled = _with_self_model(self_model_md, fallback_ctx)
124
206
  return {
125
207
  "query": query,
126
- "context_markdown": fallback_ctx,
208
+ "context_markdown": assembled,
127
209
  "sources": [],
128
210
  "stats": {"method": "fallback", "matches": 0, "budget_trimmed": trimmed},
129
211
  "context_quality": quality,
130
212
  "trace": {
131
213
  "budget_approx_tokens": budget,
132
- "used_approx_tokens": approx_tokens(fallback_ctx),
133
- "sections": [{
214
+ "used_approx_tokens": approx_tokens(assembled),
215
+ "sections": _self_model_trace(self_model_md) + [{
134
216
  "name": "Knowledge (fallback)",
135
217
  "source": "knowledge",
136
218
  "approx_tokens": approx_tokens(fallback_ctx),
@@ -154,11 +236,12 @@ def retrieve_context_for_generation(
154
236
  sections = _build_context_sections(results, extra_nodes_by_id, hop_data.get("edges", []))
155
237
  context_md = _render_markdown(query, sections)
156
238
  sources = _extract_sources(results)
157
- context_md, trimmed = _fit_to_budget(context_md, budget)
239
+ context_md, trimmed = _fit_to_budget(context_md, knowledge_budget)
240
+ assembled = _with_self_model(self_model_md, context_md)
158
241
 
159
242
  return {
160
243
  "query": query,
161
- "context_markdown": context_md,
244
+ "context_markdown": assembled,
162
245
  "sources": sources,
163
246
  "stats": {
164
247
  "method": "hybrid",
@@ -167,12 +250,15 @@ def retrieve_context_for_generation(
167
250
  "graph_edges": len(hop_data.get("edges", [])),
168
251
  "budget_trimmed": trimmed,
169
252
  },
170
- # Same honest signal chat reports for the same Brain.
171
- "context_quality": context_quality_signal("hybrid", len(results)),
253
+ # Same honest signal chat reports for the same Brain — including the
254
+ # multimodal key when the document's context really rests on pictures.
255
+ "context_quality": context_quality_signal(
256
+ "hybrid", len(results), multimodal=multimodal_signal(results)
257
+ ),
172
258
  "trace": {
173
259
  "budget_approx_tokens": budget,
174
- "used_approx_tokens": approx_tokens(context_md),
175
- "sections": [
260
+ "used_approx_tokens": approx_tokens(assembled),
261
+ "sections": _self_model_trace(self_model_md) + [
176
262
  {
177
263
  "name": section["title"],
178
264
  "source": "knowledge",
@@ -199,6 +285,7 @@ def _build_context_sections(
199
285
 
200
286
  docs = [r for r in primary_results if r["type"] in (
201
287
  "Document", "File", "SlideDeck", "Spreadsheet", "CodeFile", "Image", "ImageText",
288
+ "Audio",
202
289
  )]
203
290
  if docs:
204
291
  sections.append({