ltcai 4.0.1 → 4.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 (192) hide show
  1. package/README.md +33 -24
  2. package/desktop/electron/main.cjs +44 -0
  3. package/docs/CHANGELOG.md +84 -0
  4. package/docs/V4_1_FRONTEND_ARCHITECTURE_REVIEW.md +65 -0
  5. package/docs/V4_1_FRONTEND_MIGRATION_REPORT.md +70 -0
  6. package/docs/V4_1_VALIDATION_REPORT.md +47 -0
  7. package/docs/V4_2_BRAIN_CORE_ARCHITECTURE.md +97 -0
  8. package/docs/V4_2_STORAGE_MIGRATION_REPORT.md +91 -0
  9. package/docs/V4_2_VALIDATION_REPORT.md +89 -0
  10. package/docs/V4_DIGITAL_BRAIN_RECOVERY.md +31 -26
  11. package/frontend/index.html +24 -0
  12. package/frontend/openapi.json +14436 -0
  13. package/frontend/src/App.tsx +184 -0
  14. package/frontend/src/api/client.ts +320 -0
  15. package/frontend/src/api/openapi.ts +16921 -0
  16. package/frontend/src/components/primitives.tsx +204 -0
  17. package/frontend/src/components/ui/badge.tsx +27 -0
  18. package/frontend/src/components/ui/button.tsx +37 -0
  19. package/frontend/src/components/ui/card.tsx +22 -0
  20. package/frontend/src/components/ui/input.tsx +16 -0
  21. package/frontend/src/components/ui/textarea.tsx +16 -0
  22. package/frontend/src/lib/utils.ts +33 -0
  23. package/frontend/src/main.tsx +23 -0
  24. package/frontend/src/pages/Act.tsx +245 -0
  25. package/frontend/src/pages/Ask.tsx +200 -0
  26. package/frontend/src/pages/Brain.tsx +267 -0
  27. package/frontend/src/pages/Capture.tsx +158 -0
  28. package/frontend/src/pages/Library.tsx +187 -0
  29. package/frontend/src/pages/System.tsx +378 -0
  30. package/frontend/src/routes.ts +85 -0
  31. package/frontend/src/store/appStore.ts +54 -0
  32. package/frontend/src/styles.css +107 -0
  33. package/kg_schema.py +1 -1
  34. package/knowledge_graph.py +4 -4
  35. package/lattice_brain/__init__.py +70 -0
  36. package/lattice_brain/_kg_common.py +1 -0
  37. package/lattice_brain/archive.py +133 -0
  38. package/lattice_brain/context.py +3 -0
  39. package/lattice_brain/conversations.py +3 -0
  40. package/lattice_brain/core.py +82 -0
  41. package/lattice_brain/discovery.py +1 -0
  42. package/lattice_brain/documents.py +1 -0
  43. package/lattice_brain/embeddings.py +82 -0
  44. package/lattice_brain/identity.py +13 -0
  45. package/lattice_brain/ingest.py +1 -0
  46. package/lattice_brain/memory.py +3 -0
  47. package/lattice_brain/network.py +1 -0
  48. package/lattice_brain/projection.py +1 -0
  49. package/lattice_brain/provenance.py +1 -0
  50. package/lattice_brain/retrieval.py +1 -0
  51. package/lattice_brain/schema.py +1 -0
  52. package/lattice_brain/storage/__init__.py +22 -0
  53. package/lattice_brain/storage/base.py +72 -0
  54. package/lattice_brain/storage/docker.py +105 -0
  55. package/lattice_brain/storage/factory.py +31 -0
  56. package/lattice_brain/storage/migration.py +190 -0
  57. package/lattice_brain/storage/postgres.py +123 -0
  58. package/lattice_brain/storage/sqlite.py +128 -0
  59. package/lattice_brain/store.py +3 -0
  60. package/lattice_brain/write_master.py +1 -0
  61. package/latticeai/__init__.py +1 -1
  62. package/latticeai/api/portability.py +69 -0
  63. package/latticeai/api/setup.py +5 -4
  64. package/latticeai/api/static_routes.py +4 -4
  65. package/latticeai/app_factory.py +17 -10
  66. package/latticeai/brain/__init__.py +6 -6
  67. package/latticeai/brain/_kg_common.py +1 -1
  68. package/latticeai/brain/network.py +1 -1
  69. package/latticeai/brain/retrieval.py +15 -0
  70. package/latticeai/brain/store.py +22 -6
  71. package/latticeai/core/config.py +8 -0
  72. package/latticeai/core/marketplace.py +1 -1
  73. package/latticeai/core/multi_agent.py +1 -1
  74. package/latticeai/core/workspace_os.py +1 -1
  75. package/latticeai/services/kg_portability.py +82 -1
  76. package/package.json +55 -15
  77. package/scripts/build_frontend_assets.mjs +38 -0
  78. package/scripts/bump_version.py +4 -1
  79. package/scripts/export_openapi.py +31 -0
  80. package/scripts/lint_frontend.mjs +91 -0
  81. package/scripts/migrate_brain_storage.py +53 -0
  82. package/scripts/run_python.mjs +47 -0
  83. package/scripts/wheel_smoke.py +3 -0
  84. package/src-tauri/Cargo.lock +4833 -0
  85. package/src-tauri/Cargo.toml +19 -0
  86. package/src-tauri/build.rs +3 -0
  87. package/src-tauri/capabilities/default.json +7 -0
  88. package/src-tauri/src/main.rs +78 -0
  89. package/src-tauri/tauri.conf.json +39 -0
  90. package/static/app/asset-manifest.json +32 -0
  91. package/static/app/assets/core-CwxXejkd.js +2 -0
  92. package/static/app/assets/core-CwxXejkd.js.map +1 -0
  93. package/static/app/assets/index-CDjiH_se.css +2 -0
  94. package/static/app/assets/index-C_HAkbAg.js +333 -0
  95. package/static/app/assets/index-C_HAkbAg.js.map +1 -0
  96. package/static/app/index.html +25 -0
  97. package/static/manifest.json +2 -2
  98. package/static/sw.js +4 -4
  99. package/scripts/build_v3_assets.mjs +0 -170
  100. package/scripts/lint_v3.mjs +0 -120
  101. package/static/v3/asset-manifest.json +0 -63
  102. package/static/v3/css/lattice.base.49deefb5.css +0 -128
  103. package/static/v3/css/lattice.base.css +0 -128
  104. package/static/v3/css/lattice.components.cde18231.css +0 -472
  105. package/static/v3/css/lattice.components.css +0 -472
  106. package/static/v3/css/lattice.shell.29d36d85.css +0 -452
  107. package/static/v3/css/lattice.shell.css +0 -452
  108. package/static/v3/css/lattice.tokens.304cbc40.css +0 -135
  109. package/static/v3/css/lattice.tokens.css +0 -135
  110. package/static/v3/css/lattice.views.0a18b6c5.css +0 -360
  111. package/static/v3/css/lattice.views.css +0 -360
  112. package/static/v3/index.html +0 -68
  113. package/static/v3/js/app.c5c80c46.js +0 -26
  114. package/static/v3/js/app.js +0 -26
  115. package/static/v3/js/core/api.ba0fbf14.js +0 -625
  116. package/static/v3/js/core/api.js +0 -625
  117. package/static/v3/js/core/components.f25b3b93.js +0 -230
  118. package/static/v3/js/core/components.js +0 -230
  119. package/static/v3/js/core/dom.a2773eb0.js +0 -148
  120. package/static/v3/js/core/dom.js +0 -148
  121. package/static/v3/js/core/i18n.880e1fec.js +0 -575
  122. package/static/v3/js/core/i18n.js +0 -575
  123. package/static/v3/js/core/router.584570f2.js +0 -37
  124. package/static/v3/js/core/router.js +0 -37
  125. package/static/v3/js/core/routes.37522821.js +0 -101
  126. package/static/v3/js/core/routes.js +0 -101
  127. package/static/v3/js/core/shell.e3f6bbfa.js +0 -420
  128. package/static/v3/js/core/shell.js +0 -420
  129. package/static/v3/js/core/store.7b2aa044.js +0 -123
  130. package/static/v3/js/core/store.js +0 -123
  131. package/static/v3/js/views/account.eff40715.js +0 -143
  132. package/static/v3/js/views/account.js +0 -143
  133. package/static/v3/js/views/activity.0d271ef9.js +0 -67
  134. package/static/v3/js/views/activity.js +0 -67
  135. package/static/v3/js/views/admin-audit.660a1fb1.js +0 -185
  136. package/static/v3/js/views/admin-audit.js +0 -185
  137. package/static/v3/js/views/admin-permissions.a7ae5f09.js +0 -177
  138. package/static/v3/js/views/admin-permissions.js +0 -177
  139. package/static/v3/js/views/admin-policies.3658fd86.js +0 -102
  140. package/static/v3/js/views/admin-policies.js +0 -102
  141. package/static/v3/js/views/admin-private-vpc.7d342d36.js +0 -135
  142. package/static/v3/js/views/admin-private-vpc.js +0 -135
  143. package/static/v3/js/views/admin-security.07c66b72.js +0 -180
  144. package/static/v3/js/views/admin-security.js +0 -180
  145. package/static/v3/js/views/admin-users.f7ac7b43.js +0 -166
  146. package/static/v3/js/views/admin-users.js +0 -166
  147. package/static/v3/js/views/agents.17c5288d.js +0 -564
  148. package/static/v3/js/views/agents.js +0 -564
  149. package/static/v3/js/views/chat.e250e2cc.js +0 -624
  150. package/static/v3/js/views/chat.js +0 -624
  151. package/static/v3/js/views/files.adad14c1.js +0 -365
  152. package/static/v3/js/views/files.js +0 -365
  153. package/static/v3/js/views/graph-canvas.17c15d65.js +0 -509
  154. package/static/v3/js/views/graph-canvas.js +0 -509
  155. package/static/v3/js/views/home.24f8b8ae.js +0 -200
  156. package/static/v3/js/views/home.js +0 -200
  157. package/static/v3/js/views/hooks.37895880.js +0 -220
  158. package/static/v3/js/views/hooks.js +0 -220
  159. package/static/v3/js/views/hybrid-search.2fb63ed9.js +0 -194
  160. package/static/v3/js/views/hybrid-search.js +0 -194
  161. package/static/v3/js/views/knowledge-graph.4d09c537.js +0 -529
  162. package/static/v3/js/views/knowledge-graph.js +0 -529
  163. package/static/v3/js/views/marketplace.ab0583d4.js +0 -141
  164. package/static/v3/js/views/marketplace.js +0 -141
  165. package/static/v3/js/views/mcp.99b5c6a7.js +0 -114
  166. package/static/v3/js/views/mcp.js +0 -114
  167. package/static/v3/js/views/memory.4ebdf474.js +0 -147
  168. package/static/v3/js/views/memory.js +0 -147
  169. package/static/v3/js/views/models.a1ffa147.js +0 -256
  170. package/static/v3/js/views/models.js +0 -256
  171. package/static/v3/js/views/my-computer.d9d9ae1c.js +0 -463
  172. package/static/v3/js/views/my-computer.js +0 -463
  173. package/static/v3/js/views/network.52a4f181.js +0 -97
  174. package/static/v3/js/views/network.js +0 -97
  175. package/static/v3/js/views/pipeline.c522f1ce.js +0 -157
  176. package/static/v3/js/views/pipeline.js +0 -157
  177. package/static/v3/js/views/planning.4876fd77.js +0 -174
  178. package/static/v3/js/views/planning.js +0 -174
  179. package/static/v3/js/views/runs.b63b2afa.js +0 -144
  180. package/static/v3/js/views/runs.js +0 -144
  181. package/static/v3/js/views/settings.b7140634.js +0 -317
  182. package/static/v3/js/views/settings.js +0 -317
  183. package/static/v3/js/views/skills.c6c2f965.js +0 -109
  184. package/static/v3/js/views/skills.js +0 -109
  185. package/static/v3/js/views/snapshots.6f5db095.js +0 -135
  186. package/static/v3/js/views/snapshots.js +0 -135
  187. package/static/v3/js/views/tools.e4f11276.js +0 -108
  188. package/static/v3/js/views/tools.js +0 -108
  189. package/static/v3/js/views/workflows.7752225a.js +0 -213
  190. package/static/v3/js/views/workflows.js +0 -213
  191. package/static/v3/js/views/workspace-admin.c466029b.js +0 -156
  192. package/static/v3/js/views/workspace-admin.js +0 -156
