ltcai 9.9.4 → 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.
Files changed (64) hide show
  1. package/README.md +38 -42
  2. package/docs/CHANGELOG.md +35 -0
  3. package/docs/COMMUNITY_AND_PLUGINS.md +1 -1
  4. package/docs/DEVELOPMENT.md +1 -1
  5. package/docs/ONBOARDING.md +1 -1
  6. package/docs/OPERATIONS.md +1 -1
  7. package/docs/SURFACE_PARITY.md +10 -4
  8. package/docs/TRUST_MODEL.md +1 -1
  9. package/docs/WHY_LATTICE.md +1 -1
  10. package/docs/kg-schema.md +1 -1
  11. package/lattice_brain/__init__.py +1 -1
  12. package/lattice_brain/graph/rerank.py +160 -0
  13. package/lattice_brain/graph/retrieval.py +28 -442
  14. package/lattice_brain/graph/retrieval_reads.py +459 -0
  15. package/lattice_brain/graph/store.py +2 -0
  16. package/lattice_brain/quality.py +26 -6
  17. package/lattice_brain/runtime/multi_agent.py +1 -1
  18. package/latticeai/__init__.py +1 -1
  19. package/latticeai/api/chat_agent_http.py +87 -46
  20. package/latticeai/api/chat_contracts.py +1 -1
  21. package/latticeai/core/agent.py +156 -18
  22. package/latticeai/core/legacy_compatibility.py +1 -1
  23. package/latticeai/core/marketplace.py +1 -1
  24. package/latticeai/core/run_store.py +2 -0
  25. package/latticeai/core/workspace_os.py +1 -1
  26. package/latticeai/integrations/telegram_bot.py +82 -23
  27. package/latticeai/services/architecture_readiness.py +1 -1
  28. package/latticeai/services/product_readiness.py +1 -1
  29. package/latticeai/services/tool_dispatch.py +56 -0
  30. package/package.json +2 -1
  31. package/scripts/check_current_release_docs.mjs +1 -1
  32. package/scripts/run_sidecar_e2e.mjs +159 -0
  33. package/src-tauri/Cargo.lock +1 -1
  34. package/src-tauri/Cargo.toml +1 -1
  35. package/src-tauri/tauri.conf.json +1 -1
  36. package/static/app/asset-manifest.json +28 -28
  37. package/static/app/assets/{Act-BCmTU0E2.js → Act-DmxfbqBp.js} +1 -1
  38. package/static/app/assets/{Brain-CTnjox7w.js → Brain-DK_zKCda.js} +1 -1
  39. package/static/app/assets/{BrainHome-BJ3sFNX0.js → BrainHome-CCE_0hni.js} +1 -1
  40. package/static/app/assets/{BrainSignals-C52lwZVD.js → BrainSignals-r_J68_zh.js} +1 -1
  41. package/static/app/assets/{Capture-B6vBhFa3.js → Capture-CvxwzpJZ.js} +1 -1
  42. package/static/app/assets/{CommandPalette-90u9FWFH.js → CommandPalette-CVLiFIQC.js} +1 -1
  43. package/static/app/assets/{Library-pKCK0_tk.js → Library-4As9VUvy.js} +1 -1
  44. package/static/app/assets/{LivingBrain-Jlf2wFqI.js → LivingBrain-BVWk58MW.js} +1 -1
  45. package/static/app/assets/{ProductFlow-yg1fKP1P.js → ProductFlow-B-Tz0Um4.js} +1 -1
  46. package/static/app/assets/{ReviewCard-DWvD7n9h.js → ReviewCard-Va3fPQuj.js} +1 -1
  47. package/static/app/assets/{System-BAEuHqNY.js → System-DSR2zKWd.js} +1 -1
  48. package/static/app/assets/{bot-nB_buEZD.js → bot-VmP0kkeA.js} +1 -1
  49. package/static/app/assets/{circle-pause-DLNw6Ucp.js → circle-pause-T9POo4qy.js} +1 -1
  50. package/static/app/assets/{circle-play-B-IsFL1y.js → circle-play-Bz5iTD0p.js} +1 -1
  51. package/static/app/assets/{cpu-CEPBHaBl.js → cpu-CJhjRtNq.js} +1 -1
  52. package/static/app/assets/{folder-open-hmN0N9cX.js → folder-open-B2K_22VI.js} +1 -1
  53. package/static/app/assets/{hard-drive-CBV_B_Yd.js → hard-drive-CjJtYqHf.js} +1 -1
  54. package/static/app/assets/{index-DrmOCySv.js → index-CEu0Wqjl.js} +3 -3
  55. package/static/app/assets/{input-gtVCg-ll.js → input-CSi6OBJ9.js} +1 -1
  56. package/static/app/assets/{navigation-Bot0hvuv.js → navigation-BPQqEQwr.js} +1 -1
  57. package/static/app/assets/{network-jE42eKfT.js → network-Ck1nqc-v.js} +1 -1
  58. package/static/app/assets/{primitives-CX2Komon.js → primitives-B2f9N7Dh.js} +1 -1
  59. package/static/app/assets/{shield-alert-BftATuAA.js → shield-alert-yA_Y8lOB.js} +1 -1
  60. package/static/app/assets/{textarea-CiMJfOSI.js → textarea-C4wM0D-Z.js} +1 -1
  61. package/static/app/assets/{useFocusTrap-B7RPGfFy.js → useFocusTrap-CsnIhsDJ.js} +1 -1
  62. package/static/app/assets/{utils-SJUNVOj5.js → utils-BqoznzGP.js} +1 -1
  63. package/static/app/index.html +3 -3
  64. package/static/sw.js +1 -1
