ltcai 3.6.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/README.md +11 -7
  2. package/docs/V4_BRAIN_ARCHITECTURE.md +322 -0
  3. package/docs/V4_DIGITAL_BRAIN_RECOVERY.md +509 -0
  4. package/docs/V4_IMPLEMENTATION_PLAN.md +470 -0
  5. package/docs/kg-schema.md +47 -53
  6. package/kg_schema.py +93 -10
  7. package/knowledge_graph.py +362 -33
  8. package/knowledge_graph_api.py +11 -127
  9. package/latticeai/__init__.py +1 -1
  10. package/latticeai/api/admin.py +1 -1
  11. package/latticeai/api/agents.py +7 -1
  12. package/latticeai/api/auth.py +27 -4
  13. package/latticeai/api/chat.py +112 -76
  14. package/latticeai/api/health.py +1 -1
  15. package/latticeai/api/hooks.py +1 -1
  16. package/latticeai/api/knowledge_graph.py +146 -0
  17. package/latticeai/api/local_files.py +1 -1
  18. package/latticeai/api/mcp.py +23 -11
  19. package/latticeai/api/memory.py +1 -1
  20. package/latticeai/api/models.py +1 -1
  21. package/latticeai/api/network.py +81 -0
  22. package/latticeai/api/realtime.py +1 -1
  23. package/latticeai/api/search.py +26 -2
  24. package/latticeai/api/security_dashboard.py +2 -3
  25. package/latticeai/api/setup.py +2 -2
  26. package/latticeai/api/static_routes.py +2 -4
  27. package/latticeai/api/tools.py +3 -0
  28. package/latticeai/api/workflow_designer.py +46 -0
  29. package/latticeai/api/workspace.py +71 -49
  30. package/latticeai/app_factory.py +1710 -0
  31. package/latticeai/brain/__init__.py +18 -0
  32. package/latticeai/brain/context.py +213 -0
  33. package/latticeai/brain/conversations.py +236 -0
  34. package/latticeai/brain/identity.py +175 -0
  35. package/latticeai/brain/memory.py +102 -0
  36. package/latticeai/brain/network.py +205 -0
  37. package/latticeai/core/agent.py +31 -7
  38. package/latticeai/core/audit.py +0 -7
  39. package/latticeai/core/config.py +1 -1
  40. package/latticeai/core/context_builder.py +1 -2
  41. package/latticeai/core/enterprise.py +1 -1
  42. package/latticeai/core/graph_curator.py +2 -2
  43. package/latticeai/core/marketplace.py +1 -1
  44. package/latticeai/core/mcp_registry.py +791 -0
  45. package/latticeai/core/model_compat.py +1 -1
  46. package/latticeai/core/model_resolution.py +0 -1
  47. package/latticeai/core/multi_agent.py +238 -4
  48. package/latticeai/core/security.py +1 -1
  49. package/latticeai/core/sessions.py +37 -7
  50. package/latticeai/core/workflow_engine.py +114 -2
  51. package/latticeai/core/workspace_os.py +58 -10
  52. package/latticeai/models/__init__.py +7 -0
  53. package/latticeai/models/router.py +779 -0
  54. package/latticeai/server_app.py +29 -1536
  55. package/latticeai/services/agent_runtime.py +1 -0
  56. package/latticeai/services/app_context.py +75 -14
  57. package/latticeai/services/ingestion.py +47 -0
  58. package/latticeai/services/kg_portability.py +33 -3
  59. package/latticeai/services/memory_service.py +39 -11
  60. package/latticeai/services/model_runtime.py +2 -5
  61. package/latticeai/services/platform_runtime.py +100 -23
  62. package/latticeai/services/search_service.py +17 -8
  63. package/latticeai/services/tool_dispatch.py +12 -2
  64. package/latticeai/services/triggers.py +241 -0
  65. package/latticeai/services/upload_service.py +37 -12
  66. package/latticeai/services/workspace_service.py +31 -0
  67. package/llm_router.py +29 -772
  68. package/ltcai_cli.py +1 -2
  69. package/mcp_registry.py +25 -788
  70. package/p_reinforce.py +124 -14
  71. package/package.json +9 -7
  72. package/scripts/bump_version.py +99 -0
  73. package/scripts/generate_diagrams.py +0 -1
  74. package/scripts/lint_v3.mjs +82 -18
  75. package/scripts/validate_release_artifacts.py +0 -1
  76. package/scripts/wheel_smoke.py +142 -0
  77. package/server.py +11 -7
  78. package/setup_wizard.py +1142 -0
  79. package/static/account.html +2 -4
  80. package/static/admin.html +3 -5
  81. package/static/chat.html +3 -6
  82. package/static/graph.html +2 -4
  83. package/static/sw.js +81 -52
  84. package/static/v3/asset-manifest.json +20 -19
  85. package/static/v3/css/{lattice.base.e4cdd05d.css → lattice.base.49deefb5.css} +1 -1
  86. package/static/v3/css/lattice.base.css +1 -1
  87. package/static/v3/css/{lattice.components.9b49d614.css → lattice.components.cde18231.css} +1 -1
  88. package/static/v3/css/lattice.components.css +1 -1
  89. package/static/v3/css/{lattice.shell.8fcc9d33.css → lattice.shell.29d36d85.css} +1 -1
  90. package/static/v3/css/lattice.shell.css +1 -1
  91. package/static/v3/css/{lattice.tokens.e7018963.css → lattice.tokens.304cbc40.css} +3 -0
  92. package/static/v3/css/lattice.tokens.css +3 -0
  93. package/static/v3/css/{lattice.views.22f69117.css → lattice.views.0a18b6c5.css} +2 -2
  94. package/static/v3/css/lattice.views.css +2 -2
  95. package/static/v3/index.html +3 -4
  96. package/static/v3/js/{app.c541f955.js → app.356e6452.js} +1 -1
  97. package/static/v3/js/core/{api.33d6320e.js → api.7a308b89.js} +1 -1
  98. package/static/v3/js/core/{routes.2ce3815a.js → routes.7222343d.js} +22 -22
  99. package/static/v3/js/core/routes.js +22 -22
  100. package/static/v3/js/core/{shell.8c163e0e.js → shell.a1657f20.js} +4 -4
  101. package/static/v3/js/core/shell.js +1 -1
  102. package/static/v3/js/core/{store.34ebd5e6.js → store.204a08b2.js} +1 -1
  103. package/static/v3/js/core/store.js +1 -1
  104. package/static/v3/js/views/graph-canvas.17c15d65.js +509 -0
  105. package/static/v3/js/views/graph-canvas.js +509 -0
  106. package/static/v3/js/views/{hybrid-search.b22b97e0.js → hybrid-search.2fb63ed9.js} +1 -2
  107. package/static/v3/js/views/hybrid-search.js +1 -2
  108. package/static/v3/js/views/{knowledge-graph.a96040a5.js → knowledge-graph.5e40cbeb.js} +33 -37
  109. package/static/v3/js/views/knowledge-graph.js +33 -37
  110. package/static/vendor/chart.umd.min.js +20 -0
  111. package/static/vendor/fonts/inter-latin-300-normal.woff2 +0 -0
  112. package/static/vendor/fonts/inter-latin-400-normal.woff2 +0 -0
  113. package/static/vendor/fonts/inter-latin-500-normal.woff2 +0 -0
  114. package/static/vendor/fonts/inter-latin-600-normal.woff2 +0 -0
  115. package/static/vendor/fonts/inter-latin-700-normal.woff2 +0 -0
  116. package/static/vendor/fonts/inter-latin-800-normal.woff2 +0 -0
  117. package/static/vendor/fonts/inter.css +44 -0
  118. package/static/vendor/icons/tabler-icons.min.css +4 -0
  119. package/static/vendor/icons/tabler-icons.woff2 +0 -0
  120. package/static/vendor/marked.min.js +69 -0
  121. package/static/workspace.html +2 -2
  122. package/telegram_bot.py +1 -2
  123. package/tools/commands.py +4 -2
  124. package/tools/computer.py +1 -1
  125. package/tools/documents.py +1 -3
  126. package/tools/filesystem.py +0 -4
  127. package/tools/knowledge.py +1 -3
  128. package/tools/network.py +1 -3
  129. package/codex_telegram_bot.py +0 -195
  130. package/docs/assets/v3.4.0/agent-run.png +0 -0
  131. package/docs/assets/v3.4.0/agents.png +0 -0
  132. package/docs/assets/v3.4.0/before/chat-before.png +0 -0
  133. package/docs/assets/v3.4.0/before/files-before.png +0 -0
  134. package/docs/assets/v3.4.0/chat.png +0 -0
  135. package/docs/assets/v3.4.0/connect-folder.png +0 -0
  136. package/docs/assets/v3.4.0/files.png +0 -0
  137. package/docs/assets/v3.4.0/home.png +0 -0
  138. package/docs/assets/v3.4.0/hooks-dispatch.png +0 -0
  139. package/docs/assets/v3.4.0/knowledge-graph.png +0 -0
  140. package/docs/assets/v3.4.0/local-agent.png +0 -0
  141. package/docs/assets/v3.4.0/memory.png +0 -0
  142. package/docs/assets/v3.4.0/settings.png +0 -0
  143. package/docs/assets/v3.4.0/vision-input.png +0 -0
  144. package/docs/assets/v3.4.0/workflows.png +0 -0
  145. package/docs/assets/v3.4.1/e2e_runtime_log.txt +0 -42
  146. package/docs/assets/v3.4.1/hooks-dispatch.png +0 -0
  147. package/docs/assets/v3.4.1/local-agent.png +0 -0
  148. package/docs/images/admin-dashboard.png +0 -0
  149. package/docs/images/architecture.png +0 -0
  150. package/docs/images/enterprise.png +0 -0
  151. package/docs/images/graph.png +0 -0
  152. package/docs/images/hero.gif +0 -0
  153. package/docs/images/knowledge-graph.png +0 -0
  154. package/docs/images/lattice-ai-demo.gif +0 -0
  155. package/docs/images/lattice-ai-hero.png +0 -0
  156. package/docs/images/logo.svg +0 -33
  157. package/docs/images/mobile-responsive.png +0 -0
  158. package/docs/images/model-recommendation.png +0 -0
  159. package/docs/images/onboarding.png +0 -0
  160. package/docs/images/organization.png +0 -0
  161. package/docs/images/pipeline.png +0 -0
  162. package/docs/images/screenshot-admin.png +0 -0
  163. package/docs/images/screenshot-chat.png +0 -0
  164. package/docs/images/screenshot-graph.png +0 -0
  165. package/docs/images/skills.png +0 -0
  166. package/docs/images/workspace-dark.png +0 -0
  167. package/docs/images/workspace-light.png +0 -0
  168. package/docs/images/workspace.png +0 -0
  169. package/requirements.txt +0 -16
