ltcai 4.3.1 → 4.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.
Files changed (131) hide show
  1. package/README.md +191 -278
  2. package/docs/CHANGELOG.md +128 -0
  3. package/docs/V4_3_2_DEADCODE_AUDIT_REPORT.md +174 -0
  4. package/docs/V4_3_2_DOCUMENTATION_CLEANUP_REPORT.md +81 -0
  5. package/docs/V4_3_2_GITHUB_VERCEL_CHECK_REPORT.md +75 -0
  6. package/docs/V4_3_2_GRAPH_UX_REPORT.md +48 -0
  7. package/docs/V4_3_2_INDEPENDENT_AUDIT_PACKAGE.md +209 -0
  8. package/docs/V4_3_2_PRODUCT_POLISH_REPORT.md +57 -0
  9. package/docs/V4_3_2_SELF_AUDIT_REPORT.md +63 -0
  10. package/docs/V4_3_2_VALIDATION_REPORT.md +97 -0
  11. package/docs/V4_3_3_VALIDATION_REPORT.md +46 -0
  12. package/docs/V4_4_0_EXTRACTION_REPORT.md +239 -0
  13. package/docs/V4_DIGITAL_BRAIN_RECOVERY.md +18 -19
  14. package/frontend/openapi.json +1 -1
  15. package/frontend/src/components/primitives.tsx +92 -10
  16. package/frontend/src/pages/Act.tsx +11 -9
  17. package/frontend/src/pages/Ask.tsx +2 -2
  18. package/frontend/src/pages/Brain.tsx +607 -65
  19. package/frontend/src/pages/Capture.tsx +11 -7
  20. package/frontend/src/pages/Library.tsx +3 -3
  21. package/frontend/src/pages/System.tsx +186 -23
  22. package/lattice_brain/__init__.py +38 -23
  23. package/lattice_brain/_kg_common.py +11 -1
  24. package/lattice_brain/context.py +212 -2
  25. package/lattice_brain/conversations.py +234 -1
  26. package/lattice_brain/discovery.py +11 -1
  27. package/lattice_brain/documents.py +11 -1
  28. package/lattice_brain/graph/__init__.py +28 -0
  29. package/lattice_brain/graph/_kg_common.py +1123 -0
  30. package/lattice_brain/graph/curator.py +473 -0
  31. package/lattice_brain/graph/discovery.py +1455 -0
  32. package/lattice_brain/graph/documents.py +218 -0
  33. package/lattice_brain/graph/identity.py +175 -0
  34. package/lattice_brain/graph/ingest.py +644 -0
  35. package/lattice_brain/graph/network.py +205 -0
  36. package/lattice_brain/graph/projection.py +571 -0
  37. package/lattice_brain/graph/provenance.py +401 -0
  38. package/lattice_brain/graph/retrieval.py +1341 -0
  39. package/lattice_brain/graph/schema.py +640 -0
  40. package/lattice_brain/graph/store.py +237 -0
  41. package/lattice_brain/graph/write_master.py +225 -0
  42. package/lattice_brain/identity.py +11 -13
  43. package/lattice_brain/ingest.py +11 -1
  44. package/lattice_brain/ingestion.py +318 -0
  45. package/lattice_brain/memory.py +100 -1
  46. package/lattice_brain/network.py +11 -1
  47. package/lattice_brain/portability.py +431 -0
  48. package/lattice_brain/projection.py +11 -1
  49. package/lattice_brain/provenance.py +11 -1
  50. package/lattice_brain/retrieval.py +11 -1
  51. package/lattice_brain/runtime/__init__.py +32 -0
  52. package/lattice_brain/runtime/agent_runtime.py +569 -0
  53. package/lattice_brain/runtime/hooks.py +754 -0
  54. package/lattice_brain/runtime/multi_agent.py +795 -0
  55. package/lattice_brain/schema.py +11 -1
  56. package/lattice_brain/store.py +10 -2
  57. package/lattice_brain/workflow.py +461 -0
  58. package/lattice_brain/write_master.py +11 -1
  59. package/latticeai/__init__.py +1 -1
  60. package/latticeai/api/agents.py +2 -2
  61. package/latticeai/api/browser.py +1 -1
  62. package/latticeai/api/chat.py +1 -1
  63. package/latticeai/api/computer_use.py +1 -1
  64. package/latticeai/api/hooks.py +2 -2
  65. package/latticeai/api/mcp.py +1 -1
  66. package/latticeai/api/tools.py +1 -1
  67. package/latticeai/api/workflow_designer.py +2 -2
  68. package/latticeai/app_factory.py +4 -4
  69. package/latticeai/brain/__init__.py +24 -6
  70. package/latticeai/brain/_kg_common.py +11 -1117
  71. package/latticeai/brain/context.py +12 -208
  72. package/latticeai/brain/conversations.py +12 -231
  73. package/latticeai/brain/discovery.py +13 -1451
  74. package/latticeai/brain/documents.py +13 -214
  75. package/latticeai/brain/identity.py +11 -169
  76. package/latticeai/brain/ingest.py +13 -640
  77. package/latticeai/brain/memory.py +12 -97
  78. package/latticeai/brain/network.py +12 -200
  79. package/latticeai/brain/projection.py +13 -567
  80. package/latticeai/brain/provenance.py +13 -397
  81. package/latticeai/brain/retrieval.py +13 -1337
  82. package/latticeai/brain/schema.py +12 -635
  83. package/latticeai/brain/store.py +13 -233
  84. package/latticeai/brain/write_master.py +13 -221
  85. package/latticeai/core/agent.py +1 -1
  86. package/latticeai/core/agent_registry.py +2 -2
  87. package/latticeai/core/builtin_hooks.py +2 -2
  88. package/latticeai/core/graph_curator.py +6 -468
  89. package/latticeai/core/hooks.py +6 -749
  90. package/latticeai/core/marketplace.py +1 -1
  91. package/latticeai/core/multi_agent.py +6 -790
  92. package/latticeai/core/workflow_engine.py +6 -456
  93. package/latticeai/core/workspace_os.py +1 -1
  94. package/latticeai/services/agent_runtime.py +6 -564
  95. package/latticeai/services/ingestion.py +6 -313
  96. package/latticeai/services/kg_portability.py +6 -426
  97. package/latticeai/services/platform_runtime.py +3 -3
  98. package/latticeai/services/run_executor.py +1 -1
  99. package/latticeai/services/upload_service.py +1 -1
  100. package/p_reinforce.py +1 -1
  101. package/package.json +3 -6
  102. package/scripts/build_vercel_static.mjs +77 -0
  103. package/scripts/bump_version.py +1 -1
  104. package/scripts/check_markdown_links.mjs +75 -0
  105. package/scripts/wheel_smoke.py +7 -0
  106. package/src-tauri/Cargo.lock +1 -1
  107. package/src-tauri/Cargo.toml +1 -1
  108. package/src-tauri/src/main.rs +12 -2
  109. package/src-tauri/tauri.conf.json +1 -1
  110. package/static/app/asset-manifest.json +5 -5
  111. package/static/app/assets/index-CHHal8Zl.css +2 -0
  112. package/static/app/assets/index-pdzil9ac.js +333 -0
  113. package/static/app/assets/index-pdzil9ac.js.map +1 -0
  114. package/static/app/index.html +2 -2
  115. package/latticeai/api/deps.py +0 -15
  116. package/scripts/capture/README.md +0 -28
  117. package/scripts/capture/capture_enterprise.js +0 -8
  118. package/scripts/capture/capture_graph.js +0 -8
  119. package/scripts/capture/capture_onboarding.js +0 -8
  120. package/scripts/capture/capture_page.js +0 -43
  121. package/scripts/capture/capture_release_media.js +0 -125
  122. package/scripts/capture/capture_skills.js +0 -8
  123. package/scripts/capture/capture_v340.js +0 -88
  124. package/scripts/capture/capture_workspace.js +0 -8
  125. package/scripts/generate_diagrams.py +0 -512
  126. package/scripts/release-0.3.1.sh +0 -105
  127. package/scripts/take_screenshots.js +0 -69
  128. package/static/app/assets/index-BhPuj8rT.js +0 -333
  129. package/static/app/assets/index-BhPuj8rT.js.map +0 -1
  130. package/static/app/assets/index-yZswHE3d.css +0 -2
  131. package/static/css/tokens.3ba22e37.css +0 -260