package/README.md CHANGED
@@ -11,7 +11,7 @@
11
11
  [![CI Status](https://github.com/TaeSooPark-PTS/LatticeAI/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/TaeSooPark-PTS/LatticeAI/actions/workflows/ci.yml)
12
12
  [![License](https://img.shields.io/badge/license-MIT-green)](LICENSE)
13
13
 
14
- ![v9.9.4 Living Brain walkthrough](output/release/v9.9.4/gifs/v9.9.4-living-brain-walkthrough.gif)
14
+ ![v9.9.5 Living Brain walkthrough](output/release/v9.9.5/gifs/v9.9.5-living-brain-walkthrough.gif)
15
15
 
16
16
  Chat, files, folders, notes, and web pages all flow into one durable knowledge
17
17
  graph on your computer. Any model — local MLX or cloud — can speak with that
@@ -24,9 +24,9 @@ memory. Nothing leaves your machine without explicit consent.
24
24
 
25
25
  | | |
26
26
  | --- | --- |
27
- | **Chat with a Brain that remembers** — every conversation grows durable, source-linked memory ![Brain Chat](output/release/v9.9.4/screenshots/04-brain-chat-home.png) | **See how knowledge connects** — a real relationship graph, not a file list ![Memory Graph](output/release/v9.9.4/screenshots/05-memory-graph.png) |
28
- | **Capture anything** — files, whole folders, notes, screenshots, web pages ![Capture](output/release/v9.9.4/screenshots/06-capture.png) | **Automate with review** — agent changes become proposals you approve first ![Review Center](output/release/v9.9.4/screenshots/12-review-center.png) |
29
- | **Pick a model in one click** — recommended local models for your hardware ![Recommended Models](output/release/v9.9.4/screenshots/02-recommended-models.png) | **Stay in control** — audit, roles, retention in a separate admin surface ![Admin Console](output/release/v9.9.4/screenshots/10-admin-console.png) |
27
+ | **Chat with a Brain that remembers** — every conversation grows durable, source-linked memory ![Brain Chat](output/release/v9.9.5/screenshots/04-brain-chat-home.png) | **See how knowledge connects** — a real relationship graph, not a file list ![Memory Graph](output/release/v9.9.5/screenshots/05-memory-graph.png) |
28
+ | **Capture anything** — files, whole folders, notes, screenshots, web pages ![Capture](output/release/v9.9.5/screenshots/06-capture.png) | **Automate with review** — agent changes become proposals you approve first ![Review Center](output/release/v9.9.5/screenshots/12-review-center.png) |
29
+ | **Pick a model in one click** — recommended local models for your hardware ![Recommended Models](output/release/v9.9.5/screenshots/02-recommended-models.png) | **Stay in control** — audit, roles, retention in a separate admin surface ![Admin Console](output/release/v9.9.5/screenshots/10-admin-console.png) |
30
30
 
31
31
  ## Why Lattice AI
32
32
 
@@ -57,52 +57,47 @@ First-run flow — wake the Brain, pick the owner, load a recommended model:
57
57
 
58
58
  | | | |
59
59
  | --- | --- | --- |
60
- | ![Login](output/release/v9.9.4/screenshots/01-login.png) | ![Model install](output/release/v9.9.4/screenshots/03-install-load-progress.png) | ![Model library](output/release/v9.9.4/screenshots/07-model-library.png) |
60
+ | ![Login](output/release/v9.9.5/screenshots/01-login.png) | ![Model install](output/release/v9.9.5/screenshots/03-install-load-progress.png) | ![Model library](output/release/v9.9.5/screenshots/07-model-library.png) |
61
61
 
62
62
  Screenshot index and capture notes:
63
- [output/release/v9.9.4/SCREENSHOT_INDEX.md](output/release/v9.9.4/SCREENSHOT_INDEX.md)
63
+ [output/release/v9.9.5/SCREENSHOT_INDEX.md](output/release/v9.9.5/SCREENSHOT_INDEX.md)
64
64
 
65
65
  ## Current Release
66
66
 
67
- The current release is **9.9.4Durable Loops**:
68
-
69
- - **Loops that survive restarts.** Paused `awaiting_approval` runs are
70
- persisted to disk (hashed tokens, wall-clock expiry) and resume after a
71
- server restart; expired approvals answer with a one-click replan hint, and
72
- `GET /agent/approvals` re-surfaces pending runs. Approval cards show a live
73
- TTL countdown.
74
- - **You can watch the loop work.** Streamed agent runs emit named
75
- `agent_step` SSE events (plan tools → verify → terminal) rendered as a
76
- live step timeline; weak-model parse repairs are counted and surfaced
77
- honestly ("모델 응답을 N회 보정했어요").
78
- - **One retrieval policy.** A single `RetrievalPolicy` module (rule-based
79
- query rewrite, query-class weights, 14-day recency age-decay in a
80
- [0.5, 1.0] band) feeds both the service and graph fusion layers, gated by
81
- the benchmark CI test; embedder swaps now report `stale_embedder` instead
82
- of silently emptying the vector channel.
83
- - **Retrieval quality at the chunk level.** Markdown chunks at heading
84
- boundaries (with `heading_path` provenance), code at function boundaries,
85
- PDF chunks carry page numbers, plain text stays byte-identical — and
86
- cited source cards click through to the stored chunk text.
87
- - **Grounded answers, cited sources.** When retrieved context exists the
88
- answer prompt instructs [1]-style citations; a synthetic grounding bench
89
- gates the verdict heuristic in CI; briefings suggest periodic graph noise
90
- curation, and an opt-in review mode stages curator promotions for human
91
- approval.
92
- - **A wider harness.** Manifest-aware plan rewriting plus React/Vite and
93
- Python-package manifests, a bounded executor context window, five
94
- deterministic workflow scenarios, a funnel soft gate, and a weekly
95
- fail-open real-model agent smoke with a report artifact.
67
+ The current release is **9.9.5Closed Gaps**:
68
+
69
+ - **One approval path.** Legacy `human_in_loop` now rides the same durable
70
+ approval store as `awaiting_approval` (hashed tokens, restart-safe). The
71
+ old wire contract (`waiting_approval` + `context_id`) still works; the
72
+ separate in-memory `_pending` map is gone.
73
+ - **Rollback that is honest.** File recovery is `git` → pre-write `snapshot`
74
+ `none`, so non-git workspaces and newly created files can still be
75
+ restored. Each rollback entry reports its mode.
76
+ - **Critic sees artifact truth.** Before judging file work, the critic gets a
77
+ deterministic checklist of written paths with sanitize/repair flags — an
78
+ auto-repaired scaffold cannot pass as fulfillment unchecked.
79
+ - **Mid-run workspace awareness.** Later executor steps see files this run
80
+ already wrote, so multi-step "create then explain" work is not blind.
81
+ - **Optional cross-encoder rerank.** Off by default; set
82
+ `LATTICEAI_CROSS_ENCODER_RERANK=1` to reorder hybrid matches (identity
83
+ fallback when the model is missing). Hybrid responses carry a `rerank`
84
+ meta block.
85
+ - **Surface parity for approvals.** VS Code gains List/Approve/Reject
86
+ commands with token cache; Telegram handles both `waiting_approval` and
87
+ `awaiting_approval` with token-first resume.
88
+ - **Live sidecar E2E.** Nightly Playwright first-value tests hit a real
89
+ FastAPI sidecar (`npm run test:e2e:sidecar`), not only the visual mock
90
+ server.
96
91
 
97
92
  Release notes: [RELEASE.md](RELEASE.md) · Full history: [docs/CHANGELOG.md](docs/CHANGELOG.md)
98
93
 
99
- Expected artifacts for 9.9.4 release must use exact filenames:
94
+ Expected artifacts for 9.9.5 release must use exact filenames:
100
95
 
101
- - `dist/ltcai-9.9.4-py3-none-any.whl`
102
- - `dist/ltcai-9.9.4.tar.gz`
103
- - `ltcai-9.9.4.tgz`
104
- - `dist/ltcai-9.9.4.vsix`
105
- - `src-tauri/target/release/bundle/dmg/Lattice AI_9.9.4_aarch64.dmg`
96
+ - `dist/ltcai-9.9.5-py3-none-any.whl`
97
+ - `dist/ltcai-9.9.5.tar.gz`
98
+ - `ltcai-9.9.5.tgz`
99
+ - `dist/ltcai-9.9.5.vsix`
100
+ - `src-tauri/target/release/bundle/dmg/Lattice AI_9.9.5_aarch64.dmg`
106
101
 
107
102
  Do not use wildcard artifact uploads. Package registry publishing remains owner-run.
108
103
 
@@ -122,7 +117,7 @@ See [ARCHITECTURE.md](ARCHITECTURE.md) for details and
122
117
 
123
118
  - External package registries are owner-published and can lag behind GitHub.
124
119
  - PostgreSQL/pgvector is optional scale/migration tooling. SQLite remains the
125
- live local Brain store in 9.9.4.
120
+ live local Brain store in 9.9.5.
126
121
  - Docker, model downloads, cloud model calls, Telegram, Brain Network, and
127
122
  update checks require explicit user action.
128
123
  - Conversation does not fabricate answers when no model is loaded. Agent and
@@ -134,6 +129,7 @@ See [ARCHITECTURE.md](ARCHITECTURE.md) for details and
134
129
 
135
130
  | Version | Theme |
136
131
  | --- | --- |
132
+ | 9.9.5 | Closed Gaps |
137
133
  | 9.9.4 | Durable Loops |
138
134
  | 9.9.3 | Closed Loops |
139
135
  | 9.9.2 | Artifact Trust |
package/docs/CHANGELOG.md CHANGED
@@ -4,6 +4,41 @@ The top entry is either the current unreleased main-branch work or the current
4
4
  release line. Older entries are historical and may describe behavior as it
5
5
  existed at that release.
6
6
 
7
+ ## [9.9.5] - 2026-07-26
8
+
9
+ ### Added
10
+ - Optional cross-encoder rerank (`lattice_brain/graph/rerank.py`): env
11
+ `LATTICEAI_CROSS_ENCODER_RERANK=1` (model via
12
+ `LATTICEAI_CROSS_ENCODER_MODEL`); hybrid_search returns additive `rerank`
13
+ meta; default path is identity with no model download.
14
+ - Sidecar-backed Playwright first-value E2E: `tests/e2e/`,
15
+ `scripts/run_sidecar_e2e.mjs`, `npm run test:e2e:sidecar`, nightly
16
+ `.github/workflows/e2e-sidecar.yml`.
17
+ - VS Code approval commands: `ltcai.listApprovals`, `ltcai.approveAgent`,
18
+ `ltcai.rejectAgent` with pause-token session cache.
19
+ - Telegram approval handles both `waiting_approval` and `awaiting_approval`
20
+ with run_id+token resume preferred.
21
+ - Agent loop L4/L5/L7 helpers: `artifact_checklist`, `files_written`,
22
+ snapshot rollback ports (`snapshot_file` / `restore_snapshot`) with
23
+ mode-tagged results (`git` | `snapshot` | `none`).
24
+ - Knowledge-graph read surface decomposition:
25
+ `lattice_brain/graph/retrieval_reads.py` (`KnowledgeGraphReadsMixin`).
26
+
27
+ ### Changed
28
+ - Legacy `human_in_loop` pauses now use the durable `AgentRunStore` path
29
+ (`legacy_context=True`); the separate in-memory `_pending` map is removed.
30
+ Wire contract (`status=waiting_approval`, `context_id`) is preserved.
31
+ - Rollback recovers file-create actions via pre-write snapshots when git is
32
+ unavailable or not governed.
33
+ - Critic verify prompts include a deterministic artifact checklist derived
34
+ from transcript sanitize/repair flags.
35
+ - Executor prompts list files already written in the current run.
36
+ - `docs/SURFACE_PARITY.md` marks VS Code/Telegram approval as provided.
37
+
38
+ ### Tests
39
+ - `test_agent_loop_l4_l5_l7.py`, `test_cross_encoder_rerank.py`,
40
+ `test_snapshot_rollback_ports.py`, L1 approval flow extensions.
41
+
7
42
  ## [9.9.4] - 2026-07-26
8
43
 
9
44
  ### Added
@@ -1,6 +1,6 @@
1
1
  # Community And Plugins
2
2
 
3
- Current release: **9.9.4Durable Loops**.
3
+ Current release: **9.9.5Closed Gaps**.
4
4
 
5
5
  LatticeAI defines the path from a strong local-first framework (8.4.0
6
6
  action-aware baseline, 8.5.0 registry+DI hardening, 8.6.0 capture/navigation
@@ -3,7 +3,7 @@
3
3
  > **Status: canonical** — current contributor guidance, kept in sync with the
4
4
  > current release.
5
5
 
6
- Current release: **9.9.4Durable Loops**.
6
+ Current release: **9.9.5Closed Gaps**.
7
7
 
8
8
  This document is for contributors working on the local-first Digital Brain
9
9
  codebase. Product positioning and quick start stay in `README.md`; release
@@ -1,6 +1,6 @@
1
1
  # Lattice AI Onboarding
2
2
 
3
- Current release: **9.9.4Durable Loops**.
3
+ Current release: **9.9.5Closed Gaps**.
4
4
 
5
5
  The first-run goal is a five-minute path from "I opened the app" to "my Brain
6
6
  has a source, a question, and proof." This page is the product contract behind
@@ -1,4 +1,4 @@
1
- # Lattice AI — Operations Guide (v9.9.4)
1
+ # Lattice AI — Operations Guide (v9.9.5)
2
2
 
3
3
  > **Status: canonical** — kept in sync with the current release. Storage layout
4
4
  > below reflects the SQLite live Brain store and workspace scoping, not the
@@ -2,6 +2,7 @@
2
2
 
3
3
  > **Status: reference**
4
4
  > 도입: v9.9.4 (2026-07-25, 리뷰 Wave 1.4 "표면 패리티 체크리스트").
5
+ > 갱신: v9.9.5 — VS Code/Telegram 승인 흐름 갭 해소.
5
6
  > 목적: 각 표면(서피스)이 Brain 계약의 어디까지를 제공하는지 **정직하게** 기록한다.
6
7
  > "앱마다 다른 Lattice"를 방지하는 기준표이며, 기능을 과장하지 않는다.
7
8
 
@@ -24,15 +25,15 @@
24
25
  | 첫 저장 (Capture) | ✅ DnD·폴더·웹 수집, 품질 경고 | ✅ (웹과 동일) | ◐ `sendToLattice` 선택 영역 전송 | ✅ 페이지 캡처 (단일 목적) | ◐ 메시지 수집 |
25
26
  | 첫 회상 (Recall) | ✅ 하이브리드 검색 + grounding 배지 + 출처→청크 | ✅ (동일) | ◐ `askCurrentFile` — 배지 없음 ✖ | — | ◐ 답변만, 배지 없음 |
26
27
  | 첫 산출물 (Artifact) | ✅ artifacts[] 카드·미리보기·Brain 기억 칩 | ✅ (동일) | ◐ `createFile` — 검증 파이프라인 경유, 카드 UI 없음 | — | ◐ 파일 전송만 |
27
- | 첫 보호 (Approval) | ✅ 승인 카드 + TTL 카운트다운 + 재시작 생존 | ✅ (동일) | 승인 UI 없음 웹으로 유도 | — | 승인 불가 웹으로 유도 |
28
+ | 첫 보호 (Approval) | ✅ 승인 카드 + TTL 카운트다운 + 재시작 생존 | ✅ (동일) | List/Approve/Reject 커맨드 + 토큰 캐시 | — | 인라인 Done/Cancel + run_id/token 재개 |
28
29
 
29
30
  ## 기능 상세 × 표면
30
31
 
31
32
  | 기능 | Web `/app` | Desktop | VS Code | Browser | Telegram |
32
33
  | --- | --- | --- | --- | --- | --- |
33
34
  | 에이전트 스텝 타임라인 (v9.9.4) | ✅ 라이브 SSE | ✅ | — | — | — |
34
- | 승인 재개 (`/agent/resume`) | ✅ | ✅ | | — | |
35
- | 대기 중 승인 목록 (`GET /agent/approvals`, v9.9.4) | ✅ | ✅ | | — | — |
35
+ | 승인 재개 (`/agent/resume`) | ✅ | ✅ | `ltcai.approveAgent` / `rejectAgent` | — | callback → resume |
36
+ | 대기 중 승인 목록 (`GET /agent/approvals`, v9.9.4) | ✅ | ✅ | `ltcai.listApprovals` | — | — (봇 로컬 pending map) |
36
37
  | Watch 상태 신호 (v9.9.4) | ✅ 홈 카드 | ✅ | — | — | — |
37
38
  | Review Center (제안 승인/거절) | ✅ | ✅ | ✖ | — | — |
38
39
  | 데모 코퍼스 First Value Loop | ✅ | ✅ | — | — | — |
@@ -43,5 +44,10 @@
43
44
 
44
45
  1. **API 계약이 기준이다.** 모든 표면은 동일한 sidecar API를 소비한다. 표면별 전용 API를 만들지 않는다.
45
46
  2. **◐/✖ 는 문서화된 상태로만 존재한다.** 새 기능을 Web에 추가할 때 이 표를 갱신하고, 다른 표면에 제공하지 않을 경우 "—(의도)" 인지 "✖(갭)" 인지 명시한다.
46
- 3. **✖ 갭은 릴리스 노트의 Honest Limitations 후보다.** 현재 갭: VS Code/Telegram 승인 흐름(웹으로 유도하는 안내만 존재), VS Code 회상 배지.
47
+ 3. **✖ 갭은 릴리스 노트의 Honest Limitations 후보다.** 현재 갭: VS Code 회상 배지, VS Code Review Center.
47
48
  4. 릴리스마다 이 표를 검토한다 (release checklist의 docs 단계).
49
+
50
+ ## VS Code / Telegram 승인 메모 (v9.9.5)
51
+
52
+ - **토큰은 GET `/agent/approvals`에 다시 내려가지 않는다** (보안). Web은 일시 중지 응답의 토큰을 클라이언트에 보관하고, VS Code도 동일하게 pause 응답의 토큰을 확장 세션 캐시에 둔다. 캐시에 없으면 붙여넣기 또는 웹 UI로 유도한다.
53
+ - **Telegram**은 `waiting_approval`(레거시 `human_in_loop`)과 `awaiting_approval`(토큰) 둘 다 인라인 키보드로 처리하며, resume 시 run_id+token을 우선한다.
@@ -1,6 +1,6 @@
1
1
  # Lattice AI Trust Model
2
2
 
3
- Current release: **9.9.4Durable Loops**.
3
+ Current release: **9.9.5Closed Gaps**.
4
4
 
5
5
  Lattice AI is local-first, explicit about external communication, and honest
6
6
  when a capability is unavailable.
@@ -1,6 +1,6 @@
1
1
  # Why Lattice AI Exists
2
2
 
3
- Current release: **9.9.4Durable Loops**.
3
+ Current release: **9.9.5Closed Gaps**.
4
4
 
5
5
  **Lattice AI is a local-first Digital Brain that keeps your knowledge durable
6
6
  across any AI model.**
package/docs/kg-schema.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Knowledge Graph Schema
2
2
 
3
- Current release: **9.9.4Durable Loops**.
3
+ Current release: **9.9.5Closed Gaps**.
4
4
 
5
5
  명세 출처: `lattice_ai_full_spec.pptx` 슬라이드 20·21·22
6
6
  구현: `lattice_brain/graph/schema.py`
@@ -26,7 +26,7 @@ from .storage import (
26
26
  storage_from_env,
27
27
  )
28
28
 
29
- __version__ = "9.9.4"
29
+ __version__ = "9.9.5"
30
30
 
31
31
  __all__ = [
32
32
  "AgentRuntime",
@@ -0,0 +1,160 @@
1
+ """Optional cross-encoder rerank for hybrid retrieval (v9.9.5).
2
+
3
+ Default path is identity (fused score preserved) — no model download, no
4
+ latency tax, no unearned claims. Opt in with::
5
+
6
+ LATTICEAI_CROSS_ENCODER_RERANK=1
7
+ # optional model id (sentence-transformers CrossEncoder):
8
+ LATTICEAI_CROSS_ENCODER_MODEL=cross-encoder/ms-marco-MiniLM-L-6-v2
9
+
10
+ When the env kill-switch is off, or ``sentence_transformers`` / the model is
11
+ unavailable, :func:`rerank_matches` returns the candidates unchanged and
12
+ reports ``mode="identity"``. Failures never raise into the search path.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import logging
18
+ import os
19
+ from typing import Any, Dict, List, Optional
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+ CROSS_ENCODER_RERANK_ENV = "LATTICEAI_CROSS_ENCODER_RERANK"
24
+ CROSS_ENCODER_MODEL_ENV = "LATTICEAI_CROSS_ENCODER_MODEL"
25
+ DEFAULT_CROSS_ENCODER_MODEL = "cross-encoder/ms-marco-MiniLM-L-6-v2"
26
+
27
+ _model_cache: Dict[str, Any] = {}
28
+
29
+
30
+ def _rerank_enabled() -> bool:
31
+ raw = os.getenv(CROSS_ENCODER_RERANK_ENV, "").strip().lower()
32
+ return raw in {"1", "true", "yes", "on"}
33
+
34
+
35
+ def _model_id() -> str:
36
+ raw = os.getenv(CROSS_ENCODER_MODEL_ENV, "").strip()
37
+ return raw or DEFAULT_CROSS_ENCODER_MODEL
38
+
39
+
40
+ def _candidate_text(match: Dict[str, Any]) -> str:
41
+ parts = [
42
+ str(match.get("title") or ""),
43
+ str(match.get("summary") or ""),
44
+ str((match.get("metadata") or {}).get("snippet") or ""),
45
+ ]
46
+ return " ".join(p for p in parts if p).strip() or str(match.get("node_id") or "")
47
+
48
+
49
+ def _load_cross_encoder(model_id: str) -> Any:
50
+ if model_id in _model_cache:
51
+ return _model_cache[model_id]
52
+ from sentence_transformers import CrossEncoder # type: ignore
53
+
54
+ model = CrossEncoder(model_id)
55
+ _model_cache[model_id] = model
56
+ return model
57
+
58
+
59
+ def identity_rerank(
60
+ query: str,
61
+ candidates: List[Dict[str, Any]],
62
+ *,
63
+ top_k: Optional[int] = None,
64
+ ) -> Dict[str, Any]:
65
+ """Preserve fused ordering; stamp identity scores for an honest contract."""
66
+ del query # identity path does not use the query text
67
+ ranked = list(candidates)
68
+ for item in ranked:
69
+ item["rerank_score"] = float(item.get("score") or item.get("fused_score") or 0.0)
70
+ if top_k is not None:
71
+ ranked = ranked[: max(1, int(top_k))]
72
+ return {
73
+ "matches": ranked,
74
+ "mode": "identity",
75
+ "model": None,
76
+ "detail": None,
77
+ }
78
+
79
+
80
+ def cross_encoder_rerank(
81
+ query: str,
82
+ candidates: List[Dict[str, Any]],
83
+ *,
84
+ top_k: Optional[int] = None,
85
+ model_id: Optional[str] = None,
86
+ ) -> Dict[str, Any]:
87
+ """Score (query, candidate) pairs with a CrossEncoder when available."""
88
+ if not candidates:
89
+ return {
90
+ "matches": [],
91
+ "mode": "cross_encoder",
92
+ "model": model_id or _model_id(),
93
+ "detail": None,
94
+ }
95
+ mid = model_id or _model_id()
96
+ try:
97
+ model = _load_cross_encoder(mid)
98
+ except Exception as exc: # noqa: BLE001 — never break search
99
+ logger.info("cross-encoder unavailable (%s); falling back to identity", exc)
100
+ result = identity_rerank(query, candidates, top_k=top_k)
101
+ result["detail"] = f"cross_encoder_unavailable: {exc}"
102
+ return result
103
+
104
+ pairs = [[str(query or ""), _candidate_text(c)] for c in candidates]
105
+ try:
106
+ scores = model.predict(pairs)
107
+ except Exception as exc: # noqa: BLE001
108
+ logger.warning("cross-encoder predict failed: %s", exc)
109
+ result = identity_rerank(query, candidates, top_k=top_k)
110
+ result["detail"] = f"cross_encoder_predict_failed: {exc}"
111
+ return result
112
+
113
+ ranked = list(candidates)
114
+ for item, score in zip(ranked, scores):
115
+ item["rerank_score"] = float(score)
116
+ # Surface the rerank score as the primary ranking key while keeping
117
+ # the pre-rerank fused score under scores.fused for audit.
118
+ scores_map = item.setdefault("scores", {})
119
+ if isinstance(scores_map, dict):
120
+ scores_map.setdefault("fused", float(item.get("score") or 0.0))
121
+ scores_map["rerank"] = float(score)
122
+ item["score"] = float(score)
123
+ ranked.sort(key=lambda m: (-float(m.get("rerank_score") or 0.0), str(m.get("node_id") or "")))
124
+ if top_k is not None:
125
+ ranked = ranked[: max(1, int(top_k))]
126
+ for rank, match in enumerate(ranked, start=1):
127
+ match["rank"] = rank
128
+ return {
129
+ "matches": ranked,
130
+ "mode": "cross_encoder",
131
+ "model": mid,
132
+ "detail": None,
133
+ }
134
+
135
+
136
+ def rerank_matches(
137
+ query: str,
138
+ candidates: List[Dict[str, Any]],
139
+ *,
140
+ top_k: Optional[int] = None,
141
+ force: Optional[bool] = None,
142
+ ) -> Dict[str, Any]:
143
+ """Public entry: cross-encoder when enabled, else identity.
144
+
145
+ ``force=True/False`` overrides the env kill-switch (tests only).
146
+ """
147
+ enabled = _rerank_enabled() if force is None else bool(force)
148
+ if not enabled:
149
+ return identity_rerank(query, candidates, top_k=top_k)
150
+ return cross_encoder_rerank(query, candidates, top_k=top_k)
151
+
152
+
153
+ __all__ = [
154
+ "CROSS_ENCODER_MODEL_ENV",
155
+ "CROSS_ENCODER_RERANK_ENV",
156
+ "DEFAULT_CROSS_ENCODER_MODEL",
157
+ "cross_encoder_rerank",
158
+ "identity_rerank",
159
+ "rerank_matches",
160
+ ]