ltcai 9.9.3 → 9.9.5
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 +39 -38
- package/docs/CHANGELOG.md +100 -0
- package/docs/COMMUNITY_AND_PLUGINS.md +1 -1
- package/docs/DEVELOPMENT.md +1 -1
- package/docs/ONBOARDING.md +1 -1
- package/docs/OPERATIONS.md +1 -1
- package/docs/SURFACE_PARITY.md +53 -0
- package/docs/TRUST_MODEL.md +1 -1
- package/docs/WHY_LATTICE.md +1 -1
- package/docs/kg-schema.md +1 -1
- package/lattice_brain/__init__.py +1 -1
- package/lattice_brain/graph/_kg_common.py +335 -0
- package/lattice_brain/graph/discovery_index.py +8 -1
- package/lattice_brain/graph/ingest.py +43 -9
- package/lattice_brain/graph/projection.py +221 -37
- package/lattice_brain/graph/rerank.py +160 -0
- package/lattice_brain/graph/retrieval.py +90 -455
- package/lattice_brain/graph/retrieval_policy.py +174 -0
- package/lattice_brain/graph/retrieval_reads.py +459 -0
- package/lattice_brain/graph/retrieval_vector.py +122 -2
- package/lattice_brain/graph/store.py +2 -0
- package/lattice_brain/portability.py +41 -12
- package/lattice_brain/quality.py +26 -6
- package/lattice_brain/runtime/multi_agent.py +1 -1
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/chat.py +6 -0
- package/latticeai/api/chat_agent_http.py +262 -52
- package/latticeai/api/chat_contracts.py +1 -1
- package/latticeai/api/chat_intents.py +37 -18
- package/latticeai/api/chat_stream.py +76 -2
- package/latticeai/api/knowledge_graph.py +35 -1
- package/latticeai/core/agent.py +420 -27
- package/latticeai/core/enterprise.py +5 -0
- package/latticeai/core/file_generation.py +130 -5
- package/latticeai/core/legacy_compatibility.py +1 -1
- package/latticeai/core/marketplace.py +1 -1
- package/latticeai/core/run_store.py +245 -0
- package/latticeai/core/workspace_os.py +1 -1
- package/latticeai/integrations/telegram_bot.py +82 -23
- package/latticeai/models/router.py +25 -12
- package/latticeai/services/architecture_readiness.py +1 -1
- package/latticeai/services/command_center.py +90 -1
- package/latticeai/services/folder_watch.py +5 -0
- package/latticeai/services/funnel_metrics.py +9 -1
- package/latticeai/services/product_readiness.py +1 -1
- package/latticeai/services/search_service.py +43 -13
- package/latticeai/services/tool_dispatch.py +56 -0
- package/package.json +2 -1
- package/scripts/bench_agent_smoke.py +410 -0
- package/scripts/check_current_release_docs.mjs +1 -1
- package/scripts/funnel_soft_gate.py +192 -0
- package/scripts/run_sidecar_e2e.mjs +159 -0
- package/src-tauri/Cargo.lock +1 -1
- package/src-tauri/Cargo.toml +1 -1
- package/src-tauri/tauri.conf.json +1 -1
- package/static/app/asset-manifest.json +29 -28
- package/static/app/assets/Act-DmxfbqBp.js +1 -0
- package/static/app/assets/{Brain-DEY9jLVt.js → Brain-DK_zKCda.js} +2 -2
- package/static/app/assets/BrainHome-CCE_0hni.js +2 -0
- package/static/app/assets/BrainSignals-r_J68_zh.js +1 -0
- package/static/app/assets/{Capture-CVZ09QXi.js → Capture-CvxwzpJZ.js} +1 -1
- package/static/app/assets/{CommandPalette-DepwOQFv.js → CommandPalette-CVLiFIQC.js} +1 -1
- package/static/app/assets/{Library-Bp0n-HlW.js → Library-4As9VUvy.js} +1 -1
- package/static/app/assets/{LivingBrain-DxP4efJF.js → LivingBrain-BVWk58MW.js} +1 -1
- package/static/app/assets/{ProductFlow-DRbm7NEq.js → ProductFlow-B-Tz0Um4.js} +1 -1
- package/static/app/assets/{ReviewCard-C4HAO7A3.js → ReviewCard-Va3fPQuj.js} +1 -1
- package/static/app/assets/{System-ByQcmJW-.js → System-DSR2zKWd.js} +1 -1
- package/static/app/assets/{bot-BNDyZLR7.js → bot-VmP0kkeA.js} +1 -1
- package/static/app/assets/circle-pause-T9POo4qy.js +1 -0
- package/static/app/assets/{circle-play-BkhdcHgd.js → circle-play-Bz5iTD0p.js} +1 -1
- package/static/app/assets/{cpu-C6jjYm6i.js → cpu-CJhjRtNq.js} +1 -1
- package/static/app/assets/{folder-open-DjGIvDBQ.js → folder-open-B2K_22VI.js} +1 -1
- package/static/app/assets/{hard-drive-BlSbwSaT.js → hard-drive-CjJtYqHf.js} +1 -1
- package/static/app/assets/{index-Bge3DXW7.css → index-7FAfYm4v.css} +1 -1
- package/static/app/assets/{index-CHu7cgj3.js → index-CEu0Wqjl.js} +3 -3
- package/static/app/assets/{input-DVDI0YR3.js → input-CSi6OBJ9.js} +1 -1
- package/static/app/assets/{navigation-BddhEWA0.js → navigation-BPQqEQwr.js} +1 -1
- package/static/app/assets/{network-pYQt5oBu.js → network-Ck1nqc-v.js} +1 -1
- package/static/app/assets/{primitives-D7gCdEvS.js → primitives-B2f9N7Dh.js} +1 -1
- package/static/app/assets/{shield-alert-K9RKGQeg.js → shield-alert-yA_Y8lOB.js} +1 -1
- package/static/app/assets/{textarea-sqQmoBKL.js → textarea-C4wM0D-Z.js} +1 -1
- package/static/app/assets/{useFocusTrap-7EV9dFP2.js → useFocusTrap-CsnIhsDJ.js} +1 -1
- package/static/app/assets/utils-BqoznzGP.js +7 -0
- package/static/app/index.html +4 -4
- package/static/sw.js +1 -1
- package/static/app/assets/Act-DmdruVKV.js +0 -1
- package/static/app/assets/BrainHome-CeNaxjP1.js +0 -2
- package/static/app/assets/BrainSignals-CStjIqYi.js +0 -1
- package/static/app/assets/utils-uQYKXNeq.js +0 -7
|
@@ -13,6 +13,78 @@ class KnowledgeGraphVectorMixin:
|
|
|
13
13
|
retrieval/write helpers (e.g. self._vector_text_for_node) through the MRO.
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
|
+
# ── embedder fingerprint (review Wave 2.2 — stale_embedder) ──────────────
|
|
17
|
+
# vector_search filters on the CURRENT model/dim, so swapping the embedder
|
|
18
|
+
# silently yields zero vector rows. The fingerprint persisted in graph_meta
|
|
19
|
+
# records which embedder actually built the index; a mismatch is surfaced
|
|
20
|
+
# as the honest ``stale_embedder`` signal instead of a silent degradation.
|
|
21
|
+
|
|
22
|
+
_EMBEDDER_FINGERPRINT_KEY = "embedder_fingerprint"
|
|
23
|
+
|
|
24
|
+
def _embedder_fingerprint_record(
|
|
25
|
+
self, conn: sqlite3.Connection
|
|
26
|
+
) -> Optional[Dict[str, Any]]:
|
|
27
|
+
"""Read the recorded embedder fingerprint from graph_meta (or None)."""
|
|
28
|
+
row = conn.execute(
|
|
29
|
+
"SELECT value FROM graph_meta WHERE key=?",
|
|
30
|
+
(self._EMBEDDER_FINGERPRINT_KEY,),
|
|
31
|
+
).fetchone()
|
|
32
|
+
if not row:
|
|
33
|
+
return None
|
|
34
|
+
payload = _safe_loads(row["value"])
|
|
35
|
+
if not isinstance(payload, dict) or not payload.get("model_id"):
|
|
36
|
+
return None
|
|
37
|
+
try:
|
|
38
|
+
dim = int(payload.get("dim") or 0)
|
|
39
|
+
except (TypeError, ValueError):
|
|
40
|
+
dim = 0
|
|
41
|
+
return {"model_id": str(payload["model_id"]), "dim": dim}
|
|
42
|
+
|
|
43
|
+
def _write_embedder_fingerprint(self, conn: sqlite3.Connection) -> Dict[str, Any]:
|
|
44
|
+
"""Persist the CURRENT embedder identity (same transaction as caller)."""
|
|
45
|
+
fingerprint = {
|
|
46
|
+
"model_id": self._embedding_model.model_id,
|
|
47
|
+
"dim": int(self._embedding_model.dim),
|
|
48
|
+
}
|
|
49
|
+
conn.execute(
|
|
50
|
+
"INSERT OR REPLACE INTO graph_meta(key, value) VALUES (?, ?)",
|
|
51
|
+
(self._EMBEDDER_FINGERPRINT_KEY, _json(fingerprint)),
|
|
52
|
+
)
|
|
53
|
+
return fingerprint
|
|
54
|
+
|
|
55
|
+
def record_embedder_fingerprint(self) -> Dict[str, Any]:
|
|
56
|
+
"""Record the current embedder (model_id + dim) as the index builder."""
|
|
57
|
+
with self._connect() as conn:
|
|
58
|
+
return self._write_embedder_fingerprint(conn)
|
|
59
|
+
|
|
60
|
+
def embedder_fingerprint_status(self) -> Dict[str, Any]:
|
|
61
|
+
"""Compare the current embedder against the recorded index fingerprint.
|
|
62
|
+
|
|
63
|
+
Returns ``{"current": {model_id, dim}, "recorded": {...} | None,
|
|
64
|
+
"stale_embedder": bool}``. ``stale_embedder`` is True only when a
|
|
65
|
+
fingerprint was recorded AND it differs from the current embedder —
|
|
66
|
+
an unrecorded index (legacy DBs, nothing indexed yet) is honestly
|
|
67
|
+
"unknown", never reported stale. Never raises.
|
|
68
|
+
"""
|
|
69
|
+
current = {
|
|
70
|
+
"model_id": self._embedding_model.model_id,
|
|
71
|
+
"dim": int(self._embedding_model.dim),
|
|
72
|
+
}
|
|
73
|
+
recorded: Optional[Dict[str, Any]] = None
|
|
74
|
+
try:
|
|
75
|
+
with self._connect() as conn:
|
|
76
|
+
recorded = self._embedder_fingerprint_record(conn)
|
|
77
|
+
except Exception: # noqa: BLE001 — status must degrade, never raise
|
|
78
|
+
recorded = None
|
|
79
|
+
stale = bool(
|
|
80
|
+
recorded is not None
|
|
81
|
+
and (
|
|
82
|
+
recorded.get("model_id") != current["model_id"]
|
|
83
|
+
or recorded.get("dim") != current["dim"]
|
|
84
|
+
)
|
|
85
|
+
)
|
|
86
|
+
return {"current": current, "recorded": recorded, "stale_embedder": stale}
|
|
87
|
+
|
|
16
88
|
def _iter_vector_source_items(
|
|
17
89
|
self,
|
|
18
90
|
conn: sqlite3.Connection,
|
|
@@ -153,6 +225,11 @@ class KnowledgeGraphVectorMixin:
|
|
|
153
225
|
indexed += 1
|
|
154
226
|
else:
|
|
155
227
|
skipped += 1
|
|
228
|
+
if indexed and self._embedder_fingerprint_record(conn) is None:
|
|
229
|
+
# First successful vector write establishes the fingerprint;
|
|
230
|
+
# later incremental writes never overwrite it (only a full
|
|
231
|
+
# rebuild may flip it after an embedder swap).
|
|
232
|
+
self._write_embedder_fingerprint(conn)
|
|
156
233
|
summary.update(
|
|
157
234
|
{
|
|
158
235
|
"items_total": len(items),
|
|
@@ -254,6 +331,10 @@ class KnowledgeGraphVectorMixin:
|
|
|
254
331
|
op_id,
|
|
255
332
|
),
|
|
256
333
|
)
|
|
334
|
+
# A successful rebuild (re)establishes which embedder built
|
|
335
|
+
# the index — this is the only path that may flip a recorded
|
|
336
|
+
# fingerprint after an embedder swap.
|
|
337
|
+
self._write_embedder_fingerprint(conn)
|
|
257
338
|
return {
|
|
258
339
|
"status": "completed",
|
|
259
340
|
"operation_id": op_id,
|
|
@@ -401,8 +482,10 @@ class KnowledgeGraphVectorMixin:
|
|
|
401
482
|
"within_target": float(duration_ms) <= 10_000,
|
|
402
483
|
}
|
|
403
484
|
)
|
|
485
|
+
embedder_status = self.embedder_fingerprint_status()
|
|
404
486
|
return {
|
|
405
487
|
"status": "ready" if pending == 0 else "needs_reindex",
|
|
488
|
+
"embedder": embedder_status,
|
|
406
489
|
"storage": {
|
|
407
490
|
"db_path": str(self.db_path),
|
|
408
491
|
"backend": "sqlite",
|
|
@@ -449,7 +532,11 @@ class KnowledgeGraphVectorMixin:
|
|
|
449
532
|
"backlog_reasons": backlog_reasons,
|
|
450
533
|
"backlog_samples": backlog_samples,
|
|
451
534
|
"incremental_reindex_recommended": pending > 0,
|
|
452
|
-
|
|
535
|
+
# A stale embedder means every old-model row must be re-embedded;
|
|
536
|
+
# only a full rebuild (which re-records the fingerprint) heals it.
|
|
537
|
+
"full_rebuild_recommended": bool(
|
|
538
|
+
orphaned_items > 0 or embedder_status["stale_embedder"]
|
|
539
|
+
),
|
|
453
540
|
"latency_budget": latency_budget,
|
|
454
541
|
},
|
|
455
542
|
"operations": [
|
|
@@ -475,7 +562,11 @@ class KnowledgeGraphVectorMixin:
|
|
|
475
562
|
|
|
476
563
|
Reduces :meth:`index_status` (``pending = missing + stale``) to the
|
|
477
564
|
fixed contract ``{"status", "pending_items", "total_items", "detail"}``
|
|
478
|
-
with ``status`` in ``ready`` / ``pending`` / ``
|
|
565
|
+
with ``status`` in ``ready`` / ``pending`` / ``stale_embedder`` /
|
|
566
|
+
``unavailable``. ``stale_embedder`` (review Wave 2.2) is reported only
|
|
567
|
+
when the recorded embedder fingerprint differs from the current
|
|
568
|
+
embedder AND rows indexed under the old model still exist — the index
|
|
569
|
+
needs a full rebuild, not an incremental sync.
|
|
479
570
|
|
|
480
571
|
Never raises: environments where the embedding provider or index
|
|
481
572
|
storage cannot be used report ``"unavailable"`` with the cause in
|
|
@@ -492,6 +583,35 @@ class KnowledgeGraphVectorMixin:
|
|
|
492
583
|
}
|
|
493
584
|
pending = int(status.get("pending_items") or 0)
|
|
494
585
|
total = int(status.get("source_items") or 0)
|
|
586
|
+
embedder = status.get("embedder") or {}
|
|
587
|
+
if embedder.get("stale_embedder"):
|
|
588
|
+
old_model_rows = 0
|
|
589
|
+
try:
|
|
590
|
+
with self._connect() as conn:
|
|
591
|
+
old_model_rows = int(
|
|
592
|
+
conn.execute(
|
|
593
|
+
"SELECT COUNT(*) AS c FROM vector_embeddings "
|
|
594
|
+
"WHERE embedding_model<>? OR embedding_dim<>?",
|
|
595
|
+
(
|
|
596
|
+
self._embedding_model.model_id,
|
|
597
|
+
int(self._embedding_model.dim),
|
|
598
|
+
),
|
|
599
|
+
).fetchone()["c"]
|
|
600
|
+
)
|
|
601
|
+
except Exception: # noqa: BLE001 — keep the existing statuses on failure
|
|
602
|
+
old_model_rows = 0
|
|
603
|
+
if old_model_rows > 0:
|
|
604
|
+
recorded = embedder.get("recorded") or {}
|
|
605
|
+
return {
|
|
606
|
+
"status": "stale_embedder",
|
|
607
|
+
"pending_items": pending,
|
|
608
|
+
"total_items": total,
|
|
609
|
+
"detail": (
|
|
610
|
+
f"embedding model changed ({recorded.get('model_id')} → "
|
|
611
|
+
f"{self._embedding_model.model_id}); {old_model_rows} indexed "
|
|
612
|
+
"rows still use the previous model — run a full vector index rebuild"
|
|
613
|
+
),
|
|
614
|
+
}
|
|
495
615
|
if pending > 0:
|
|
496
616
|
return {
|
|
497
617
|
"status": "pending",
|
|
@@ -11,6 +11,7 @@ from .projection import KnowledgeGraphProjectionMixin
|
|
|
11
11
|
from .provenance import KnowledgeGraphProvenanceMixin
|
|
12
12
|
from .retrieval import KnowledgeGraphRetrievalMixin
|
|
13
13
|
from .retrieval_docgen import KnowledgeGraphDocGenMixin
|
|
14
|
+
from .retrieval_reads import KnowledgeGraphReadsMixin
|
|
14
15
|
from .retrieval_vector import KnowledgeGraphVectorMixin
|
|
15
16
|
from .write_master import KnowledgeGraphWriteMixin
|
|
16
17
|
|
|
@@ -24,6 +25,7 @@ class KnowledgeGraphStore(
|
|
|
24
25
|
KnowledgeGraphProvenanceMixin,
|
|
25
26
|
KnowledgeGraphDocumentsMixin,
|
|
26
27
|
KnowledgeGraphRetrievalMixin,
|
|
28
|
+
KnowledgeGraphReadsMixin,
|
|
27
29
|
KnowledgeGraphVectorMixin,
|
|
28
30
|
KnowledgeGraphDocGenMixin,
|
|
29
31
|
):
|
|
@@ -14,6 +14,7 @@ any cloud service. Two complementary mechanisms, both fully local:
|
|
|
14
14
|
from __future__ import annotations
|
|
15
15
|
|
|
16
16
|
import json
|
|
17
|
+
import logging
|
|
17
18
|
import os
|
|
18
19
|
import shutil
|
|
19
20
|
import sqlite3
|
|
@@ -74,9 +75,12 @@ def _checkpoint_sqlite(db_path: Path) -> None:
|
|
|
74
75
|
|
|
75
76
|
|
|
76
77
|
def _restore_sibling(path: Path, backup: Path) -> None:
|
|
77
|
-
|
|
78
|
+
try:
|
|
78
79
|
shutil.copy2(backup, path)
|
|
79
|
-
|
|
80
|
+
except FileNotFoundError:
|
|
81
|
+
# The backup vanished (or never existed — transient -wal/-shm): the
|
|
82
|
+
# honest reconstruction is "no such sibling", never a crash that
|
|
83
|
+
# masks the error that triggered the rollback.
|
|
80
84
|
path.unlink(missing_ok=True)
|
|
81
85
|
|
|
82
86
|
|
|
@@ -101,10 +105,22 @@ def _replace_sqlite_atomically(src: Path, dest: Path, backup_dir: Path) -> None:
|
|
|
101
105
|
sibling.unlink(missing_ok=True)
|
|
102
106
|
os.replace(tmp, dest)
|
|
103
107
|
except Exception:
|
|
104
|
-
|
|
105
|
-
|
|
108
|
+
# Recovery I/O must never replace the swap error being reported —
|
|
109
|
+
# an exception raised here would mask it (the CI-observed
|
|
110
|
+
# "[Errno 2]" over the real failure). Best-effort restore, then
|
|
111
|
+
# always re-raise the original.
|
|
112
|
+
try:
|
|
113
|
+
tmp.unlink(missing_ok=True)
|
|
114
|
+
except OSError as cleanup_exc:
|
|
115
|
+
logging.warning("restore tmp cleanup failed: %s", cleanup_exc)
|
|
106
116
|
for sibling in _sqlite_siblings(dest):
|
|
107
|
-
|
|
117
|
+
try:
|
|
118
|
+
_restore_sibling(sibling, backups.get(sibling, backup_dir / sibling.name))
|
|
119
|
+
except OSError as rollback_exc:
|
|
120
|
+
logging.warning(
|
|
121
|
+
"restore sibling rollback incomplete for %s: %s",
|
|
122
|
+
sibling, rollback_exc,
|
|
123
|
+
)
|
|
108
124
|
raise
|
|
109
125
|
|
|
110
126
|
|
|
@@ -127,12 +143,17 @@ def _replace_tree_with_backup(src: Optional[Path], dest: Path, backup_dir: Path)
|
|
|
127
143
|
shutil.rmtree(dest)
|
|
128
144
|
os.replace(staged, dest)
|
|
129
145
|
except Exception:
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
146
|
+
# Same masking guard as the sqlite swap: rollback I/O is
|
|
147
|
+
# best-effort and the original failure always propagates.
|
|
148
|
+
try:
|
|
149
|
+
if staged.exists():
|
|
150
|
+
shutil.rmtree(staged)
|
|
151
|
+
if dest.exists():
|
|
152
|
+
shutil.rmtree(dest)
|
|
153
|
+
if backup.exists():
|
|
154
|
+
shutil.copytree(backup, dest)
|
|
155
|
+
except OSError as rollback_exc:
|
|
156
|
+
logging.warning("blob tree rollback incomplete: %s", rollback_exc)
|
|
136
157
|
raise
|
|
137
158
|
|
|
138
159
|
|
|
@@ -307,7 +328,15 @@ class KGPortabilityService:
|
|
|
307
328
|
blob_src = tmp / "blobs"
|
|
308
329
|
_replace_tree_with_backup(blob_src if blob_src.exists() else None, blob_dest, backup_dir)
|
|
309
330
|
except Exception:
|
|
310
|
-
|
|
331
|
+
# The rollback is best-effort recovery; an I/O slip inside
|
|
332
|
+
# it must never mask the restore failure being reported.
|
|
333
|
+
try:
|
|
334
|
+
_rollback_sqlite_from_backup(db_dest, backup_dir)
|
|
335
|
+
except OSError as rollback_exc:
|
|
336
|
+
logging.warning(
|
|
337
|
+
"pre-restore rollback incomplete (backup kept at %s): %s",
|
|
338
|
+
backup_dir, rollback_exc,
|
|
339
|
+
)
|
|
311
340
|
raise
|
|
312
341
|
stats = self._kg.stats()
|
|
313
342
|
return {
|
package/lattice_brain/quality.py
CHANGED
|
@@ -131,13 +131,33 @@ class HybridFusion:
|
|
|
131
131
|
return sorted(results, key=lambda x: x["fused_score"], reverse=True)
|
|
132
132
|
|
|
133
133
|
class RerankerInterface:
|
|
134
|
-
"""Pluggable reranker interface
|
|
134
|
+
"""Pluggable reranker interface.
|
|
135
|
+
|
|
136
|
+
Default is identity (fused score). When
|
|
137
|
+
``LATTICEAI_CROSS_ENCODER_RERANK=1`` and a CrossEncoder is importable,
|
|
138
|
+
delegates to :func:`lattice_brain.graph.rerank.rerank_matches`. Failures
|
|
139
|
+
never raise — always returns a ranked list.
|
|
140
|
+
"""
|
|
141
|
+
|
|
135
142
|
def rerank(self, query: str, candidates: List[Dict], top_k: int = 5) -> List[Dict]:
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
143
|
+
try:
|
|
144
|
+
from lattice_brain.graph.rerank import rerank_matches
|
|
145
|
+
|
|
146
|
+
# Map fused_score → score so the shared helper ranks consistently.
|
|
147
|
+
prepared = []
|
|
148
|
+
for c in candidates:
|
|
149
|
+
item = dict(c)
|
|
150
|
+
if "score" not in item:
|
|
151
|
+
item["score"] = item.get("fused_score", 0.0)
|
|
152
|
+
prepared.append(item)
|
|
153
|
+
result = rerank_matches(query, prepared, top_k=top_k)
|
|
154
|
+
return list(result.get("matches") or [])
|
|
155
|
+
except Exception: # noqa: BLE001 — quality path must never raise
|
|
156
|
+
for c in candidates:
|
|
157
|
+
c["rerank_score"] = c.get("fused_score", 0.0)
|
|
158
|
+
return sorted(
|
|
159
|
+
candidates, key=lambda x: x.get("rerank_score", 0), reverse=True
|
|
160
|
+
)[:top_k]
|
|
141
161
|
|
|
142
162
|
# -----------------------------
|
|
143
163
|
# 3. Memory Candidate Extraction / Scoring / Dedupe / Merge / Conflict / Retention
|
|
@@ -48,7 +48,7 @@ from .contracts import multi_agent_contract
|
|
|
48
48
|
from ..utils import now_iso as _now
|
|
49
49
|
|
|
50
50
|
|
|
51
|
-
MULTI_AGENT_VERSION = "9.9.
|
|
51
|
+
MULTI_AGENT_VERSION = "9.9.5"
|
|
52
52
|
|
|
53
53
|
AGENT_ROLES = ("researcher", "planner", "executor", "reviewer", "release")
|
|
54
54
|
CORE_PIPELINE = ("planner", "executor", "reviewer")
|
package/latticeai/__init__.py
CHANGED
package/latticeai/api/chat.py
CHANGED
|
@@ -45,6 +45,7 @@ from latticeai.api.chat_helpers import (
|
|
|
45
45
|
workspace_scope_from_request,
|
|
46
46
|
)
|
|
47
47
|
from latticeai.api.chat_history import HistoryRouteDependencies, register_history_routes
|
|
48
|
+
from latticeai.core.run_store import AgentRunStore
|
|
48
49
|
from latticeai.api.chat_intents import ChatIntentController
|
|
49
50
|
from latticeai.api.chat_stream import stream_chat
|
|
50
51
|
from latticeai.services.app_context import AppContext
|
|
@@ -191,6 +192,11 @@ def create_chat_router(context: AppContext) -> APIRouter:
|
|
|
191
192
|
agent_root=AGENT_ROOT,
|
|
192
193
|
ensure_agent_root=ensure_agent_root,
|
|
193
194
|
funnel_metrics=context.funnel_metrics,
|
|
195
|
+
run_store=(
|
|
196
|
+
AgentRunStore(Path(context.data_dir) / "agent_runs")
|
|
197
|
+
if context.data_dir
|
|
198
|
+
else None
|
|
199
|
+
),
|
|
194
200
|
)
|
|
195
201
|
intent_controller = ChatIntentController(
|
|
196
202
|
model_router=model_router,
|