@@ -1,318 +1,11 @@
1
- """Unified ingestion pipeline the single write-side seam into the Knowledge Graph.
1
+ """Compatibility shim: physically moved to ``lattice_brain.ingestion``.
2
2
 
3
- v3.6.0 Knowledge Graph First principle: *no data source bypasses the Knowledge
4
- Graph and no source creates an isolated silo*. Every source — local files,
5
- connected folders, PDFs/Markdown/text/code, web URLs, browser tabs — is
6
- normalized into one :class:`IngestionItem` and pushed through one
7
- :meth:`IngestionPipeline.ingest` entrypoint:
8
-
9
- Source → normalize → content hash → (file | text) ingest → provenance
10
-
11
- The pipeline is deliberately thin. It owns normalization, idempotency reporting,
12
- provenance capture, and — crucially — routing every ingest through the shared
13
- ``dispatch_tool`` lifecycle so ``pre_tool``/``post_tool`` hooks fire on data
14
- ingestion exactly as they do on tool calls. The heavy graph construction lives in
15
- :class:`knowledge_graph.KnowledgeGraphStore` (``ingest_document`` for files,
16
- ``ingest_source`` for text/web), which this module composes rather than
17
- re-implements.
3
+ Aliases itself to the physical module so identity, module-level state, and
4
+ monkeypatching keep working through the old import path.
18
5
  """
19
6
 
20
- from __future__ import annotations
21
-
22
- import hashlib
23
- from dataclasses import dataclass, field
24
- from datetime import datetime, timezone
25
- from pathlib import Path
26
- from typing import Any, Dict, List, Optional
27
-
28
- from latticeai.core.hooks import dispatch_tool
29
-
30
- # Source types that arrive as a file on disk (read via ingest_document).
31
- FILE_SOURCE_TYPES = frozenset({"file", "local_file", "upload", "pdf"})
32
- # Source types that arrive as extracted text (read via ingest_source).
33
- TEXT_SOURCE_TYPES = frozenset(
34
- {"web_url", "browser_tab", "text", "markdown", "note", "code", "clipboard"}
35
- )
36
- # Conversational exchanges (read via ingest_message — role/content semantics,
37
- # conversation chaining). v4: chat and MCP messages stop bypassing the
38
- # pipeline, so they carry provenance and fire the hook lifecycle like every
39
- # other source.
40
- CHAT_SOURCE_TYPES = frozenset({"chat_message", "mcp_message"})
41
- # Typed memory records (read via ingest_event → Decision/Experience/Event
42
- # nodes). The Memory System writes through the same door as everything else.
43
- MEMORY_SOURCE_TYPES = frozenset({"decision", "experience", "workspace_event"})
44
- _MEMORY_NODE_TYPES = {"decision": "Decision", "experience": "Experience", "workspace_event": "Event"}
45
-
46
- DEFAULT_MAX_TEXT_BYTES = 5 * 1024 * 1024 # 5 MB of extracted text per item
47
-
48
-
49
- def _now_iso() -> str:
50
- return datetime.now(timezone.utc).isoformat()
51
-
52
-
53
- @dataclass
54
- class IngestionItem:
55
- """A single thing to ingest, normalized across every source type."""
56
-
57
- source_type: str
58
- title: Optional[str] = None
59
- text: Optional[str] = None # text/web sources
60
- path: Optional[str] = None # file sources
61
- source_uri: Optional[str] = None
62
- mime_type: Optional[str] = None
63
- owner: Optional[str] = None
64
- workspace_id: Optional[str] = None
65
- permissions: Optional[Dict[str, Any]] = None
66
- captured_at: Optional[str] = None
67
- modified_at: Optional[str] = None
68
- conversation_id: Optional[str] = None
69
- agent_used: Optional[str] = None
70
- metadata: Dict[str, Any] = field(default_factory=dict)
71
-
72
-
73
- @dataclass
74
- class IngestionResult:
75
- """The outcome of one ingestion, including provenance and idempotency."""
76
-
77
- status: str # ok | unavailable | blocked | failed
78
- source_type: str
79
- node_id: Optional[str] = None
80
- source_node_id: Optional[str] = None
81
- content_hash: Optional[str] = None
82
- title: Optional[str] = None
83
- chunk_ids: List[str] = field(default_factory=list)
84
- chunk_count: int = 0
85
- duplicate: bool = False
86
- embedded: bool = False
87
- indexing_status: str = "pending" # indexed | skipped | failed | pending
88
- provenance_id: Optional[str] = None
89
- detail: Optional[str] = None
90
-
91
- def as_dict(self) -> Dict[str, Any]:
92
- return {
93
- "status": self.status,
94
- "source_type": self.source_type,
95
- "node_id": self.node_id,
96
- "source_node_id": self.source_node_id,
97
- "content_hash": self.content_hash,
98
- "title": self.title,
99
- "chunk_ids": self.chunk_ids,
100
- "chunk_count": self.chunk_count,
101
- "duplicate": self.duplicate,
102
- "embedded": self.embedded,
103
- "indexing_status": self.indexing_status,
104
- "provenance_id": self.provenance_id,
105
- "detail": self.detail,
106
- }
107
-
108
-
109
- class IngestionPipeline:
110
- """Single normalized entrypoint that feeds every source into the graph."""
111
-
112
- def __init__(
113
- self,
114
- knowledge_graph: Any,
115
- *,
116
- hooks: Any = None,
117
- enable_graph: bool = True,
118
- audit: Optional[Any] = None,
119
- max_text_bytes: int = DEFAULT_MAX_TEXT_BYTES,
120
- pipeline_name: str = "unified-ingestion",
121
- ) -> None:
122
- self._kg = knowledge_graph
123
- self._hooks = hooks
124
- self._enable = bool(enable_graph)
125
- self._audit = audit
126
- self._max_text_bytes = int(max_text_bytes)
127
- self._pipeline_name = pipeline_name
128
-
129
- def available(self) -> bool:
130
- return self._enable and self._kg is not None
131
-
132
- # ── public API ───────────────────────────────────────────────────────────
133
- def ingest(self, item: IngestionItem, *, user_email: Optional[str] = None) -> IngestionResult:
134
- """Normalize, hash, route through dispatch_tool, and record provenance."""
135
- source_type = str(item.source_type or "text").strip()
136
- if not self.available():
137
- return IngestionResult(
138
- status="unavailable", source_type=source_type,
139
- indexing_status="skipped",
140
- detail="Knowledge Graph is disabled (LATTICEAI_ENABLE_GRAPH).",
141
- )
142
-
143
- captured_at = item.captured_at or _now_iso()
144
- owner = item.owner or user_email
145
- tool_name = f"kg_ingest.{source_type}"
146
- # Only the keys are read by the hook payload, so this dict is safe/cheap.
147
- args = {
148
- "source_type": source_type,
149
- "source_uri": item.source_uri,
150
- "owner": owner,
151
- "workspace_id": item.workspace_id,
152
- }
153
-
154
- def _run() -> Dict[str, Any]:
155
- if source_type in CHAT_SOURCE_TYPES:
156
- return self._ingest_chat(item, source_type=source_type, owner=owner)
157
- if source_type in MEMORY_SOURCE_TYPES:
158
- return self._ingest_memory_record(item, source_type=source_type, owner=owner)
159
- if source_type in FILE_SOURCE_TYPES or (item.path and not item.text):
160
- return self._ingest_file(item, source_type=source_type, owner=owner, captured_at=captured_at)
161
- return self._ingest_text(item, source_type=source_type, owner=owner, captured_at=captured_at)
162
-
163
- try:
164
- raw = dispatch_tool(
165
- self._hooks, tool_name, args, _run,
166
- user_email=user_email, workspace_id=item.workspace_id, source="ingestion",
167
- )
168
- except PermissionError as exc:
169
- return IngestionResult(
170
- status="blocked", source_type=source_type,
171
- indexing_status="skipped", detail=str(exc),
172
- )
173
- except FileNotFoundError as exc:
174
- return IngestionResult(
175
- status="failed", source_type=source_type,
176
- indexing_status="failed", detail=str(exc),
177
- )
178
- except Exception as exc: # noqa: BLE001 — surface as a failed result, never crash the caller
179
- return IngestionResult(
180
- status="failed", source_type=source_type,
181
- indexing_status="failed", detail=str(exc),
182
- )
183
-
184
- node_id = raw.get("node_id")
185
- content_hash = raw.get("content_hash") or raw.get("sha256")
186
- chunk_ids = list(raw.get("chunk_ids") or [])
187
- embedded = bool(self._kg.node_is_embedded(node_id)) if node_id else False
188
- title = raw.get("title") or item.title
189
-
190
- prov = self._kg.record_provenance(
191
- node_id=node_id,
192
- source_type=source_type,
193
- pipeline=self._pipeline_name,
194
- source_uri=item.source_uri,
195
- content_hash=content_hash,
196
- title=title,
197
- owner=owner,
198
- workspace_id=item.workspace_id,
199
- captured_at=captured_at,
200
- modified_at=item.modified_at,
201
- embedded=embedded,
202
- linked=bool(raw.get("source_node_id")),
203
- duplicate=bool(raw.get("duplicate")),
204
- agent_used=item.agent_used,
205
- chunk_count=len(chunk_ids),
206
- permissions=item.permissions,
207
- metadata=item.metadata,
208
- )
209
- if self._audit is not None:
210
- try:
211
- self._audit(
212
- "kg_ingest",
213
- {
214
- "source_type": source_type, "node_id": node_id,
215
- "content_hash": content_hash, "duplicate": bool(raw.get("duplicate")),
216
- },
217
- user_email,
218
- )
219
- except Exception: # noqa: BLE001 — audit must never break ingestion
220
- pass
221
-
222
- return IngestionResult(
223
- status="ok",
224
- source_type=source_type,
225
- node_id=node_id,
226
- source_node_id=raw.get("source_node_id"),
227
- content_hash=content_hash,
228
- title=title,
229
- chunk_ids=chunk_ids,
230
- chunk_count=len(chunk_ids),
231
- duplicate=bool(raw.get("duplicate")),
232
- embedded=embedded,
233
- indexing_status="indexed",
234
- provenance_id=prov.get("id"),
235
- )
236
-
237
- # ── routing helpers ──────────────────────────────────────────────────────
238
- def _ingest_text(self, item, *, source_type, owner, captured_at) -> Dict[str, Any]:
239
- text = item.text or ""
240
- if len(text.encode("utf-8", "ignore")) > self._max_text_bytes:
241
- raise ValueError(
242
- f"Text payload exceeds the {self._max_text_bytes // (1024 * 1024)}MB ingestion limit."
243
- )
244
- title = item.title or item.source_uri or source_type
245
- return self._kg.ingest_source(
246
- source_type=source_type,
247
- title=title,
248
- text=text,
249
- source_uri=item.source_uri,
250
- owner=owner,
251
- workspace_id=item.workspace_id,
252
- permissions=item.permissions,
253
- captured_at=captured_at,
254
- modified_at=item.modified_at,
255
- conversation_id=item.conversation_id,
256
- metadata={"mime_type": item.mime_type, **(item.metadata or {})},
257
- )
258
-
259
- def _ingest_chat(self, item, *, source_type, owner) -> Dict[str, Any]:
260
- text = item.text or ""
261
- meta = item.metadata or {}
262
- role = str(meta.get("role") or "user")
263
- result = self._kg.ingest_message(
264
- role,
265
- text,
266
- user_email=owner,
267
- user_nickname=meta.get("user_nickname"),
268
- source=meta.get("source") or source_type,
269
- conversation_id=item.conversation_id,
270
- raw=meta.get("raw"),
271
- )
272
- # ingest_message reports message/response node ids; normalize the keys
273
- # the provenance step expects.
274
- result.setdefault("node_id", result.get("node_id") or result.get("message_node_id") or result.get("id"))
275
- result.setdefault("title", item.title or text[:80])
276
- return result
277
-
278
- def _ingest_memory_record(self, item, *, source_type, owner) -> Dict[str, Any]:
279
- node_type = _MEMORY_NODE_TYPES[source_type]
280
- meta = item.metadata or {}
281
- result = self._kg.ingest_event(
282
- node_type,
283
- item.title or (item.text or node_type)[:120],
284
- user_email=owner,
285
- source=meta.get("source") or source_type,
286
- conversation_id=item.conversation_id,
287
- metadata={**meta, "detail": (item.text or "")[:2000]},
288
- )
289
- result.setdefault("node_id", result.get("node_id") or result.get("id"))
290
- result.setdefault("title", item.title)
291
- return result
292
-
293
- def _ingest_file(self, item, *, source_type, owner, captured_at) -> Dict[str, Any]:
294
- if not item.path:
295
- raise ValueError("File ingestion requires a path.")
296
- path = Path(item.path)
297
- if not path.exists():
298
- raise FileNotFoundError(f"File not found: {path}")
299
- return self._kg.ingest_document(
300
- path,
301
- original_filename=item.title or path.name,
302
- mime_type=item.mime_type,
303
- uploader=owner,
304
- conversation_id=item.conversation_id,
305
- extracted=item.metadata.get("extracted") if item.metadata else None,
306
- source_type=source_type,
307
- source_uri=item.source_uri or str(path),
308
- captured_at=captured_at,
309
- modified_at=item.modified_at,
310
- owner=owner,
311
- workspace_id=item.workspace_id,
312
- permissions=item.permissions,
313
- )
7
+ import sys
314
8
 
9
+ import lattice_brain.ingestion as _impl
315
10
 
316
- def content_hash_text(text: str) -> str:
317
- """Canonical content hash for a text payload (matches store hashing scheme)."""
318
- return hashlib.sha256((text or "").encode("utf-8", "ignore")).hexdigest()
11
+ sys.modules[__name__] = _impl