ltcai 7.5.0 → 7.7.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 (61) hide show
  1. package/README.md +48 -44
  2. package/docs/CHANGELOG.md +45 -0
  3. package/frontend/src/App.tsx +1 -1
  4. package/frontend/src/api/client.ts +9 -2
  5. package/frontend/src/components/LivingBrain.tsx +9 -1
  6. package/frontend/src/components/ProductFlow.tsx +64 -1
  7. package/frontend/src/components/onboarding/RecommendationScreen.tsx +36 -28
  8. package/frontend/src/components/onboarding/recommendationModel.ts +1 -1
  9. package/frontend/src/features/brain/BrainConversation.tsx +123 -2
  10. package/frontend/src/features/brain/BrainHome.tsx +34 -0
  11. package/frontend/src/features/review/ReviewInbox.tsx +2 -2
  12. package/frontend/src/features/review/reviewHelpers.ts +11 -11
  13. package/frontend/src/i18n.ts +148 -16
  14. package/frontend/src/pages/Act.tsx +28 -14
  15. package/frontend/src/routes.ts +6 -6
  16. package/frontend/src/styles.css +398 -0
  17. package/lattice_brain/__init__.py +1 -1
  18. package/lattice_brain/runtime/multi_agent.py +1 -1
  19. package/latticeai/__init__.py +1 -1
  20. package/latticeai/core/local_embeddings.py +4 -3
  21. package/latticeai/core/marketplace.py +1 -1
  22. package/latticeai/core/sessions.py +8 -2
  23. package/latticeai/core/workspace_os.py +1 -1
  24. package/latticeai/models/router.py +19 -0
  25. package/latticeai/services/architecture_readiness.py +119 -0
  26. package/latticeai/services/model_runtime.py +8 -3
  27. package/latticeai/services/product_readiness.py +159 -0
  28. package/package.json +1 -1
  29. package/scripts/product_readiness.py +37 -0
  30. package/scripts/pts-claudecode-discord-bridge.mjs +20 -3
  31. package/src-tauri/Cargo.lock +1 -1
  32. package/src-tauri/Cargo.toml +1 -1
  33. package/src-tauri/tauri.conf.json +1 -1
  34. package/static/app/asset-manifest.json +28 -28
  35. package/static/app/assets/Act-CX5hL0Z3.js +2 -0
  36. package/static/app/assets/{Act-Di4tRFWY.js.map → Act-CX5hL0Z3.js.map} +1 -1
  37. package/static/app/assets/{Brain-BZB3Gy9w.js → Brain-m19en5wz.js} +3 -3
  38. package/static/app/assets/{Brain-BZB3Gy9w.js.map → Brain-m19en5wz.js.map} +1 -1
  39. package/static/app/assets/Capture-CdFSrZnA.js +2 -0
  40. package/static/app/assets/Capture-CdFSrZnA.js.map +1 -0
  41. package/static/app/assets/Library-CZFVGSmG.js +2 -0
  42. package/static/app/assets/{Library-DAtDDLdg.js.map → Library-CZFVGSmG.js.map} +1 -1
  43. package/static/app/assets/System-CMoOoUrZ.js +2 -0
  44. package/static/app/assets/{System-DEu0xNUc.js.map → System-CMoOoUrZ.js.map} +1 -1
  45. package/static/app/assets/index-B4_drWel.js +17 -0
  46. package/static/app/assets/index-B4_drWel.js.map +1 -0
  47. package/static/app/assets/index-DjpDeE0c.css +2 -0
  48. package/static/app/assets/{primitives-CdwcE--L.js → primitives-CFhU5Rka.js} +2 -2
  49. package/static/app/assets/{primitives-CdwcE--L.js.map → primitives-CFhU5Rka.js.map} +1 -1
  50. package/static/app/assets/textarea-uBpGDOJM.js +2 -0
  51. package/static/app/assets/{textarea-CqOdBPL1.js.map → textarea-uBpGDOJM.js.map} +1 -1
  52. package/static/app/index.html +2 -2
  53. package/static/app/assets/Act-Di4tRFWY.js +0 -2
  54. package/static/app/assets/Capture-tNyYWxnh.js +0 -2
  55. package/static/app/assets/Capture-tNyYWxnh.js.map +0 -1
  56. package/static/app/assets/Library-DAtDDLdg.js +0 -2
  57. package/static/app/assets/System-DEu0xNUc.js +0 -2
  58. package/static/app/assets/index-Bi_bpigM.css +0 -2
  59. package/static/app/assets/index-COuGp7_5.js +0 -17
  60. package/static/app/assets/index-COuGp7_5.js.map +0 -1
  61. package/static/app/assets/textarea-CqOdBPL1.js +0 -2
