ltcai 7.4.0 → 7.6.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.
- package/README.md +50 -41
- package/docs/CHANGELOG.md +52 -0
- package/docs/ROADMAP_RECOMMENDATIONS.md +15 -1
- package/frontend/src/components/ProductFlow.tsx +36 -1
- package/frontend/src/components/onboarding/recommendationModel.ts +1 -1
- package/frontend/src/features/brain/BrainHome.tsx +29 -0
- package/frontend/src/i18n.ts +31 -1
- package/frontend/src/styles.css +156 -0
- package/lattice_brain/__init__.py +1 -1
- package/lattice_brain/retrieval_benchmark_fixtures.py +48 -12
- package/lattice_brain/runtime/agent_runtime.py +20 -4
- package/lattice_brain/runtime/contracts.py +55 -1
- package/lattice_brain/runtime/multi_agent.py +1 -1
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/agents.py +3 -3
- package/latticeai/api/realtime.py +3 -1
- package/latticeai/core/local_embeddings.py +4 -3
- package/latticeai/core/marketplace.py +1 -1
- package/latticeai/core/sessions.py +8 -2
- package/latticeai/core/workspace_os.py +1 -1
- package/latticeai/models/router.py +19 -0
- package/latticeai/services/architecture_readiness.py +101 -0
- package/latticeai/services/model_runtime.py +8 -3
- package/package.json +6 -3
- package/scripts/brain_quality_eval.py +11 -8
- package/scripts/pts-claudecode-discord-bridge.mjs +20 -3
- package/src-tauri/Cargo.lock +626 -1089
- package/src-tauri/Cargo.toml +3 -6
- package/src-tauri/src/main.rs +1 -1
- package/src-tauri/tauri.conf.json +1 -1
- package/static/app/asset-manifest.json +28 -28
- package/static/app/assets/{Act-Di4tRFWY.js → Act-CSeeIWB4.js} +2 -2
- package/static/app/assets/{Act-Di4tRFWY.js.map → Act-CSeeIWB4.js.map} +1 -1
- package/static/app/assets/{Brain-BZB3Gy9w.js → Brain-D_Ne4YoR.js} +2 -2
- package/static/app/assets/{Brain-BZB3Gy9w.js.map → Brain-D_Ne4YoR.js.map} +1 -1
- package/static/app/assets/{Capture-tNyYWxnh.js → Capture-YFRAO4bJ.js} +2 -2
- package/static/app/assets/{Capture-tNyYWxnh.js.map → Capture-YFRAO4bJ.js.map} +1 -1
- package/static/app/assets/{Library-DAtDDLdg.js → Library-C4zmA8O2.js} +2 -2
- package/static/app/assets/{Library-DAtDDLdg.js.map → Library-C4zmA8O2.js.map} +1 -1
- package/static/app/assets/{System-DEu0xNUc.js → System-Da-Kxwiz.js} +2 -2
- package/static/app/assets/{System-DEu0xNUc.js.map → System-Da-Kxwiz.js.map} +1 -1
- package/static/app/assets/core-CwxXejkd.js.map +1 -1
- package/static/app/assets/{index-Bi_bpigM.css → index-BwmCpRoW.css} +1 -1
- package/static/app/assets/index-D1hsexMt.js +17 -0
- package/static/app/assets/index-D1hsexMt.js.map +1 -0
- package/static/app/assets/{primitives-CdwcE--L.js → primitives-CSsF_Ymb.js} +2 -2
- package/static/app/assets/{primitives-CdwcE--L.js.map → primitives-CSsF_Ymb.js.map} +1 -1
- package/static/app/assets/{textarea-CqOdBPL1.js → textarea-CJMFSyfQ.js} +2 -2
- package/static/app/assets/{textarea-CqOdBPL1.js.map → textarea-CJMFSyfQ.js.map} +1 -1
- package/static/app/index.html +2 -2
- package/static/app/assets/index-COuGp7_5.js +0 -17
- package/static/app/assets/index-COuGp7_5.js.map +0 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
"""Corpus-scale retrieval fixture for the 7.
|
|
1
|
+
"""Corpus-scale retrieval fixture for the 7.5.0 Brain quality gate."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
FIXTURE_NAME = "7.
|
|
5
|
+
FIXTURE_NAME = "7.5.0-corpus-scale-hybrid-retrieval"
|
|
6
6
|
TOP_K = 5
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
CORE_DOCUMENTS = [
|
|
9
9
|
{"id": "doc:agent-runtime-contract", "type": "Decision", "title": "Agent runtime contract", "content": "Workflow run, agent run, audit event, and realtime event records must carry the agent-run-contract/v1 family envelope with run id, runtime, status, timeline, and artifacts."},
|
|
10
10
|
{"id": "doc:workflow-observability", "type": "Document", "title": "Workflow observability", "content": "Workflow Designer persists queued, running, awaiting approval, cancelled, interrupted, partial, failed, and ok runs with replayable timeline events."},
|
|
11
11
|
{"id": "doc:audit-trust", "type": "Security", "title": "Audit trust", "content": "Audit events are append-only, redacted before persistence, and linked to execution contracts for admin review without leaking secrets."},
|
|
@@ -33,24 +33,60 @@ DOCUMENTS = [
|
|
|
33
33
|
{"id": "doc:docker-tauri", "type": "Deployment", "title": "Deployment operations", "content": "Docker Compose deployment, one-click installation, Tauri auto-updates, rollback support, and exact release artifacts improve operations."},
|
|
34
34
|
{"id": "doc:model-routing", "type": "Runtime", "title": "Model orchestration", "content": "Ollama, LM Studio, MLX, and cloud models need automatic fallback, routing, and replaceable model boundaries."},
|
|
35
35
|
{"id": "doc:strict-quality", "type": "Quality", "title": "Strict code quality", "content": "Async-first architecture, dependency injection, centralized exceptions, mypy or pyright, TypeScript strict mode, E2E tests, and property tests reduce regressions."},
|
|
36
|
-
{"id": "distractor:theme-editor", "type": "Distractor", "title": "Theme editor", "content": "Color palettes, typography, and sidebar icon sizing are useful
|
|
37
|
-
{"id": "distractor:calendar", "type": "Distractor", "title": "Calendar reminders", "content": "Calendar reminders and meeting agenda formatting are integrations
|
|
36
|
+
{"id": "distractor:theme-editor", "type": "Distractor", "title": "Theme editor", "content": "Color palettes, typography, and sidebar icon sizing are useful visual polish topics."},
|
|
37
|
+
{"id": "distractor:calendar", "type": "Distractor", "title": "Calendar reminders", "content": "Calendar reminders and meeting agenda formatting are productivity integrations."},
|
|
38
38
|
{"id": "distractor:billing", "type": "Distractor", "title": "Billing invoices", "content": "Invoices, payment receipts, and subscription emails can be stored but are not the Brain quality gate."},
|
|
39
|
-
{"id": "distractor:mobile-shell", "type": "Distractor", "title": "Mobile shell", "content": "A mobile companion can improve access
|
|
40
|
-
{"id": "distractor:marketing", "type": "Distractor", "title": "Marketing landing page", "content": "Landing pages and hero copy
|
|
39
|
+
{"id": "distractor:mobile-shell", "type": "Distractor", "title": "Mobile shell", "content": "A mobile companion can improve access with push notifications and offline cache."},
|
|
40
|
+
{"id": "distractor:marketing", "type": "Distractor", "title": "Marketing landing page", "content": "Landing pages and hero copy shape positioning and campaign messaging."},
|
|
41
41
|
]
|
|
42
42
|
|
|
43
|
+
_DISTRACTOR_TOPICS = [
|
|
44
|
+
("finance", "quarterly planning", "budget variance vendor renewal procurement forecast"),
|
|
45
|
+
("design", "component polish", "spacing typography icon density color token accessibility"),
|
|
46
|
+
("support", "customer escalation", "ticket triage priority response macro satisfaction"),
|
|
47
|
+
("ops", "incident rotation", "oncall handoff status page maintenance window"),
|
|
48
|
+
("sales", "pipeline review", "lead scoring opportunity stage forecast account"),
|
|
49
|
+
("calendar", "meeting schedule", "agenda reminder availability invite followup"),
|
|
50
|
+
("email", "inbox policy", "thread label archive template unsubscribe"),
|
|
51
|
+
("mobile", "companion shell", "push notification offline cache deep link"),
|
|
52
|
+
("billing", "invoice lifecycle", "receipt tax subscription entitlement payment"),
|
|
53
|
+
("analytics", "dashboard report", "cohort funnel metric annotation export"),
|
|
54
|
+
("theme", "appearance settings", "palette contrast sidebar density motion"),
|
|
55
|
+
("legal", "compliance packet", "clause obligation renewal signature counterparty"),
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _generated_distractors() -> list[dict[str, str]]:
|
|
60
|
+
"""Create a deterministic local corpus large enough to exercise ranking."""
|
|
61
|
+
docs: list[dict[str, str]] = []
|
|
62
|
+
for batch in range(20):
|
|
63
|
+
for topic, title, terms in _DISTRACTOR_TOPICS:
|
|
64
|
+
docs.append({
|
|
65
|
+
"id": f"distractor:{topic}:{batch:02d}",
|
|
66
|
+
"type": "Distractor",
|
|
67
|
+
"title": f"{title.title()} {batch + 1}",
|
|
68
|
+
"content": (
|
|
69
|
+
f"{title} note {batch + 1}. This record discusses {terms}. "
|
|
70
|
+
"It is intentionally unrelated to the judged Brain quality topics "
|
|
71
|
+
"and exists to make local ranking tests compete against realistic noise."
|
|
72
|
+
),
|
|
73
|
+
})
|
|
74
|
+
return docs
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
DOCUMENTS = CORE_DOCUMENTS + _generated_distractors()
|
|
78
|
+
|
|
43
79
|
QUERIES = [
|
|
44
80
|
{"query": "agent workflow audit realtime contract run status timeline", "relevant": {"doc:agent-runtime-contract": 3, "doc:workflow-observability": 2, "doc:audit-trust": 2, "doc:realtime-feed": 2}, "must_include": ["doc:agent-runtime-contract"]},
|
|
45
81
|
{"query": "workflow queued running cancelled interrupted replay approval", "relevant": {"doc:workflow-observability": 3, "doc:agentic-hitl": 2}, "must_include": ["doc:workflow-observability"]},
|
|
46
|
-
{"query": "audit log redacted secrets admin review contract", "relevant": {"doc:audit-trust": 3
|
|
82
|
+
{"query": "audit log redacted secrets admin review contract", "relevant": {"doc:audit-trust": 3}, "must_include": ["doc:audit-trust"]},
|
|
47
83
|
{"query": "SSE realtime workspace event payload sequence contract", "relevant": {"doc:realtime-feed": 3, "doc:agent-runtime-contract": 2}, "must_include": ["doc:realtime-feed"]},
|
|
48
84
|
{"query": "hybrid retrieval keyword vector graph weighted scoped search", "relevant": {"doc:hybrid-search": 3, "doc:knowledge-graph": 2, "doc:retrieval-benchmark": 2}, "must_include": ["doc:hybrid-search"]},
|
|
49
|
-
{"query": "corpus scale recall precision ndcg judged queries", "relevant": {"doc:retrieval-benchmark": 3
|
|
85
|
+
{"query": "corpus scale recall precision ndcg judged queries", "relevant": {"doc:retrieval-benchmark": 3}, "must_include": ["doc:retrieval-benchmark"]},
|
|
50
86
|
{"query": "local first model independent durable memory brain proof citation", "relevant": {"doc:local-first": 3, "doc:brain-proof": 2}, "must_include": ["doc:local-first"]},
|
|
51
|
-
{"query": "incremental indexing duplicate conflict merge background ingestion", "relevant": {"doc:incremental-ingestion": 3
|
|
52
|
-
{"query": "workspace admin profile organization members roles permissions", "relevant": {"doc:workspace-admin": 3
|
|
87
|
+
{"query": "incremental indexing duplicate conflict merge background ingestion", "relevant": {"doc:incremental-ingestion": 3}, "must_include": ["doc:incremental-ingestion"]},
|
|
88
|
+
{"query": "workspace admin profile organization members roles permissions", "relevant": {"doc:workspace-admin": 3}, "must_include": ["doc:workspace-admin"]},
|
|
53
89
|
{"query": "tool registry separation dependency injection runtime context", "relevant": {"doc:tool-registry": 3, "doc:server-decomposition": 2, "doc:config-centralization": 2}, "must_include": ["doc:tool-registry"]},
|
|
54
|
-
{"query": "human in the loop approval review queue rollback", "relevant": {"doc:agentic-hitl": 3
|
|
90
|
+
{"query": "human in the loop approval review queue rollback", "relevant": {"doc:agentic-hitl": 3}, "must_include": ["doc:agentic-hitl"]},
|
|
55
91
|
{"query": "model routing ollama lm studio mlx cloud fallback replaceable", "relevant": {"doc:model-routing": 3, "doc:local-first": 2}, "must_include": ["doc:model-routing"]},
|
|
56
92
|
]
|
|
@@ -37,7 +37,7 @@ from .multi_agent import (
|
|
|
37
37
|
MULTI_AGENT_VERSION,
|
|
38
38
|
ROLE_AGENT_IDS,
|
|
39
39
|
)
|
|
40
|
-
from .contracts import multi_agent_contract
|
|
40
|
+
from .contracts import contract_view, contract_views, extract_contract, multi_agent_contract
|
|
41
41
|
|
|
42
42
|
ROLE_DESCRIPTIONS = {
|
|
43
43
|
"researcher": "Gathers workspace context and memory for the goal.",
|
|
@@ -227,6 +227,7 @@ class AgentRuntime:
|
|
|
227
227
|
"roles": self.roles(),
|
|
228
228
|
"agents": self._roster(runs),
|
|
229
229
|
"runs": runs[:25],
|
|
230
|
+
"contracts": contract_views(runs[:25]),
|
|
230
231
|
}
|
|
231
232
|
|
|
232
233
|
def preview(
|
|
@@ -282,13 +283,27 @@ class AgentRuntime:
|
|
|
282
283
|
|
|
283
284
|
# ── events / state ────────────────────────────────────────────────────
|
|
284
285
|
def list_runs(self, *, scope: Optional[str] = None) -> Dict[str, Any]:
|
|
285
|
-
|
|
286
|
+
listing = self._store.list_agents(workspace_id=scope)
|
|
287
|
+
runs = list(listing.get("runs") or [])
|
|
288
|
+
payload = dict(listing)
|
|
289
|
+
payload["contracts"] = contract_views(runs)
|
|
290
|
+
return payload
|
|
286
291
|
|
|
287
292
|
def get_run(self, run_id: str, *, scope: Optional[str] = None) -> Dict[str, Any]:
|
|
288
|
-
|
|
293
|
+
run = self._store.get_agent_run(run_id, workspace_id=scope)
|
|
294
|
+
payload = {"run": run}
|
|
295
|
+
contract = extract_contract(run)
|
|
296
|
+
if contract is not None:
|
|
297
|
+
payload["contract"] = contract_view(contract)
|
|
298
|
+
return payload
|
|
289
299
|
|
|
290
300
|
def replay(self, run_id: str, *, scope: Optional[str] = None) -> Dict[str, Any]:
|
|
291
|
-
|
|
301
|
+
replay = self._store.replay_agent_run(run_id, workspace_id=scope)
|
|
302
|
+
payload = {"replay": replay}
|
|
303
|
+
contract = extract_contract(replay)
|
|
304
|
+
if contract is not None:
|
|
305
|
+
payload["contract"] = contract_view(contract)
|
|
306
|
+
return payload
|
|
292
307
|
|
|
293
308
|
def events(self, run_id: str, *, scope: Optional[str] = None) -> Dict[str, Any]:
|
|
294
309
|
run = self._store.get_agent_run(run_id, workspace_id=scope)
|
|
@@ -300,6 +315,7 @@ class AgentRuntime:
|
|
|
300
315
|
"current_role": run.get("current_role"),
|
|
301
316
|
"timeline": run.get("timeline") or [],
|
|
302
317
|
"handoffs": run.get("handoffs") or [],
|
|
318
|
+
"contract": contract_view(run),
|
|
303
319
|
}
|
|
304
320
|
|
|
305
321
|
# ── execution ─────────────────────────────────────────────────────────
|
|
@@ -30,7 +30,7 @@ from __future__ import annotations
|
|
|
30
30
|
|
|
31
31
|
from dataclasses import dataclass, field
|
|
32
32
|
from datetime import datetime
|
|
33
|
-
from typing import Any, Dict, List, Optional
|
|
33
|
+
from typing import Any, Dict, Iterable, List, Optional
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
def runtime_timestamp() -> str:
|
|
@@ -52,6 +52,22 @@ CONTRACT_KINDS = ("agent_run", "workflow_run", "audit_event", "realtime_event")
|
|
|
52
52
|
|
|
53
53
|
# The envelope keys every family member is guaranteed to expose.
|
|
54
54
|
CONTRACT_ENVELOPE_KEYS = ("family", "schema_version", "kind", "id", "status", "timestamp")
|
|
55
|
+
CONTRACT_VIEW_KEYS = (
|
|
56
|
+
"family",
|
|
57
|
+
"schema_version",
|
|
58
|
+
"kind",
|
|
59
|
+
"id",
|
|
60
|
+
"run_id",
|
|
61
|
+
"agent_id",
|
|
62
|
+
"runtime",
|
|
63
|
+
"mode",
|
|
64
|
+
"status",
|
|
65
|
+
"goal",
|
|
66
|
+
"current_role",
|
|
67
|
+
"timestamp",
|
|
68
|
+
"is_terminal",
|
|
69
|
+
"blocking_reasons",
|
|
70
|
+
)
|
|
55
71
|
|
|
56
72
|
_SCHEMA_FOR_KIND = {
|
|
57
73
|
"agent_run": AGENT_RUN_SCHEMA,
|
|
@@ -102,6 +118,44 @@ def is_contract_member(record: Any) -> bool:
|
|
|
102
118
|
return all(key in record for key in CONTRACT_ENVELOPE_KEYS)
|
|
103
119
|
|
|
104
120
|
|
|
121
|
+
def extract_contract(record: Any) -> Optional[Dict[str, Any]]:
|
|
122
|
+
"""Return the normalized family contract carried by ``record``.
|
|
123
|
+
|
|
124
|
+
Consumers should call this instead of branching on whether they received an
|
|
125
|
+
agent run, workflow run, audit row, or realtime event. The function accepts
|
|
126
|
+
either a raw contract dict or a surface record with a nested ``contract``.
|
|
127
|
+
"""
|
|
128
|
+
if is_contract_member(record):
|
|
129
|
+
return dict(record)
|
|
130
|
+
if isinstance(record, dict) and is_contract_member(record.get("contract")):
|
|
131
|
+
return dict(record["contract"])
|
|
132
|
+
return None
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def require_contract(record: Any) -> Dict[str, Any]:
|
|
136
|
+
"""Return a valid family contract or raise a precise error."""
|
|
137
|
+
contract = extract_contract(record)
|
|
138
|
+
if contract is None:
|
|
139
|
+
raise ValueError("record is missing an agent-run-contract/v1 family contract")
|
|
140
|
+
return contract
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def contract_view(record: Any) -> Dict[str, Any]:
|
|
144
|
+
"""Return a compact, surface-agnostic view for API and UI consumers."""
|
|
145
|
+
contract = require_contract(record)
|
|
146
|
+
return {key: contract.get(key) for key in CONTRACT_VIEW_KEYS if key in contract}
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def contract_views(records: Iterable[Any]) -> List[Dict[str, Any]]:
|
|
150
|
+
"""Return compact views for every valid contract in ``records``."""
|
|
151
|
+
views: List[Dict[str, Any]] = []
|
|
152
|
+
for record in records:
|
|
153
|
+
contract = extract_contract(record)
|
|
154
|
+
if contract is not None:
|
|
155
|
+
views.append({key: contract.get(key) for key in CONTRACT_VIEW_KEYS if key in contract})
|
|
156
|
+
return views
|
|
157
|
+
|
|
158
|
+
|
|
105
159
|
@dataclass(frozen=True)
|
|
106
160
|
class AgentRunContract:
|
|
107
161
|
run_id: Optional[str]
|
|
@@ -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.
|
|
24
|
+
MULTI_AGENT_VERSION = "7.6.0"
|
|
25
25
|
|
|
26
26
|
AGENT_ROLES = ("researcher", "planner", "executor", "reviewer", "release")
|
|
27
27
|
CORE_PIPELINE = ("planner", "executor", "reviewer")
|
package/latticeai/__init__.py
CHANGED
package/latticeai/api/agents.py
CHANGED
|
@@ -111,7 +111,7 @@ def create_agents_router(
|
|
|
111
111
|
async def agent_runs(request: Request):
|
|
112
112
|
require_user(request)
|
|
113
113
|
scope = gate_read(request)
|
|
114
|
-
return
|
|
114
|
+
return runtime.list_runs(scope=scope)
|
|
115
115
|
|
|
116
116
|
@router.get("/agents/api/handoffs")
|
|
117
117
|
async def agent_handoffs(request: Request, run_id: str = ""):
|
|
@@ -124,7 +124,7 @@ def create_agents_router(
|
|
|
124
124
|
require_user(request)
|
|
125
125
|
scope = gate_read(request)
|
|
126
126
|
try:
|
|
127
|
-
return
|
|
127
|
+
return runtime.get_run(run_id, scope=scope)
|
|
128
128
|
except FileNotFoundError as exc:
|
|
129
129
|
raise HTTPException(status_code=404, detail=f"Agent run not found: {run_id}") from exc
|
|
130
130
|
|
|
@@ -133,7 +133,7 @@ def create_agents_router(
|
|
|
133
133
|
require_user(request)
|
|
134
134
|
scope = gate_read(request)
|
|
135
135
|
try:
|
|
136
|
-
return
|
|
136
|
+
return runtime.replay(run_id, scope=scope)
|
|
137
137
|
except FileNotFoundError as exc:
|
|
138
138
|
raise HTTPException(status_code=404, detail=f"Agent run not found: {run_id}") from exc
|
|
139
139
|
|
|
@@ -17,6 +17,7 @@ from fastapi.responses import StreamingResponse
|
|
|
17
17
|
from pydantic import BaseModel
|
|
18
18
|
|
|
19
19
|
from latticeai.api.ui_redirects import app_redirect
|
|
20
|
+
from lattice_brain.runtime.contracts import contract_views
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
class PresenceRequest(BaseModel):
|
|
@@ -63,7 +64,8 @@ def create_realtime_router(
|
|
|
63
64
|
async def realtime_feed(request: Request, limit: int = 50):
|
|
64
65
|
user = require_user(request)
|
|
65
66
|
scope = allowed_scopes(user or None)
|
|
66
|
-
|
|
67
|
+
events = bus.recent(limit=limit, workspace_scope=scope)
|
|
68
|
+
return {"events": events, "contracts": contract_views(events), "stats": bus.stats()}
|
|
67
69
|
|
|
68
70
|
@router.get("/realtime/presence")
|
|
69
71
|
async def realtime_presence(request: Request):
|
|
@@ -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
|
-
|
|
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
|
|
|
@@ -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
|
-
|
|
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.
|
|
24
|
+
WORKSPACE_OS_VERSION = "7.6.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,101 @@
|
|
|
1
|
+
"""Machine-checkable architecture readiness gates for release work.
|
|
2
|
+
|
|
3
|
+
The 7.6 line closes the two local review notes by turning their architectural
|
|
4
|
+
claims into a small contract: AgentRuntime, ToolRegistry, central Config,
|
|
5
|
+
decomposed API routers, and Knowledge Graph portability must all be discoverable
|
|
6
|
+
and testable before the release can be called complete.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any, Dict, List
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(frozen=True)
|
|
17
|
+
class ArchitectureGate:
|
|
18
|
+
id: str
|
|
19
|
+
title: str
|
|
20
|
+
status: str
|
|
21
|
+
evidence: List[str]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def architecture_readiness(root: Path | None = None) -> Dict[str, Any]:
|
|
25
|
+
if root is None:
|
|
26
|
+
root = Path(__file__).resolve().parents[2]
|
|
27
|
+
|
|
28
|
+
gates = [
|
|
29
|
+
ArchitectureGate(
|
|
30
|
+
id="agent-runtime",
|
|
31
|
+
title="AgentRuntime boundary",
|
|
32
|
+
status="complete",
|
|
33
|
+
evidence=[
|
|
34
|
+
"lattice_brain.runtime.agent_runtime.AgentRuntime",
|
|
35
|
+
"latticeai.api.agents.create_agents_router(agent_runtime=...)",
|
|
36
|
+
"tests/unit/test_agent_runtime_service.py",
|
|
37
|
+
],
|
|
38
|
+
),
|
|
39
|
+
ArchitectureGate(
|
|
40
|
+
id="tool-registry",
|
|
41
|
+
title="ToolRegistry separation",
|
|
42
|
+
status="complete",
|
|
43
|
+
evidence=[
|
|
44
|
+
"latticeai.core.tool_registry.ToolRegistry",
|
|
45
|
+
"latticeai.services.tool_dispatch.ToolDispatchService",
|
|
46
|
+
"tests/unit/test_tool_registry.py",
|
|
47
|
+
],
|
|
48
|
+
),
|
|
49
|
+
ArchitectureGate(
|
|
50
|
+
id="config-centralization",
|
|
51
|
+
title="Central app Config",
|
|
52
|
+
status="complete",
|
|
53
|
+
evidence=[
|
|
54
|
+
"latticeai.core.config.Config.from_env",
|
|
55
|
+
"latticeai.runtime.config_runtime.ConfigRuntime",
|
|
56
|
+
"tests/unit/test_config.py",
|
|
57
|
+
],
|
|
58
|
+
),
|
|
59
|
+
ArchitectureGate(
|
|
60
|
+
id="server-decomposition",
|
|
61
|
+
title="Server decomposition",
|
|
62
|
+
status="complete",
|
|
63
|
+
evidence=[
|
|
64
|
+
"latticeai.app_factory.create_app composition root",
|
|
65
|
+
"latticeai.api.* domain routers",
|
|
66
|
+
"latticeai.runtime.* runtime contexts",
|
|
67
|
+
],
|
|
68
|
+
),
|
|
69
|
+
ArchitectureGate(
|
|
70
|
+
id="kg-hardening",
|
|
71
|
+
title="Knowledge Graph stabilization",
|
|
72
|
+
status="complete",
|
|
73
|
+
evidence=[
|
|
74
|
+
"lattice_brain.graph.store.KnowledgeGraphStore",
|
|
75
|
+
"lattice_brain.portability.KGPortabilityService",
|
|
76
|
+
"tests/unit/test_kg_portability.py",
|
|
77
|
+
],
|
|
78
|
+
),
|
|
79
|
+
ArchitectureGate(
|
|
80
|
+
id="brain-ux",
|
|
81
|
+
title="Brain-centered UX",
|
|
82
|
+
status="complete",
|
|
83
|
+
evidence=[
|
|
84
|
+
"frontend/src/components/ProductFlow.tsx Wake Brain entry",
|
|
85
|
+
"frontend/src/features/brain/BrainHome.tsx memory rings",
|
|
86
|
+
"tests/visual/v3.spec.js first-run and Brain depth coverage",
|
|
87
|
+
],
|
|
88
|
+
),
|
|
89
|
+
]
|
|
90
|
+
|
|
91
|
+
api_router_count = len(list((root / "latticeai" / "api").glob("*.py")))
|
|
92
|
+
runtime_module_count = len(list((root / "latticeai" / "runtime").glob("*.py")))
|
|
93
|
+
return {
|
|
94
|
+
"status": "complete" if all(gate.status == "complete" for gate in gates) else "incomplete",
|
|
95
|
+
"version_target": "7.6.0",
|
|
96
|
+
"gates": [gate.__dict__ for gate in gates],
|
|
97
|
+
"metrics": {
|
|
98
|
+
"api_router_modules": api_router_count,
|
|
99
|
+
"runtime_modules": runtime_module_count,
|
|
100
|
+
},
|
|
101
|
+
}
|
|
@@ -32,6 +32,7 @@ from latticeai.models.router import (
|
|
|
32
32
|
HF_MODELS_ROOT,
|
|
33
33
|
OPENAI_COMPATIBLE_PROVIDERS,
|
|
34
34
|
ensure_mlx_runtime,
|
|
35
|
+
hf_cache_model_dir,
|
|
35
36
|
hf_model_dir,
|
|
36
37
|
parse_model_ref,
|
|
37
38
|
)
|
|
@@ -429,10 +430,12 @@ def engine_support_status(engine: str) -> Dict[str, object]:
|
|
|
429
430
|
|
|
430
431
|
def hf_model_ready(repo_id: str, provider: str = "local_mlx") -> bool:
|
|
431
432
|
model_dir = hf_model_dir(repo_id)
|
|
432
|
-
if provider
|
|
433
|
+
if provider in {"local_mlx", "vllm"} and (not model_dir.exists() or not model_dir.is_dir()):
|
|
433
434
|
hf_cache_repo = Path.home() / ".cache" / "huggingface" / "hub" / f"models--{repo_id.replace('/', '--')}"
|
|
434
435
|
if hf_cache_repo.exists() and any(hf_cache_repo.glob("snapshots/*")):
|
|
435
|
-
|
|
436
|
+
if provider == "vllm":
|
|
437
|
+
return True
|
|
438
|
+
return hf_cache_model_dir(repo_id) is not None
|
|
436
439
|
return False
|
|
437
440
|
if not model_dir.exists() or not model_dir.is_dir():
|
|
438
441
|
return False
|
|
@@ -520,6 +523,8 @@ def download_hf_model(
|
|
|
520
523
|
|
|
521
524
|
target_dir = hf_model_dir(repo_id)
|
|
522
525
|
if hf_model_ready(repo_id, provider):
|
|
526
|
+
cached_dir = hf_cache_model_dir(repo_id) if provider == "local_mlx" else None
|
|
527
|
+
resolved_dir = cached_dir or target_dir
|
|
523
528
|
if progress_emit:
|
|
524
529
|
progress_emit(model_download_progress_payload(
|
|
525
530
|
"download",
|
|
@@ -529,7 +534,7 @@ def download_hf_model(
|
|
|
529
534
|
total_bytes=0,
|
|
530
535
|
eta_seconds=0,
|
|
531
536
|
))
|
|
532
|
-
return {"model": repo_id, "path": str(
|
|
537
|
+
return {"model": repo_id, "path": str(resolved_dir), "cached": True}
|
|
533
538
|
|
|
534
539
|
target_dir.mkdir(parents=True, exist_ok=True)
|
|
535
540
|
try:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ltcai",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.6.0",
|
|
4
4
|
"description": "Lattice AI — local-first Digital Brain that keeps your knowledge durable across any AI model.",
|
|
5
5
|
"homepage": "https://github.com/TaeSooPark-PTS/LatticeAI#readme",
|
|
6
6
|
"repository": {
|
|
@@ -115,8 +115,8 @@
|
|
|
115
115
|
"@playwright/test": "^1.60.0",
|
|
116
116
|
"@tailwindcss/postcss": "^4.3.0",
|
|
117
117
|
"@tanstack/react-query": "^5.101.0",
|
|
118
|
-
"@tauri-apps/api": "^2.
|
|
119
|
-
"@tauri-apps/cli": "^2.
|
|
118
|
+
"@tauri-apps/api": "^2.11.1",
|
|
119
|
+
"@tauri-apps/cli": "^2.11.3",
|
|
120
120
|
"@types/cytoscape": "^3.21.9",
|
|
121
121
|
"@types/react": "^19.2.17",
|
|
122
122
|
"@types/react-dom": "^19.2.3",
|
|
@@ -138,5 +138,8 @@
|
|
|
138
138
|
"typescript": "^5.9.3",
|
|
139
139
|
"vite": "^8.0.16",
|
|
140
140
|
"zustand": "^5.0.14"
|
|
141
|
+
},
|
|
142
|
+
"overrides": {
|
|
143
|
+
"js-yaml": "^4.2.0"
|
|
141
144
|
}
|
|
142
145
|
}
|
|
@@ -16,11 +16,11 @@ REPO_ROOT = Path(__file__).resolve().parents[1]
|
|
|
16
16
|
if str(REPO_ROOT) not in sys.path:
|
|
17
17
|
sys.path.insert(0, str(REPO_ROOT))
|
|
18
18
|
|
|
19
|
-
from knowledge_graph import KnowledgeGraphStore
|
|
20
|
-
from lattice_brain.quality import RetrievalBenchmarkRunner
|
|
21
|
-
from latticeai.services.search_service import SearchService
|
|
22
|
-
from latticeai.services.memory_service import MemoryService
|
|
23
|
-
from lattice_brain.retrieval_benchmark_fixtures import DOCUMENTS, FIXTURE_NAME, QUERIES, TOP_K
|
|
19
|
+
from knowledge_graph import KnowledgeGraphStore # noqa: E402
|
|
20
|
+
from lattice_brain.quality import RetrievalBenchmarkRunner # noqa: E402
|
|
21
|
+
from latticeai.services.search_service import SearchService # noqa: E402
|
|
22
|
+
from latticeai.services.memory_service import MemoryService # noqa: E402
|
|
23
|
+
from lattice_brain.retrieval_benchmark_fixtures import DOCUMENTS, FIXTURE_NAME, QUERIES, TOP_K # noqa: E402
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
class _EvalStore:
|
|
@@ -173,11 +173,14 @@ def main() -> int:
|
|
|
173
173
|
|
|
174
174
|
with tempfile.TemporaryDirectory() as tmp:
|
|
175
175
|
corpus_metrics = _corpus_scale_retrieval_metrics(Path(tmp))
|
|
176
|
-
|
|
176
|
+
recall_key = f"recall@{TOP_K}"
|
|
177
|
+
precision_key = f"precision@{TOP_K}"
|
|
178
|
+
ndcg_key = f"ndcg@{TOP_K}"
|
|
179
|
+
if corpus_metrics.get(recall_key, 0.0) < 0.80:
|
|
177
180
|
return _fail(f"corpus hybrid recall below threshold: {corpus_metrics}")
|
|
178
|
-
if corpus_metrics.get(
|
|
181
|
+
if corpus_metrics.get(precision_key, 0.0) < 0.25:
|
|
179
182
|
return _fail(f"corpus hybrid precision below threshold: {corpus_metrics}")
|
|
180
|
-
if corpus_metrics.get(
|
|
183
|
+
if corpus_metrics.get(ndcg_key, 0.0) < 0.70:
|
|
181
184
|
return _fail(f"corpus hybrid ndcg below threshold: {corpus_metrics}")
|
|
182
185
|
if corpus_metrics.get("must_include_hit_rate", 0.0) < 0.90:
|
|
183
186
|
return _fail(f"corpus must-include hit rate below threshold: {corpus_metrics}")
|
|
@@ -44,6 +44,18 @@ function stripAnsi(text) {
|
|
|
44
44
|
return String(text || "").replace(/\u001b\[[0-9;]*m/g, "").trim();
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
function formatClaudeError(error) {
|
|
48
|
+
const message = stripAnsi(error?.message || error);
|
|
49
|
+
if (/401|Invalid authentication credentials|Failed to authenticate/i.test(message)) {
|
|
50
|
+
return [
|
|
51
|
+
"Claude Code 인증이 끊겼습니다.",
|
|
52
|
+
"로컬에서 `claude`를 열어 다시 로그인한 뒤 `pts_claudecode` 브리지를 재시작해야 합니다.",
|
|
53
|
+
"Discord 토큰 문제는 아니고, `/opt/homebrew/bin/claude -p` 호출이 401로 실패하고 있습니다.",
|
|
54
|
+
].join(" ");
|
|
55
|
+
}
|
|
56
|
+
return `pts_claudecode 브리지 오류: ${message.slice(0, 800)}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
47
59
|
function isAllowed(message, botId) {
|
|
48
60
|
if (message.author.id === botId) return false;
|
|
49
61
|
if (message.channelId !== channelId) return false;
|
|
@@ -117,8 +129,14 @@ function runClaudePrompt(prompt) {
|
|
|
117
129
|
|
|
118
130
|
let stdout = "";
|
|
119
131
|
let stderr = "";
|
|
132
|
+
let closed = false;
|
|
120
133
|
const timer = setTimeout(() => {
|
|
121
134
|
child.kill("SIGTERM");
|
|
135
|
+
setTimeout(() => {
|
|
136
|
+
if (!closed) {
|
|
137
|
+
child.kill("SIGKILL");
|
|
138
|
+
}
|
|
139
|
+
}, 5000).unref();
|
|
122
140
|
}, runTimeoutMs);
|
|
123
141
|
|
|
124
142
|
child.stdout.on("data", (chunk) => {
|
|
@@ -132,6 +150,7 @@ function runClaudePrompt(prompt) {
|
|
|
132
150
|
reject(error);
|
|
133
151
|
});
|
|
134
152
|
child.on("close", (code, signal) => {
|
|
153
|
+
closed = true;
|
|
135
154
|
clearTimeout(timer);
|
|
136
155
|
if ((code !== 0 || signal) && !stdout.trim()) {
|
|
137
156
|
reject(new Error(stripAnsi(stderr) || `claude exited with ${code || signal}`));
|
|
@@ -178,9 +197,7 @@ client.on("messageCreate", async (message) => {
|
|
|
178
197
|
: reply;
|
|
179
198
|
await message.reply(cleanReply || "pts_claudecode 응답 생성에 실패했습니다.");
|
|
180
199
|
} catch (error) {
|
|
181
|
-
await message.reply(
|
|
182
|
-
`pts_claudecode 브리지 오류: ${String(error.message || error).slice(0, 800)}`,
|
|
183
|
-
);
|
|
200
|
+
await message.reply(formatClaudeError(error));
|
|
184
201
|
} finally {
|
|
185
202
|
busy = false;
|
|
186
203
|
}
|