@@ -0,0 +1,54 @@
1
+ import { create } from "zustand";
2
+
3
+ export type Theme = "dark" | "light";
4
+ export type WorkspaceMode = "basic" | "advanced" | "admin";
5
+
6
+ type AppState = {
7
+ theme: Theme;
8
+ mode: WorkspaceMode;
9
+ workspaceId: string | null;
10
+ apiBase: string | null;
11
+ setTheme: (theme: Theme) => void;
12
+ setMode: (mode: WorkspaceMode) => void;
13
+ setWorkspaceId: (workspaceId: string | null) => void;
14
+ setApiBase: (apiBase: string | null) => void;
15
+ };
16
+
17
+ function readTheme(): Theme {
18
+ try {
19
+ const saved = localStorage.getItem("lattice.theme");
20
+ if (saved === "light" || saved === "dark") return saved;
21
+ } catch {}
22
+ return "dark";
23
+ }
24
+
25
+ function readMode(): WorkspaceMode {
26
+ try {
27
+ const saved = localStorage.getItem("lattice.mode");
28
+ if (saved === "basic" || saved === "advanced" || saved === "admin") return saved;
29
+ } catch {}
30
+ return "basic";
31
+ }
32
+
33
+ export const useAppStore = create<AppState>((set) => ({
34
+ theme: readTheme(),
35
+ mode: readMode(),
36
+ workspaceId: null,
37
+ apiBase: null,
38
+ setTheme: (theme) => {
39
+ document.documentElement.dataset.theme = theme;
40
+ try { localStorage.setItem("lattice.theme", theme); } catch {}
41
+ set({ theme });
42
+ },
43
+ setMode: (mode) => {
44
+ try { localStorage.setItem("lattice.mode", mode); } catch {}
45
+ set({ mode });
46
+ },
47
+ setWorkspaceId: (workspaceId) => {
48
+ if (workspaceId) {
49
+ try { localStorage.setItem("lattice.workspace", workspaceId); } catch {}
50
+ }
51
+ set({ workspaceId });
52
+ },
53
+ setApiBase: (apiBase) => set({ apiBase }),
54
+ }));
@@ -0,0 +1,107 @@
1
+ @import "tailwindcss";
2
+ @import "reactflow/dist/style.css";
3
+
4
+ :root {
5
+ color-scheme: dark;
6
+ --background: 220 18% 8%;
7
+ --foreground: 210 30% 96%;
8
+ --card: 220 16% 11%;
9
+ --card-foreground: 210 30% 96%;
10
+ --muted: 218 13% 18%;
11
+ --muted-foreground: 215 14% 66%;
12
+ --primary: 176 68% 48%;
13
+ --primary-foreground: 212 26% 8%;
14
+ --secondary: 221 14% 20%;
15
+ --secondary-foreground: 210 30% 96%;
16
+ --destructive: 355 78% 58%;
17
+ --destructive-foreground: 0 0% 100%;
18
+ --border: 218 13% 23%;
19
+ --input: 218 13% 24%;
20
+ --ring: 176 68% 48%;
21
+ --brain: 176 68% 48%;
22
+ --ask: 216 78% 66%;
23
+ --capture: 142 58% 56%;
24
+ --act: 33 92% 58%;
25
+ --library: 262 68% 70%;
26
+ --system: 350 72% 68%;
27
+ }
28
+
29
+ :root[data-theme="light"] {
30
+ color-scheme: light;
31
+ --background: 210 30% 98%;
32
+ --foreground: 222 38% 9%;
33
+ --card: 0 0% 100%;
34
+ --card-foreground: 222 38% 9%;
35
+ --muted: 213 24% 92%;
36
+ --muted-foreground: 219 11% 38%;
37
+ --primary: 178 72% 32%;
38
+ --primary-foreground: 0 0% 100%;
39
+ --secondary: 211 24% 90%;
40
+ --secondary-foreground: 222 38% 9%;
41
+ --destructive: 355 72% 48%;
42
+ --destructive-foreground: 0 0% 100%;
43
+ --border: 214 20% 83%;
44
+ --input: 214 20% 78%;
45
+ --ring: 178 72% 32%;
46
+ }
47
+
48
+ @theme inline {
49
+ --color-background: hsl(var(--background));
50
+ --color-foreground: hsl(var(--foreground));
51
+ --color-card: hsl(var(--card));
52
+ --color-card-foreground: hsl(var(--card-foreground));
53
+ --color-muted: hsl(var(--muted));
54
+ --color-muted-foreground: hsl(var(--muted-foreground));
55
+ --color-primary: hsl(var(--primary));
56
+ --color-primary-foreground: hsl(var(--primary-foreground));
57
+ --color-secondary: hsl(var(--secondary));
58
+ --color-secondary-foreground: hsl(var(--secondary-foreground));
59
+ --color-destructive: hsl(var(--destructive));
60
+ --color-destructive-foreground: hsl(var(--destructive-foreground));
61
+ --color-border: hsl(var(--border));
62
+ --color-input: hsl(var(--input));
63
+ --color-ring: hsl(var(--ring));
64
+ }
65
+
66
+ * {
67
+ box-sizing: border-box;
68
+ }
69
+
70
+ body {
71
+ min-width: 320px;
72
+ margin: 0;
73
+ background: hsl(var(--background));
74
+ color: hsl(var(--foreground));
75
+ font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
76
+ }
77
+
78
+ button,
79
+ input,
80
+ textarea,
81
+ select {
82
+ font: inherit;
83
+ }
84
+
85
+ .brain-grid {
86
+ background-image:
87
+ linear-gradient(hsl(var(--border) / 0.34) 1px, transparent 1px),
88
+ linear-gradient(90deg, hsl(var(--border) / 0.34) 1px, transparent 1px);
89
+ background-size: 32px 32px;
90
+ }
91
+
92
+ .scrollbar-thin {
93
+ scrollbar-width: thin;
94
+ }
95
+
96
+ .react-flow__node {
97
+ border: 1px solid hsl(var(--border));
98
+ border-radius: 8px;
99
+ background: hsl(var(--card));
100
+ color: hsl(var(--foreground));
101
+ padding: 8px 10px;
102
+ font-size: 12px;
103
+ }
104
+
105
+ .react-flow__edge-path {
106
+ stroke: hsl(var(--primary));
107
+ }
package/kg_schema.py CHANGED
@@ -1,3 +1,3 @@
1
1
  """Compatibility shim for the v4 Knowledge Graph schema."""