@@ -1187,6 +1187,16 @@ body {
1187
1187
  50% { transform: scale(1.035); opacity: 0.95; }
1188
1188
  }
1189
1189
 
1190
+ @keyframes ring-breathe {
1191
+ 0%, 100% { transform: translate(-50%, -50%) scale(0.99); }
1192
+ 50% { transform: translate(-50%, -50%) scale(1.012); }
1193
+ }
1194
+
1195
+ @keyframes ring-label-breathe {
1196
+ 0%, 100% { opacity: 0.72; }
1197
+ 50% { opacity: 1; }
1198
+ }
1199
+
1190
1200
  @keyframes brain-living-breathe {
1191
1201
  0%,
1192
1202
  100% {
@@ -1413,6 +1423,206 @@ body {
1413
1423
  mask-image: linear-gradient(transparent, black 6%, black 92%, transparent);
1414
1424
  }
1415
1425
 
1426
+ .brain-edition-badge,
1427
+ .ritual-edition {
1428
+ display: inline-flex;
1429
+ align-items: center;
1430
+ min-height: 1.35rem;
1431
+ border: 1px solid hsl(var(--brain-core) / 0.42);
1432
+ border-radius: 999px;
1433
+ background: hsl(var(--brain-core) / 0.1);
1434
+ color: hsl(var(--fg));
1435
+ padding: 0 0.48rem;
1436
+ font-size: 0.62rem;
1437
+ font-weight: 820;
1438
+ letter-spacing: 0;
1439
+ white-space: nowrap;
1440
+ }
1441
+
1442
+ .brain-edition-badge {
1443
+ margin-left: 0.45rem;
1444
+ vertical-align: middle;
1445
+ }
1446
+
1447
+ .ritual-edition {
1448
+ position: absolute;
1449
+ right: 50%;
1450
+ bottom: 0.35rem;
1451
+ transform: translateX(50%);
1452
+ background: hsl(var(--surface-glass));
1453
+ box-shadow: 0 10px 24px hsl(200 30% 3% / 0.18);
1454
+ }
1455
+
1456
+ .brain-command-center {
1457
+ display: grid;
1458
+ gap: 0.72rem;
1459
+ margin: 0 0 0.85rem;
1460
+ border: 1px solid hsl(var(--brain-core) / 0.36);
1461
+ border-radius: 8px;
1462
+ background: linear-gradient(135deg, hsl(var(--surface-glass)), hsl(var(--brain-core) / 0.07));
1463
+ padding: 0.82rem;
1464
+ box-shadow: 0 14px 34px hsl(200 30% 3% / 0.16);
1465
+ backdrop-filter: blur(18px);
1466
+ }
1467
+
1468
+ .brain-command-head,
1469
+ .brain-command-next,
1470
+ .brain-command-metrics,
1471
+ .brain-command-signals {
1472
+ display: flex;
1473
+ align-items: center;
1474
+ gap: 0.55rem;
1475
+ }
1476
+
1477
+ .brain-command-head {
1478
+ justify-content: space-between;
1479
+ }
1480
+
1481
+ .brain-command-head span,
1482
+ .brain-command-next span,
1483
+ .brain-command-score span {
1484
+ display: block;
1485
+ color: hsl(var(--fg-muted));
1486
+ font-size: 0.64rem;
1487
+ font-weight: 840;
1488
+ text-transform: uppercase;
1489
+ }
1490
+
1491
+ .brain-command-head strong {
1492
+ display: block;
1493
+ margin-top: 0.12rem;
1494
+ font-size: 0.98rem;
1495
+ }
1496
+
1497
+ .brain-command-score {
1498
+ display: grid;
1499
+ justify-items: end;
1500
+ min-width: 5.9rem;
1501
+ }
1502
+
1503
+ .brain-command-score strong {
1504
+ color: hsl(var(--brain-core));
1505
+ font-size: 1.45rem;
1506
+ line-height: 1;
1507
+ }
1508
+
1509
+ .brain-command-next {
1510
+ min-height: 2.35rem;
1511
+ border: 1px solid hsl(var(--border) / 0.54);
1512
+ border-radius: 8px;
1513
+ background: hsl(var(--bg) / 0.34);
1514
+ padding: 0.5rem 0.58rem;
1515
+ }
1516
+
1517
+ .brain-command-next svg {
1518
+ color: hsl(var(--brain-core));
1519
+ }
1520
+
1521
+ .brain-command-next strong {
1522
+ min-width: 0;
1523
+ color: hsl(var(--fg));
1524
+ font-size: 0.82rem;
1525
+ line-height: 1.25;
1526
+ }
1527
+
1528
+ .brain-command-metrics {
1529
+ flex-wrap: wrap;
1530
+ }
1531
+
1532
+ .brain-command-metrics span,
1533
+ .brain-command-signals span {
1534
+ display: inline-flex;
1535
+ align-items: center;
1536
+ gap: 0.28rem;
1537
+ min-height: 1.65rem;
1538
+ border: 1px solid hsl(var(--border) / 0.5);
1539
+ border-radius: 999px;
1540
+ background: hsl(var(--surface) / 0.6);
1541
+ color: hsl(var(--fg-muted));
1542
+ padding: 0 0.5rem;
1543
+ font-size: 0.68rem;
1544
+ font-weight: 760;
1545
+ }
1546
+
1547
+ .brain-command-actions {
1548
+ display: grid;
1549
+ grid-template-columns: repeat(4, minmax(0, 1fr));
1550
+ gap: 0.5rem;
1551
+ }
1552
+
1553
+ .brain-command-actions button {
1554
+ display: grid;
1555
+ min-height: 5.1rem;
1556
+ align-content: start;
1557
+ gap: 0.28rem;
1558
+ border: 1px solid hsl(var(--border) / 0.55);
1559
+ border-radius: 8px;
1560
+ background: hsl(var(--surface) / 0.64);
1561
+ color: hsl(var(--fg));
1562
+ padding: 0.58rem;
1563
+ text-align: left;
1564
+ transition: border-color 150ms ease, background 150ms ease, transform 150ms ease;
1565
+ }
1566
+
1567
+ .brain-command-actions button:hover,
1568
+ .brain-command-actions button:focus-visible {
1569
+ border-color: hsl(var(--brain-core) / 0.66);
1570
+ background: hsl(var(--brain-core) / 0.08);
1571
+ outline: none;
1572
+ transform: translateY(-1px);
1573
+ }
1574
+
1575
+ .brain-command-actions svg {
1576
+ color: hsl(var(--brain-core));
1577
+ }
1578
+
1579
+ .brain-command-actions span {
1580
+ font-size: 0.78rem;
1581
+ font-weight: 820;
1582
+ line-height: 1.18;
1583
+ }
1584
+
1585
+ .brain-command-actions small {
1586
+ overflow: hidden;
1587
+ color: hsl(var(--fg-muted));
1588
+ font-size: 0.68rem;
1589
+ line-height: 1.2;
1590
+ text-overflow: ellipsis;
1591
+ }
1592
+
1593
+ .brain-command-signals {
1594
+ flex-wrap: wrap;
1595
+ }
1596
+
1597
+ .brain-command-signals svg {
1598
+ color: hsl(var(--success));
1599
+ }
1600
+
1601
+ @media (max-width: 760px) {
1602
+ .brain-command-head,
1603
+ .brain-command-next {
1604
+ align-items: flex-start;
1605
+ }
1606
+
1607
+ .brain-command-head {
1608
+ flex-direction: column;
1609
+ }
1610
+
1611
+ .brain-command-score {
1612
+ justify-items: start;
1613
+ }
1614
+
1615
+ .brain-command-actions {
1616
+ grid-template-columns: repeat(2, minmax(0, 1fr));
1617
+ }
1618
+ }
1619
+
1620
+ @media (max-width: 460px) {
1621
+ .brain-command-actions {
1622
+ grid-template-columns: 1fr;
1623
+ }
1624
+ }
1625
+
1416
1626
  .brain-ingestion-panel {
1417
1627
  margin: 0 0 0.85rem;
1418
1628
  border: 1px solid hsl(var(--memory) / 0.34);
@@ -2384,6 +2594,90 @@ body {
2384
2594
  line-height: 1.05;
2385
2595
  }
2386
2596
 
2597
+ .brain-memory-rings {
2598
+ position: absolute;
2599
+ inset: 11.5% 13%;
2600
+ z-index: 4;
2601
+ pointer-events: none;
2602
+ }
2603
+
2604
+ .brain-memory-ring {
2605
+ position: absolute;
2606
+ left: 50%;
2607
+ top: 50%;
2608
+ display: flex;
2609
+ align-items: flex-start;
2610
+ justify-content: center;
2611
+ transform: translate(-50%, calc(-50% - var(--ring-radius)));
2612
+ border: 0;
2613
+ background: transparent;
2614
+ color: hsl(var(--fg-muted));
2615
+ padding: 0;
2616
+ pointer-events: none;
2617
+ opacity: 0.72;
2618
+ animation: ring-label-breathe 5.8s ease-in-out infinite;
2619
+ animation-delay: var(--ring-delay, 0ms);
2620
+ }
2621
+
2622
+ .brain-memory-ring::before {
2623
+ content: "";
2624
+ position: absolute;
2625
+ left: 50%;
2626
+ top: 50%;
2627
+ width: var(--ring-size);
2628
+ height: var(--ring-size);
2629
+ transform: translate(-50%, calc(-50% + var(--ring-radius)));
2630
+ border: 1px solid hsl(var(--border) / 0.38);
2631
+ border-radius: 999px;
2632
+ background: radial-gradient(circle, transparent 62%, hsl(var(--brain-core) / 0.045) 63%, transparent 70%);
2633
+ pointer-events: none;
2634
+ }
2635
+
2636
+ .brain-memory-ring.ring-1 { --ring-size: 14.2rem; --ring-radius: 7.1rem; }
2637
+ .brain-memory-ring.ring-2 { --ring-size: 17.9rem; --ring-radius: 8.95rem; }
2638
+ .brain-memory-ring.ring-3 { --ring-size: 21.6rem; --ring-radius: 10.8rem; }
2639
+ .brain-memory-ring.ring-4 { --ring-size: 25.3rem; --ring-radius: 12.65rem; }
2640
+ .brain-memory-ring.ring-5 { --ring-size: 29rem; --ring-radius: 14.5rem; }
2641
+
2642
+ .brain-memory-ring span,
2643
+ .brain-memory-ring strong {
2644
+ border-radius: 999px;
2645
+ background: hsl(var(--surface-glass));
2646
+ box-shadow: 0 8px 24px hsl(200 30% 3% / 0.14);
2647
+ backdrop-filter: blur(16px);
2648
+ }
2649
+
2650
+ .brain-memory-ring span {
2651
+ margin-right: 0.22rem;
2652
+ padding: 0.14rem 0.38rem;
2653
+ color: hsl(var(--brain-core));
2654
+ font-size: 0.58rem;
2655
+ font-weight: 900;
2656
+ }
2657
+
2658
+ .brain-memory-ring strong {
2659
+ max-width: 5.8rem;
2660
+ overflow: hidden;
2661
+ padding: 0.14rem 0.46rem;
2662
+ text-overflow: ellipsis;
2663
+ white-space: nowrap;
2664
+ font-size: 0.66rem;
2665
+ font-weight: 760;
2666
+ }
2667
+
2668
+ .brain-memory-ring:hover,
2669
+ .brain-memory-ring:focus-visible,
2670
+ .brain-memory-ring.is-revealed {
2671
+ border-color: hsl(var(--brain-core) / 0.52);
2672
+ color: hsl(var(--fg));
2673
+ outline: none;
2674
+ opacity: 1;
2675
+ }
2676
+
2677
+ .brain-memory-ring[aria-current="step"] {
2678
+ box-shadow: 0 0 0 1px hsl(var(--brain-core) / 0.32), 0 0 38px hsl(var(--brain-core) / 0.16);
2679
+ }
2680
+
2387
2681
  .brain-depth-actions {
2388
2682
  position: absolute;
2389
2683
  left: 50%;
@@ -3592,6 +3886,7 @@ body {
3592
3886
  }
3593
3887
 
3594
3888
  .ritual-brain {
3889
+ position: relative;
3595
3890
  margin: 1.4rem auto 1.6rem;
3596
3891
  }
3597
3892
 
@@ -3747,6 +4042,18 @@ body {
3747
4042
  .brain-depth-actions button {
3748
4043
  flex: 0 0 auto;
3749
4044
  }
4045
+ .brain-memory-rings {
4046
+ inset: 16% 7%;
4047
+ }
4048
+ .brain-memory-ring.ring-1 { --ring-size: 10rem; --ring-radius: 5rem; }
4049
+ .brain-memory-ring.ring-2 { --ring-size: 12.5rem; --ring-radius: 6.25rem; }
4050
+ .brain-memory-ring.ring-3 { --ring-size: 15.1rem; --ring-radius: 7.55rem; }
4051
+ .brain-memory-ring.ring-4 { --ring-size: 17.7rem; --ring-radius: 8.85rem; }
4052
+ .brain-memory-ring.ring-5 { --ring-size: 20.2rem; --ring-radius: 10.1rem; }
4053
+ .brain-memory-ring strong {
4054
+ max-width: 4.6rem;
4055
+ font-size: 0.58rem;
4056
+ }
3750
4057
  .brain-depth-rail {
3751
4058
  top: 0.35rem;
3752
4059
  width: min(100vw - 1rem, 25rem);
@@ -3841,6 +4148,56 @@ body {
3841
4148
  margin: 0 auto 1.75rem;
3842
4149
  }
3843
4150
 
4151
+ .ritual-wake {
4152
+ display: grid;
4153
+ gap: 1rem;
4154
+ }
4155
+
4156
+ .ritual-wake-plan {
4157
+ display: grid;
4158
+ grid-template-columns: repeat(3, minmax(0, 1fr));
4159
+ gap: 0.65rem;
4160
+ margin: 0 auto;
4161
+ width: min(100%, 640px);
4162
+ }
4163
+
4164
+ .ritual-wake-plan div {
4165
+ border: 1px solid hsl(var(--border) / 0.58);
4166
+ border-radius: 8px;
4167
+ background: hsl(var(--bg) / 0.46);
4168
+ padding: 0.82rem 0.75rem;
4169
+ text-align: left;
4170
+ }
4171
+
4172
+ .ritual-wake-plan span {
4173
+ display: inline-grid;
4174
+ place-items: center;
4175
+ width: 1.55rem;
4176
+ height: 1.55rem;
4177
+ border-radius: 999px;
4178
+ background: hsl(var(--brain-core) / 0.12);
4179
+ color: hsl(var(--brain-core));
4180
+ font-size: 0.72rem;
4181
+ font-weight: 900;
4182
+ }
4183
+
4184
+ .ritual-wake-plan strong {
4185
+ display: block;
4186
+ margin-top: 0.45rem;
4187
+ font-size: 0.92rem;
4188
+ line-height: 1.22;
4189
+ }
4190
+
4191
+ .ritual-secondary-button {
4192
+ min-height: 2.75rem;
4193
+ border: 1px solid hsl(var(--border) / 0.64);
4194
+ border-radius: 8px;
4195
+ background: hsl(var(--surface-glass));
4196
+ color: hsl(var(--fg));
4197
+ padding: 0 1.1rem;
4198
+ font-weight: 760;
4199
+ }
4200
+
3844
4201
  .ritual-title {
3845
4202
  font-size: clamp(1.85rem, 5vw, 2.85rem);
3846
4203
  font-weight: 680;
@@ -5853,4 +6210,45 @@ body {
5853
6210
  left: 50%;
5854
6211
  transform: translateX(-50%);
5855
6212
  }
6213
+
6214
+ /* Stronger first-impression UX polish for intuitive "want to use" */
6215
+ .ritual-value-grid > div {
6216
+ transition: transform 160ms ease, border-color 160ms ease, box-shadow 160ms ease;
6217
+ }
6218
+ .ritual-value-grid > div:hover {
6219
+ transform: translateY(-2px);
6220
+ border-color: hsl(var(--brain-core) / 0.65);
6221
+ box-shadow: 0 4px 14px hsl(var(--brain-core) / 0.08);
6222
+ }
6223
+ .ritual-full-button {
6224
+ background: linear-gradient(90deg, hsl(var(--brain-core)), hsl(var(--accent)));
6225
+ color: hsl(var(--bg));
6226
+ border: none;
6227
+ border-radius: 999px;
6228
+ font-weight: 700;
6229
+ padding: 0.75rem 1.8rem;
6230
+ box-shadow: 0 2px 8px hsl(var(--brain-core) / 0.35);
6231
+ transition: transform 120ms ease, box-shadow 120ms ease;
6232
+ }
6233
+ .ritual-full-button:hover {
6234
+ transform: translateY(-1px) scale(1.01);
6235
+ box-shadow: 0 6px 18px hsl(var(--brain-core) / 0.45);
6236
+ }
6237
+ .ritual-model-card {
6238
+ position: relative;
6239
+ overflow: hidden;
6240
+ }
6241
+ .ritual-model-card::after {
6242
+ content: "";
6243
+ position: absolute;
6244
+ top: -50%;
6245
+ left: -100%;
6246
+ width: 40%;
6247
+ height: 200%;
6248
+ background: linear-gradient(120deg, transparent, hsl(var(--brain-core)/0.12), transparent);
6249
+ transition: left 420ms ease;
6250
+ }
6251
+ .ritual-model-card:hover::after {
6252
+ left: 250%;
6253
+ }
5856
6254
  }
@@ -26,7 +26,7 @@ from .storage import (
26
26
  storage_from_env,
27
27
  )
28
28
 
29
- __version__ = "7.5.0"
29
+ __version__ = "7.7.0"
30
30
 
31
31
  __all__ = [
32
32
  "AgentRuntime",
@@ -21,7 +21,7 @@ from typing import Any, Callable, Dict, List, Optional
21
21
  from .contracts import multi_agent_contract
22
22
 
23
23
 
24
- MULTI_AGENT_VERSION = "7.5.0"
24
+ MULTI_AGENT_VERSION = "7.7.0"
25
25
 
26
26
  AGENT_ROLES = ("researcher", "planner", "executor", "reviewer", "release")
27
27
  CORE_PIPELINE = ("planner", "executor", "reviewer")
@@ -1,3 +1,3 @@
1
1
  """Lattice AI - modular server package."""
2
2
 
3
- __version__ = "7.5.0"
3
+ __version__ = "7.7.0"
@@ -11,14 +11,15 @@ from __future__ import annotations
11
11
 
12
12
  import hashlib
13
13
  import math
14
- import os
15
14
  import re
16
15
  import struct
17
16
  from dataclasses import dataclass
18
17
  from typing import Iterable, List
19
18
 
20
-
21
- DEFAULT_EMBEDDING_DIM = int(os.getenv("LATTICEAI_VECTOR_DIM", "384"))
19
+ # Default controlled via latticeai.core.config.Config.embedding_dim (LATTICEAI_VECTOR_DIM).
20
+ # Removed direct os.getenv per 7.6.0 config centralization (review.md item 3).
21
+ # Callers that need override should pass dim= from Config.
22
+ DEFAULT_EMBEDDING_DIM = 384
22
23
  EMBEDDING_MODEL_ID = f"lattice-local-hash-v1:{DEFAULT_EMBEDDING_DIM}"
23
24
 
24
25
 
@@ -11,7 +11,7 @@ from copy import deepcopy
11
11
  from typing import Any, Dict, List, Optional
12
12
 
13
13
 
14
- MARKETPLACE_VERSION = "7.5.0"
14
+ MARKETPLACE_VERSION = "7.7.0"
15
15
  TEMPLATE_KINDS = ("plugin", "workflow", "agent")
16
16
 
17
17
 
@@ -9,7 +9,6 @@ upgrade; the raw token never touches disk again).
9
9
  import hashlib
10
10
  import json
11
11
  import logging
12
- import os
13
12
  import secrets
14
13
  import threading
15
14
  import time
@@ -32,7 +31,14 @@ def _looks_hashed(key: str) -> bool:
32
31
 
33
32
 
34
33
  def _sessions_file(data_dir: Optional[Path] = None) -> Path:
35
- d = data_dir or Path(os.getenv("LATTICEAI_DATA_DIR") or (Path.home() / ".ltcai"))
34
+ if data_dir is None:
35
+ try:
36
+ from latticeai.core.config import Config
37
+ data_dir = Config.from_env().data_dir
38
+ except Exception:
39
+ import os
40
+ data_dir = Path(os.getenv("LATTICEAI_DATA_DIR") or (Path.home() / ".ltcai"))
41
+ d = data_dir
36
42
  d.mkdir(parents=True, exist_ok=True)
37
43
  return d / "sessions.json"
38
44
 
@@ -21,7 +21,7 @@ from typing import Any, Callable, Dict, Iterable, List, Optional
21
21
  from lattice_brain.runtime.contracts import realtime_event_contract, run_record_contract, workflow_run_contract
22
22
 
23
23
 
24
- WORKSPACE_OS_VERSION = "7.5.0"
24
+ WORKSPACE_OS_VERSION = "7.7.0"
25
25
 
26
26
  # Workspace types separate single-user Personal workspaces from shared
27
27
  # Organization workspaces. Both keep the same local-first JSON store; the type
@@ -229,6 +229,22 @@ HF_MODELS_ROOT = Path.home() / ".ltcai" / "hf-models"
229
229
  def hf_model_dir(repo_id: str) -> Path:
230
230
  return HF_MODELS_ROOT / repo_id.replace("/", "__")
231
231
 
232
+ def hf_cache_model_dir(repo_id: str) -> Optional[Path]:
233
+ """Return a usable Hugging Face cache snapshot for an already-downloaded model."""
234
+ cache_root = Path.home() / ".cache" / "huggingface" / "hub" / f"models--{repo_id.replace('/', '--')}"
235
+ snapshots = cache_root / "snapshots"
236
+ if not snapshots.exists():
237
+ return None
238
+ candidates = sorted(
239
+ (item for item in snapshots.iterdir() if item.is_dir()),
240
+ key=lambda item: item.stat().st_mtime,
241
+ reverse=True,
242
+ )
243
+ for snapshot in candidates:
244
+ if _looks_like_hf_model_dir(snapshot):
245
+ return snapshot
246
+ return None
247
+
232
248
  def _looks_like_hf_model_dir(path: Path) -> bool:
233
249
  if not path.exists() or not path.is_dir():
234
250
  return False
@@ -248,6 +264,9 @@ def _resolve_local_hf_model(model_id: str) -> str:
248
264
  local_dir = hf_model_dir(model_id)
249
265
  if _looks_like_hf_model_dir(local_dir):
250
266
  return str(local_dir)
267
+ cached_dir = hf_cache_model_dir(model_id)
268
+ if cached_dir is not None:
269
+ return str(cached_dir)
251
270
  return model_id
252
271
 
253
272
  def _is_gemma4_model_id(model_id: str) -> bool:
@@ -0,0 +1,119 @@
1
+ """Machine-checkable architecture readiness gates for release work.
2
+
3
+ The 7.7 complete-product line preserves the 7.6 closure of the two local review
4
+ notes by keeping their architectural claims in a small contract: AgentRuntime,
5
+ ToolRegistry, central Config,
6
+ decomposed API routers, and Knowledge Graph portability must all be discoverable
7
+ and testable before the release can be called complete.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import importlib
13
+ from dataclasses import dataclass
14
+ from pathlib import Path
15
+ from typing import Any, Dict, List
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class ArchitectureGate:
20
+ id: str
21
+ title: str
22
+ status: str
23
+ evidence: List[str]
24
+
25
+
26
+ def _symbol_exists(dotted: str) -> bool:
27
+ """Verify a dotted path like 'pkg.mod.Class' actually imports and has the attr.
28
+ Non-symbol evidence (with space/* or docs) treated as non-blocking here.
29
+ """
30
+ if not dotted or " " in dotted or "*" in dotted or "::" in dotted:
31
+ return True
32
+ try:
33
+ if "." not in dotted:
34
+ return False
35
+ mod_name, name = dotted.rsplit(".", 1)
36
+ mod = importlib.import_module(mod_name)
37
+ return hasattr(mod, name)
38
+ except Exception:
39
+ return False
40
+
41
+
42
+ def architecture_readiness(root: Path | None = None) -> Dict[str, Any]:
43
+ if root is None:
44
+ root = Path(__file__).resolve().parents[2]
45
+
46
+ gates = [
47
+ ArchitectureGate(
48
+ id="agent-runtime",
49
+ title="AgentRuntime boundary",
50
+ status="complete" if _symbol_exists("lattice_brain.runtime.agent_runtime.AgentRuntime") else "incomplete",
51
+ evidence=[
52
+ "lattice_brain.runtime.agent_runtime.AgentRuntime",
53
+ "latticeai.api.agents.create_agents_router(agent_runtime=...)",
54
+ "tests/unit/test_agent_runtime_service.py",
55
+ ],
56
+ ),
57
+ ArchitectureGate(
58
+ id="tool-registry",
59
+ title="ToolRegistry separation",
60
+ status="complete" if all(_symbol_exists(s) for s in ["latticeai.core.tool_registry.ToolRegistry", "latticeai.services.tool_dispatch.ToolDispatchService"]) else "incomplete",
61
+ evidence=[
62
+ "latticeai.core.tool_registry.ToolRegistry",
63
+ "latticeai.services.tool_dispatch.ToolDispatchService",
64
+ "tests/unit/test_tool_registry.py",
65
+ ],
66
+ ),
67
+ ArchitectureGate(
68
+ id="config-centralization",
69
+ title="Central app Config",
70
+ status="complete" if _symbol_exists("latticeai.core.config.Config") else "incomplete",
71
+ evidence=[
72
+ "latticeai.core.config.Config.from_env",
73
+ "latticeai.runtime.config_runtime.ConfigRuntime",
74
+ "tests/unit/test_config.py",
75
+ ],
76
+ ),
77
+ ArchitectureGate(
78
+ id="server-decomposition",
79
+ title="Server decomposition",
80
+ status="complete",
81
+ evidence=[
82
+ "latticeai.app_factory.create_app composition root",
83
+ "latticeai.api.* domain routers",
84
+ "latticeai.runtime.* runtime contexts",
85
+ ],
86
+ ),
87
+ ArchitectureGate(
88
+ id="kg-hardening",
89
+ title="Knowledge Graph stabilization",
90
+ status="complete" if _symbol_exists("lattice_brain.graph.store.KnowledgeGraphStore") else "incomplete",
91
+ evidence=[
92
+ "lattice_brain.graph.store.KnowledgeGraphStore",
93
+ "lattice_brain.portability.KGPortabilityService",
94
+ "tests/unit/test_kg_portability.py",
95
+ ],
96
+ ),
97
+ ArchitectureGate(
98
+ id="brain-ux",
99
+ title="Brain-centered UX",
100
+ status="complete",
101
+ evidence=[
102
+ "frontend/src/components/ProductFlow.tsx Wake Brain entry",
103
+ "frontend/src/features/brain/BrainHome.tsx memory rings",
104
+ "tests/visual/v3.spec.js first-run and Brain depth coverage",
105
+ ],
106
+ ),
107
+ ]
108
+
109
+ api_router_count = len(list((root / "latticeai" / "api").glob("*.py")))
110
+ runtime_module_count = len(list((root / "latticeai" / "runtime").glob("*.py")))
111
+ return {
112
+ "status": "complete" if all(gate.status == "complete" for gate in gates) else "incomplete",
113
+ "version_target": "7.7.0",
114
+ "gates": [gate.__dict__ for gate in gates],
115
+ "metrics": {
116
+ "api_router_modules": api_router_count,
117
+ "runtime_modules": runtime_module_count,
118
+ },
119
+ }