package/kg_schema.py CHANGED
@@ -116,8 +116,14 @@ class NodeType(str, Enum):
116
116
  매핑이 없는(동적 이벤트 등) 타입은 ``CONCEPT`` 로 폴백하지만, 호출부는
117
117
  원본 문자열을 ``legacy_type`` 칼럼에 별도 보존하므로 정보 손실은 없다.
118
118
  """
119
- m = (label or "").strip().lower()
120
- return _LEGACY_NODE_MAP.get(m, cls.CONCEPT)
119
+ m = (label or "").strip()
120
+ # Canonical values round-trip exactly (v4 native writes use them);
121
+ # without this, CODE_FILE/AI_RESPONSE etc. would degrade to CONCEPT.
122
+ try:
123
+ return cls(m.upper())
124
+ except ValueError:
125
+ pass
126
+ return _LEGACY_NODE_MAP.get(m.lower(), cls.CONCEPT)
121
127
 
122
128
 
123
129
  class EdgeType(str, Enum):
@@ -170,8 +176,13 @@ class EdgeType(str, Enum):
170
176
  매핑이 없는 동적 타입은 ``MENTIONS`` 로 폴백하지만, 호출부는 원본 문자열을
171
177
  ``edges_v2.legacy_type`` 에 보존하므로 정보 손실은 없다.
172
178
  """