2
2
 
3
- from latticeai.brain.schema import * # noqa: F403,F401
3
+ from lattice_brain.schema import * # noqa: F403,F401
@@ -1,10 +1,10 @@
1
- """Compatibility shim for the v4 brain store.
1
+ """Compatibility shim for the v4.2 lattice-brain store.
2
2
 
3
- The implementation now lives under :mod:`latticeai.brain`. Root imports are
3
+ The implementation now lives under :mod:`lattice_brain`. Root imports are
4
4
  kept for older integrations and tests.
5
5
  """
6
6
 
7
- from latticeai.brain._kg_common import ( # noqa: F401
7
+ from lattice_brain._kg_common import ( # noqa: F401
8
8
  EDGE_VERB,
9
9
  GRAPH_SCHEMA_VERSION,
10
10
  LOCAL_CODE_EXTENSIONS,
@@ -24,7 +24,7 @@ from latticeai.brain._kg_common import ( # noqa: F401
24
24
  _slug,
25
25
  set_llm_router,
26
26
  )
27
- from latticeai.brain.store import KnowledgeGraphStore
27
+ from lattice_brain.store import KnowledgeGraphStore
28
28
 
29
29
  __all__ = [
30
30
  "KnowledgeGraphStore",
@@ -0,0 +1,70 @@
1
+ """lattice-brain — independent Brain Core package for Lattice AI.
2
+
3
+ Heavy graph modules are lazy-loaded so storage and archive utilities remain
4
+ usable without importing the FastAPI application or creating runtime globals.
5
+ """
6
+
7
+ from .archive import BrainArchivePaths, EncryptedBrainArchive
8
+ from .core import BrainCore, BrainCoreConfig
9
+ from .storage import (
10
+ DockerPostgresPlan,
11
+ DockerPostgresWizard,
12
+ PostgresConfig,
13
+ PostgresEngine,
14
+ SQLiteEngine,
15
+ SQLiteToPostgresMigrator,
16
+ StorageCapabilities,
17
+ StorageEngine,
18
+ StorageUnavailable,
19
+ storage_from_env,
20
+ )
21
+
22
+ __version__ = "4.2.0"
23
+
24
+ __all__ = [
25
+ "AssembledContext",
26
+ "BrainArchivePaths",
27
+ "BrainCore",
28
+ "BrainCoreConfig",
29
+ "BrainMemory",
30
+ "ContextAssembler",
31
+ "ContextSection",
32
+ "ConversationStore",
33
+ "DockerPostgresPlan",
34
+ "DockerPostgresWizard",
35
+ "EncryptedBrainArchive",
36
+ "KnowledgeGraphStore",
37
+ "PostgresConfig",
38
+ "PostgresEngine",
39
+ "SQLiteEngine",
40
+ "SQLiteToPostgresMigrator",
41
+ "StorageCapabilities",
42
+ "StorageEngine",
43
+ "StorageUnavailable",
44
+ "storage_from_env",
45
+ "__version__",
46
+ ]
47
+
48
+
49
+ def __getattr__(name: str):
50
+ if name in {"AssembledContext", "ContextAssembler", "ContextSection"}:
51
+ from .context import AssembledContext, ContextAssembler, ContextSection
52
+
53
+ return {
54
+ "AssembledContext": AssembledContext,
55
+ "ContextAssembler": ContextAssembler,
56
+ "ContextSection": ContextSection,
57
+ }[name]
58
+ if name == "ConversationStore":
59
+ from .conversations import ConversationStore
60
+
61
+ return ConversationStore
62
+ if name == "BrainMemory":
63
+ from .memory import BrainMemory
64
+
65
+ return BrainMemory
66
+ if name == "KnowledgeGraphStore":
67
+ from .store import KnowledgeGraphStore
68
+
69
+ return KnowledgeGraphStore
70
+ raise AttributeError(name)
@@ -0,0 +1 @@
1
+ from latticeai.brain._kg_common import * # noqa: F401,F403
@@ -0,0 +1,133 @@
1
+ """Encrypted .latticebrain archive support."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import base64
6
+ import json
7
+ import os
8
+ import shutil
9
+ import tempfile
10
+ import zipfile
11
+ from dataclasses import dataclass
12
+ from datetime import datetime, timezone
13
+ from pathlib import Path
14
+ from typing import Dict, Optional
15
+
16
+ from cryptography.hazmat.primitives import hashes
17
+ from cryptography.hazmat.primitives.ciphers.aead import AESGCM
18
+ from cryptography.exceptions import InvalidTag
19
+ from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
20
+
21
+
22
+ ARCHIVE_FORMAT = "latticebrain.encrypted"
23
+ ARCHIVE_VERSION = 1
24
+ KDF_ITERATIONS = 390_000
25
+
26
+
27
+ def _now() -> str:
28
+ return datetime.now(timezone.utc).isoformat()
29
+
30
+
31
+ def _derive_key(passphrase: str, salt: bytes) -> bytes:
32
+ if not passphrase:
33
+ raise ValueError("A passphrase is required for encrypted .latticebrain archives.")
34
+ kdf = PBKDF2HMAC(
35
+ algorithm=hashes.SHA256(),
36
+ length=32,
37
+ salt=salt,
38
+ iterations=KDF_ITERATIONS,
39
+ )
40
+ return kdf.derive(passphrase.encode("utf-8"))
41
+
42
+
43
+ @dataclass(frozen=True)
44
+ class BrainArchivePaths:
45
+ db_path: Path
46
+ blob_dir: Optional[Path] = None
47
+
48
+
49
+ class EncryptedBrainArchive:
50
+ """Create and restore encrypted local Brain Core archives."""
51
+
52
+ def __init__(self, paths: BrainArchivePaths) -> None:
53
+ self.paths = paths
54
+
55
+ def create(self, destination: Path, *, passphrase: str) -> Dict[str, object]:
56
+ dest = Path(destination)
57
+ if dest.suffix != ".latticebrain":
58
+ dest = dest.with_suffix(".latticebrain")
59
+ if not self.paths.db_path.exists():
60
+ raise FileNotFoundError(f"Brain database not found: {self.paths.db_path}")
61
+ dest.parent.mkdir(parents=True, exist_ok=True)
62
+ with tempfile.TemporaryDirectory() as tmp_s:
63
+ tmp = Path(tmp_s)
64
+ payload = tmp / "payload.zip"
65
+ with zipfile.ZipFile(payload, "w", zipfile.ZIP_DEFLATED) as zf:
66
+ zf.write(self.paths.db_path, "knowledge_graph.sqlite")
67
+ if self.paths.blob_dir and self.paths.blob_dir.exists():
68
+ for file in self.paths.blob_dir.rglob("*"):
69
+ if file.is_file():
70
+ zf.write(file, f"blobs/{file.relative_to(self.paths.blob_dir)}")
71
+ salt = os.urandom(16)
72
+ nonce = os.urandom(12)
73
+ key = _derive_key(passphrase, salt)
74
+ ciphertext = AESGCM(key).encrypt(nonce, payload.read_bytes(), None)
75
+ envelope = {
76
+ "format": ARCHIVE_FORMAT,
77
+ "format_version": ARCHIVE_VERSION,
78
+ "created_at": _now(),
79
+ "kdf": {
80
+ "name": "PBKDF2HMAC-SHA256",
81
+ "iterations": KDF_ITERATIONS,
82
+ "salt": base64.b64encode(salt).decode("ascii"),
83
+ },
84
+ "cipher": {
85
+ "name": "AES-256-GCM",
86
+ "nonce": base64.b64encode(nonce).decode("ascii"),
87
+ },
88
+ "payload": base64.b64encode(ciphertext).decode("ascii"),
89
+ }
90
+ dest.write_text(json.dumps(envelope, indent=2), encoding="utf-8")
91
+ return {"path": str(dest), "bytes": dest.stat().st_size, "encrypted": True}
92
+
93
+ def restore(self, source: Path, *, passphrase: str, target: BrainArchivePaths) -> Dict[str, object]:
94
+ src = Path(source)
95
+ if not src.exists():
96
+ raise FileNotFoundError(f"Brain archive not found: {src}")
97
+ envelope = json.loads(src.read_text(encoding="utf-8"))
98
+ if envelope.get("format") != ARCHIVE_FORMAT:
99
+ raise ValueError("Not a .latticebrain encrypted archive.")
100
+ salt = base64.b64decode(envelope["kdf"]["salt"])
101
+ nonce = base64.b64decode(envelope["cipher"]["nonce"])
102
+ ciphertext = base64.b64decode(envelope["payload"])
103
+ key = _derive_key(passphrase, salt)
104
+ try:
105
+ plaintext = AESGCM(key).decrypt(nonce, ciphertext, None)
106
+ except InvalidTag as exc:
107
+ raise ValueError("Archive decryption failed; passphrase or archive data is invalid.") from exc
108
+ with tempfile.TemporaryDirectory() as tmp_s:
109
+ tmp = Path(tmp_s)
110
+ payload = tmp / "payload.zip"
111
+ payload.write_bytes(plaintext)
112
+ with zipfile.ZipFile(payload) as zf:
113
+ zf.extractall(tmp / "out")
114
+ db_src = tmp / "out" / "knowledge_graph.sqlite"
115
+ if not db_src.exists():
116
+ raise ValueError("Archive payload is missing knowledge_graph.sqlite.")
117
+ target.db_path.parent.mkdir(parents=True, exist_ok=True)
118
+ for sibling in (target.db_path, Path(str(target.db_path) + "-wal"), Path(str(target.db_path) + "-shm")):
119
+ if sibling.exists():
120
+ sibling.unlink()
121
+ shutil.copyfile(db_src, target.db_path)
122
+ blobs_src = tmp / "out" / "blobs"
123
+ if target.blob_dir:
124
+ if target.blob_dir.exists():
125
+ shutil.rmtree(target.blob_dir)
126
+ if blobs_src.exists():
127
+ shutil.copytree(blobs_src, target.blob_dir)
128
+ else:
129
+ target.blob_dir.mkdir(parents=True, exist_ok=True)
130
+ return {"restored": True, "path": str(target.db_path), "encrypted": True}
131
+
132
+
133
+ __all__ = ["BrainArchivePaths", "EncryptedBrainArchive"]
@@ -0,0 +1,3 @@
1
+ from latticeai.brain.context import AssembledContext, ContextAssembler, ContextSection, approx_tokens
2
+
3
+ __all__ = ["AssembledContext", "ContextAssembler", "ContextSection", "approx_tokens"]
@@ -0,0 +1,3 @@
1
+ from latticeai.brain.conversations import ConversationStore
2
+
3
+ __all__ = ["ConversationStore"]
@@ -0,0 +1,82 @@
1
+ """Independent Brain Core package facade."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from pathlib import Path
7
+ from typing import Any, Optional
8
+
9
+ from .archive import BrainArchivePaths, EncryptedBrainArchive
10
+ from .storage import SQLiteEngine, StorageEngine, StorageUnavailable
11
+
12
+
13
+ @dataclass(frozen=True)
14
+ class BrainCoreConfig:
15
+ data_dir: Path
16
+ blob_dir: Optional[Path] = None
17
+ storage_engine: Optional[StorageEngine] = None
18
+
19
+
20
+ class BrainCore:
21
+ """Stable application boundary for the local Digital Brain.
22
+
23
+ FastAPI, CLI, tests, and future tools should depend on this package-level
24
+ facade instead of constructing scattered storage objects directly.
25
+ """
26
+
27
+ def __init__(self, config: BrainCoreConfig, *, embedder: Any = None) -> None:
28
+ self.config = config
29
+ self.data_dir = Path(config.data_dir)
30
+ self.db_path = self.data_dir / "knowledge_graph.sqlite"
31
+ self.blob_dir = Path(config.blob_dir) if config.blob_dir else self.data_dir / "knowledge_graph_blobs"
32
+ self.storage_engine = config.storage_engine or SQLiteEngine(self.db_path)
33
+ caps = self.storage_engine.capabilities()
34
+ if not caps.available:
35
+ raise StorageUnavailable(caps.reason or f"{caps.engine} storage is unavailable")
36
+ if caps.engine != "sqlite":
37
+ raise StorageUnavailable(
38
+ "The active FastAPI Brain Core runtime currently requires SQLiteEngine. "
39
+ "Use PostgresEngine through the explicit migration/scale tooling; no SQLite fallback was attempted."
40
+ )
41
+
42
+ from .conversations import ConversationStore
43
+ from .store import KnowledgeGraphStore
44
+
45
+ self.knowledge = KnowledgeGraphStore(
46
+ self.db_path,
47
+ self.blob_dir,
48
+ embedder=embedder,
49
+ storage_engine=self.storage_engine,
50
+ )
51
+ self.conversations = ConversationStore(self.db_path)
52
+ self.archive = EncryptedBrainArchive(
53
+ BrainArchivePaths(db_path=self.db_path, blob_dir=self.blob_dir)
54
+ )
55
+
56
+ @classmethod
57
+ def from_paths(
58
+ cls,
59
+ data_dir: Path,
60
+ *,
61
+ blob_dir: Optional[Path] = None,
62
+ embedder: Any = None,
63
+ storage_engine: Optional[StorageEngine] = None,
64
+ ) -> "BrainCore":
65
+ return cls(
66
+ BrainCoreConfig(
67
+ data_dir=Path(data_dir),
68
+ blob_dir=blob_dir,
69
+ storage_engine=storage_engine,
70
+ ),
71
+ embedder=embedder,
72
+ )
73
+
74
+ def status(self) -> dict:
75
+ return {
76
+ "storage": self.storage_engine.capabilities().as_dict(),
77
+ "db_path": str(self.db_path),
78
+ "blob_dir": str(self.blob_dir),
79
+ }
80
+
81
+
82
+ __all__ = ["BrainCore", "BrainCoreConfig"]
@@ -0,0 +1 @@
1
+ from latticeai.brain.discovery import * # noqa: F401,F403
@@ -0,0 +1 @@
1
+ from latticeai.brain.documents import * # noqa: F401,F403
@@ -0,0 +1,82 @@
1
+ """Local deterministic embeddings used by the standalone Brain Core package."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import math
7
+ import os
8
+ import re
9
+ import struct
10
+ from dataclasses import dataclass
11
+ from typing import Iterable, List
12
+
13
+
14
+ DEFAULT_EMBEDDING_DIM = int(os.getenv("LATTICEAI_VECTOR_DIM", "384"))
15
+ EMBEDDING_MODEL_ID = f"lattice-local-hash-v1:{DEFAULT_EMBEDDING_DIM}"
16
+
17
+
18
+ def _tokenize(text: str) -> List[str]:
19
+ raw = str(text or "").lower()
20
+ tokens = re.findall(r"[a-z0-9][a-z0-9_.:/+-]{1,}|[가-힣]{2,}", raw)
21
+ features: List[str] = []
22
+ for token in tokens:
23
+ features.append(f"tok:{token}")
24
+ if len(token) >= 5 and re.search(r"[a-z]", token):
25
+ for i in range(0, len(token) - 2):
26
+ features.append(f"tri:{token[i:i+3]}")
27
+ if re.search(r"[가-힣]", token) and len(token) >= 3:
28
+ for i in range(0, len(token) - 1):
29
+ features.append(f"ko:{token[i:i+2]}")
30
+ return features
31
+
32
+
33
+ def _hash_to_index(feature: str, dim: int) -> tuple[int, float]:
34
+ digest = hashlib.blake2b(feature.encode("utf-8"), digest_size=8).digest()
35
+ value = int.from_bytes(digest, "big", signed=False)
36
+ sign = 1.0 if (value & 1) == 0 else -1.0
37
+ return value % dim, sign
38
+
39
+
40
+ @dataclass(frozen=True)
41
+ class LocalEmbeddingModel:
42
+ """Deterministic local embedder.
43
+
44
+ This is intentionally not presented as a production semantic model. It is
45
+ a real, offline cosine signal for local-first operation and tests; setup
46
+ wizard provisioning can replace it with a user-consented model/provider.
47
+ """
48
+
49
+ dim: int = DEFAULT_EMBEDDING_DIM
50
+ model_id: str = EMBEDDING_MODEL_ID
51
+
52
+ def embed(self, text: str) -> List[float]:
53
+ vector = [0.0] * self.dim
54
+ features = _tokenize(text)
55
+ if not features:
56
+ return vector
57
+ for feature in features:
58
+ index, sign = _hash_to_index(feature, self.dim)
59
+ vector[index] += sign
60
+ norm = math.sqrt(sum(value * value for value in vector))
61
+ if norm <= 0:
62
+ return vector
63
+ return [value / norm for value in vector]
64
+
65
+ def similarity(self, left: Iterable[float], right: Iterable[float]) -> float:
66
+ return float(sum(a * b for a, b in zip(left, right)))
67
+
68
+ def encode(self, vector: Iterable[float]) -> bytes:
69
+ values = list(vector)
70
+ return struct.pack(f"<{len(values)}f", *values)
71
+
72
+ def decode(self, payload: bytes, dim: int | None = None) -> List[float]:
73
+ if not payload:
74
+ return []
75
+ count = int(dim or self.dim)
76
+ expected = count * 4
77
+ if len(payload) != expected:
78
+ count = len(payload) // 4
79
+ return list(struct.unpack(f"<{count}f", payload[: count * 4]))
80
+
81
+
82
+ __all__ = ["DEFAULT_EMBEDDING_DIM", "EMBEDDING_MODEL_ID", "LocalEmbeddingModel"]
@@ -0,0 +1,13 @@
1
+ from latticeai.brain.identity import (
2
+ DeviceIdentity,
3
+ fingerprint_of,
4
+ verify_manifest,
5
+ verify_signature,
6
+ )
7
+
8
+ __all__ = [
9
+ "DeviceIdentity",
10
+ "fingerprint_of",
11
+ "verify_manifest",
12
+ "verify_signature",
13
+ ]
@@ -0,0 +1 @@
1
+ from latticeai.brain.ingest import * # noqa: F401,F403
@@ -0,0 +1,3 @@
1
+ from latticeai.brain.memory import BrainMemory
2
+
3
+ __all__ = ["BrainMemory"]
@@ -0,0 +1 @@
1
+ from latticeai.brain.network import * # noqa: F401,F403
@@ -0,0 +1 @@
1
+ from latticeai.brain.projection import * # noqa: F401,F403
@@ -0,0 +1 @@
1
+ from latticeai.brain.provenance import * # noqa: F401,F403
@@ -0,0 +1 @@
1
+ from latticeai.brain.retrieval import * # noqa: F401,F403
@@ -0,0 +1 @@
1
+ from latticeai.brain.schema import * # noqa: F401,F403
@@ -0,0 +1,22 @@
1
+ """Pluggable storage layer for lattice-brain."""
2
+
3
+ from .base import StorageCapabilities, StorageEngine, StorageUnavailable
4
+ from .docker import DockerPostgresPlan, DockerPostgresWizard
5
+ from .factory import storage_from_env
6
+ from .migration import SQLiteToPostgresMigrator, TablePlan
7
+ from .postgres import PostgresConfig, PostgresEngine
8
+ from .sqlite import SQLiteEngine
9
+
10
+ __all__ = [
11
+ "DockerPostgresPlan",
12
+ "DockerPostgresWizard",
13
+ "PostgresConfig",
14
+ "PostgresEngine",
15
+ "SQLiteEngine",
16
+ "SQLiteToPostgresMigrator",
17
+ "StorageCapabilities",
18
+ "StorageEngine",
19
+ "StorageUnavailable",
20
+ "TablePlan",
21
+ "storage_from_env",
22
+ ]