173
- m = (label or "").strip().lower()
174
- return _LEGACY_EDGE_MAP.get(m, cls.MENTIONS)
179
+ m = (label or "").strip()
180
+ # Canonical values round-trip exactly (v4 native writes use them).
181
+ try:
182
+ return cls(m.upper())
183
+ except ValueError:
184
+ pass
185
+ return _LEGACY_EDGE_MAP.get(m.lower(), cls.MENTIONS)
175
186
 
176
187
 
177
188
  # legacy(자유 문자열 / 한글 동사) → enum 매핑 표.
@@ -318,7 +329,13 @@ CREATE TABLE IF NOT EXISTS nodes_v2 (
318
329
  attrs TEXT NOT NULL DEFAULT '{}',
319
330
  embedding BLOB,
320
331
  owner_id TEXT,
332
+ -- NULL workspace_id = legacy-global (pre-scoping rows, readable machine-wide).
333
+ workspace_id TEXT,
334
+ -- 'legacy' marks rows that predate scoping — the 'private' default must not
335
+ -- silently privatize previously machine-shared data (design-review ruling).
321
336
  visibility TEXT NOT NULL DEFAULT 'private',
337
+ -- Revision chain: a node replaced by a newer one points at its successor.
338
+ superseded_by TEXT,
322
339
  created_at TEXT NOT NULL,
323
340
  updated_at TEXT NOT NULL,
324
341
  style TEXT,
@@ -339,14 +356,33 @@ CREATE TABLE IF NOT EXISTS edges_v2 (
339
356
  metadata TEXT NOT NULL DEFAULT '{}',
340
357
  created_by TEXT NOT NULL DEFAULT 'user',
341
358
  created_at TEXT NOT NULL,
342
- -- Edge identity follows the *raw* legacy type, not the normalized type:
343
- -- two distinct legacy types between the same pair (e.g. "mentions" and
344
- -- "관련됨") must stay distinct edges even though both normalize to MENTIONS.
345
- UNIQUE(source, target, legacy_type),
359
+ -- Edge identity (v4): the normalized type AND the raw legacy type.
360
+ -- Migrated rows keep their legacy_type discriminator, so two distinct
361
+ -- legacy strings between one pair (e.g. "mentions" / "관련됨") stay
362
+ -- distinct even though both normalize to MENTIONS. Native canonical
363
+ -- writes carry legacy_type='' so their identity is effectively
364
+ -- (source, target, type) — two canonical types between the same pair
365
+ -- (e.g. MENTIONS + CONTAINS) never collide. The pre-v4
366
+ -- UNIQUE(source, target, legacy_type) would have silently merged them.
367
+ UNIQUE(source, target, type, legacy_type),
346
368
  FOREIGN KEY(source) REFERENCES nodes_v2(id) ON DELETE CASCADE,
347
369
  FOREIGN KEY(target) REFERENCES nodes_v2(id) ON DELETE CASCADE
348
370
  );
349
371
 
372
+ -- Temporal dimension (v4): every repeated observation of a relationship is
373
+ -- recorded — edges_v2's UNIQUE identity + weight=max would otherwise erase
374
+ -- when something was learned, how often, and whether it still holds.
375
+ CREATE TABLE IF NOT EXISTS edge_occurrences (
376
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
377
+ edge_id TEXT NOT NULL,
378
+ observed_at TEXT NOT NULL,
379
+ weight REAL NOT NULL DEFAULT 1.0,
380
+ source TEXT,
381
+ FOREIGN KEY(edge_id) REFERENCES edges_v2(id) ON DELETE CASCADE
382
+ );
383
+ CREATE INDEX IF NOT EXISTS idx_edge_occurrences_edge ON edge_occurrences(edge_id);
384
+ CREATE INDEX IF NOT EXISTS idx_edge_occurrences_time ON edge_occurrences(observed_at);
385
+
350
386
  CREATE INDEX IF NOT EXISTS idx_nodes_v2_type ON nodes_v2(type);
351
387
  CREATE INDEX IF NOT EXISTS idx_nodes_v2_legacy ON nodes_v2(legacy_type);
352
388
  CREATE INDEX IF NOT EXISTS idx_nodes_v2_owner ON nodes_v2(owner_id);
@@ -398,8 +434,16 @@ class KGStoreV2:
398
434
  "edges_v2": {"id", "source", "target", "type", "legacy_type", "weight",
399
435
  "confidence", "evidence", "metadata", "created_by", "created_at"},
400
436
  "nodes_v2": {"id", "type", "legacy_type", "label", "summary", "attrs",
401
- "embedding", "owner_id", "visibility", "created_at",
402
- "updated_at", "style", "tone", "importance_score", "last_used"},
437
+ "embedding", "owner_id", "workspace_id", "visibility",
438
+ "superseded_by", "created_at", "updated_at", "style",
439
+ "tone", "importance_score", "last_used"},
440
+ }
441
+
442
+ # Columns added after a table's first release that can be healed in place
443
+ # with ALTER TABLE ADD COLUMN (nullable / defaulted only).
444
+ _V2_ADDABLE_COLUMNS = {
445
+ "nodes_v2": {"workspace_id": "TEXT", "superseded_by": "TEXT"},
446
+ "edges_v2": {},
403
447
  }
404
448
 
405
449
  def _drop_stale_empty_v2_tables(self, conn: sqlite3.Connection) -> None:
@@ -420,6 +464,13 @@ class KGStoreV2:
420
464
  continue
421
465
  cols = {r[1] for r in conn.execute(f"PRAGMA table_info({table})").fetchall()}
422
466
  missing = self._V2_EXPECTED_COLUMNS[table] - cols
467
+ if not missing:
468
+ continue
469
+ # Additive columns heal in place without touching data.
470
+ addable = self._V2_ADDABLE_COLUMNS.get(table, {})
471
+ for col in sorted(missing & set(addable)):
472
+ conn.execute(f"ALTER TABLE {table} ADD COLUMN {col} {addable[col]}")
473
+ missing -= set(addable)
423
474
  if not missing:
424
475
  continue
425
476
  count = conn.execute(f"SELECT COUNT(*) FROM {table}").fetchone()[0]
@@ -446,8 +497,40 @@ class KGStoreV2:
446
497
  with self._conn() as own:
447
498
  self._init_schema_on(own)
448
499
 
500
+ def _rebuild_edges_identity(self, conn: sqlite3.Connection) -> None:
501
+ """Migrate edges_v2 from the pre-v4 UNIQUE(source, target, legacy_type)
502
+ identity to UNIQUE(source, target, type, legacy_type).
503
+
504
+ SQLite cannot alter constraints, so this is a create→copy→swap inside
505
+ the caller's transaction. Data-preserving: every existing row keeps its
506
+ legacy_type discriminator. Re-entrant: keyed on the actual constraint
507
+ in sqlite_master, not a one-time stamp.
508
+ """
509
+ row = conn.execute(
510
+ "SELECT sql FROM sqlite_master WHERE type='table' AND name='edges_v2'"
511
+ ).fetchone()
512
+ if not row or "UNIQUE(source, target, type, legacy_type)" in (row["sql"] or ""):
513
+ return
514
+ conn.execute("ALTER TABLE edges_v2 RENAME TO edges_v2_old")
515
+ # Recreate from the canonical DDL (edges_v2 portion of SCHEMA_SQL).
516
+ start = SCHEMA_SQL.index("CREATE TABLE IF NOT EXISTS edges_v2")
517
+ end = SCHEMA_SQL.index(");", start) + 2
518
+ conn.execute(SCHEMA_SQL[start:end].rstrip(";"))
519
+ conn.execute(
520
+ """
521
+ INSERT INTO edges_v2 (id, source, target, type, legacy_type, weight,
522
+ confidence, evidence, metadata, created_by, created_at)
523
+ SELECT id, source, target, type, legacy_type, weight,
524
+ confidence, evidence, metadata, created_by, created_at
525
+ FROM edges_v2_old
526
+ """
527
+ )
528
+ conn.execute("DROP TABLE edges_v2_old")
529
+ logging.info("kg_schema: rebuilt edges_v2 with (source, target, type, legacy_type) identity")
530
+
449
531
  def _init_schema_on(self, conn: sqlite3.Connection) -> None:
450
532
  self._drop_stale_empty_v2_tables(conn)
533
+ self._rebuild_edges_identity(conn)
451
534
  _exec_script(conn, SCHEMA_SQL)
452
535
  conn.execute(
453
536
  "INSERT OR REPLACE INTO kg_meta(key, value) VALUES (?, ?)",