ltcai 7.1.0 → 7.3.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 (44) hide show
  1. package/README.md +26 -19
  2. package/docs/CHANGELOG.md +41 -0
  3. package/docs/ROADMAP_RECOMMENDATIONS.md +55 -0
  4. package/frontend/src/api/client.ts +3 -0
  5. package/frontend/src/features/admin/AdminConsole.tsx +41 -1
  6. package/frontend/src/i18n.ts +24 -0
  7. package/frontend/src/styles.css +45 -0
  8. package/lattice_brain/__init__.py +1 -1
  9. package/lattice_brain/runtime/agent_runtime.py +62 -1
  10. package/lattice_brain/runtime/contracts.py +107 -0
  11. package/lattice_brain/runtime/multi_agent.py +6 -2
  12. package/latticeai/__init__.py +1 -1
  13. package/latticeai/api/agents.py +12 -0
  14. package/latticeai/api/tools.py +14 -0
  15. package/latticeai/core/agent.py +5 -0
  16. package/latticeai/core/marketplace.py +1 -1
  17. package/latticeai/core/tool_registry.py +47 -0
  18. package/latticeai/core/workspace_os.py +1 -1
  19. package/latticeai/services/tool_dispatch.py +14 -0
  20. package/package.json +1 -1
  21. package/scripts/brain_quality_eval.py +24 -1
  22. package/src-tauri/Cargo.lock +1 -1
  23. package/src-tauri/Cargo.toml +1 -1
  24. package/src-tauri/tauri.conf.json +1 -1
  25. package/static/app/asset-manifest.json +28 -28
  26. package/static/app/assets/{Act-BHEb8bAM.js → Act-Di4tRFWY.js} +2 -2
  27. package/static/app/assets/{Act-BHEb8bAM.js.map → Act-Di4tRFWY.js.map} +1 -1
  28. package/static/app/assets/{Brain-yvR7xkrV.js → Brain-BZB3Gy9w.js} +2 -2
  29. package/static/app/assets/{Brain-yvR7xkrV.js.map → Brain-BZB3Gy9w.js.map} +1 -1
  30. package/static/app/assets/{Capture-BrAmsSEH.js → Capture-tNyYWxnh.js} +2 -2
  31. package/static/app/assets/{Capture-BrAmsSEH.js.map → Capture-tNyYWxnh.js.map} +1 -1
  32. package/static/app/assets/{Library-BOVzYfxI.js → Library-DAtDDLdg.js} +2 -2
  33. package/static/app/assets/{Library-BOVzYfxI.js.map → Library-DAtDDLdg.js.map} +1 -1
  34. package/static/app/assets/{System-D4WaN4kj.js → System-DEu0xNUc.js} +2 -2
  35. package/static/app/assets/{System-D4WaN4kj.js.map → System-DEu0xNUc.js.map} +1 -1
  36. package/static/app/assets/{index-DvLsGy-z.css → index-Bi_bpigM.css} +1 -1
  37. package/static/app/assets/{index-BOB-W1FZ.js → index-COuGp7_5.js} +3 -3
  38. package/static/app/assets/index-COuGp7_5.js.map +1 -0
  39. package/static/app/assets/{primitives-C3_BfUb8.js → primitives-CdwcE--L.js} +2 -2
  40. package/static/app/assets/{primitives-C3_BfUb8.js.map → primitives-CdwcE--L.js.map} +1 -1
  41. package/static/app/assets/{textarea-CbvhHvzg.js → textarea-CqOdBPL1.js} +2 -2
  42. package/static/app/assets/{textarea-CbvhHvzg.js.map → textarea-CqOdBPL1.js.map} +1 -1
  43. package/static/app/index.html +2 -2
  44. package/static/app/assets/index-BOB-W1FZ.js.map +0 -1
package/README.md CHANGED
@@ -200,28 +200,33 @@ See [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) for developer workflow details.
200
200
 
201
201
  ## Current Release
202
202
 
203
- The current release is **7.1.0 Brain Usability Completion**:
204
-
205
- - First-run onboarding now explains detected hardware, recommendation timing,
206
- install progress, and the next action in plain language.
207
- - Brain Home shows ingestion progress for files, folders, notes, and web pages,
208
- including memory emergence after content lands.
209
- - Knowledge Graph exploration adds search suggestions, type filters, time
210
- exploration, focus movement, and neighbor highlighting.
211
- - Chat answers surface inline source citations and accessible proof payloads.
212
- - Workspace/profile/admin entry points, empty/error feedback, consent revoke
213
- feedback, and VS Code extension sync status are visible in the product shell.
214
-
215
- Expected artifacts for 7.1.0 release must use exact filenames:
216
-
217
- - `dist/ltcai-7.1.0-py3-none-any.whl`
218
- - `dist/ltcai-7.1.0.tar.gz`
219
- - `ltcai-7.1.0.tgz`
220
- - `dist/ltcai-7.1.0.vsix`
221
- - `src-tauri/target/release/bundle/dmg/Lattice AI_7.1.0_aarch64.dmg`
203
+ The current release is **7.3.0 Runtime Contract & Retrieval Quality**:
204
+
205
+ - Single-agent and multi-agent execution now share a serializable
206
+ `agent-run-contract/v1` payload for run identity, mode, status, roles,
207
+ timeline, retries, artifacts, and terminal state.
208
+ - Agent runtime API responses include the shared contract, keeping real and
209
+ simulated run history distinguishable as runtime extraction continues.
210
+ - Brain quality evaluation now includes deterministic hybrid recall/ranking
211
+ regression thresholds, not only "some recall exists" checks.
212
+ - The 7.2.0 preview/diagnostics work remains the admin trust surface while
213
+ 7.3.0 tightens the underlying run and retrieval contracts.
214
+ - The roadmap focus is durability over model choice: knowledge, run evidence,
215
+ and retrieval quality become measurable product assets.
216
+
217
+ Expected artifacts for 7.3.0 release must use exact filenames:
218
+
219
+ - `dist/ltcai-7.3.0-py3-none-any.whl`
220
+ - `dist/ltcai-7.3.0.tar.gz`
221
+ - `ltcai-7.3.0.tgz`
222
+ - `dist/ltcai-7.3.0.vsix`
223
+ - `src-tauri/target/release/bundle/dmg/Lattice AI_7.3.0_aarch64.dmg`
222
224
 
223
225
  Do not upload `dist/*`. Package registry publishing remains owner-run.
224
226
 
227
+ See [docs/ROADMAP_RECOMMENDATIONS.md](docs/ROADMAP_RECOMMENDATIONS.md) for the
228
+ strategic roadmap slice applied in 7.3.0 and the follow-up tracks.
229
+
225
230
  ## Known Limitations
226
231
 
227
232
  - External package registries are owner-published and can lag behind GitHub.
@@ -237,6 +242,8 @@ Do not upload `dist/*`. Package registry publishing remains owner-run.
237
242
 
238
243
  | Version | Theme |
239
244
  | --- | --- |
245
+ | 7.3.0 | Runtime Contract & Retrieval Quality: shared agent-run contract across runtimes plus deterministic hybrid recall/ranking regression gates |
246
+ | 7.2.0 | Runtime Trust Baseline: agent run preview/readiness, simulation-mode guardrails, live ToolRegistry manifest/diagnostics, and tests for dispatch/governance/catalog drift |
240
247
  | 7.1.0 | Brain Usability Completion: clearer first-run onboarding, ingestion progress/emergence, richer graph controls, inline answer proof, workspace/profile/admin discovery, empty/error/consent feedback, and VS Code sync status |
241
248
  | 7.0.0 | Brain Productization Loop: first-screen ingestion for files/folders/notes/web, answer-level memory proof and source citations, model-continuity demo flow, five-minute first-run loop, and recall/KG quality eval in CI |
242
249
  | 6.7.0 | Brain IA Cleanup: reachable rich pages, separated product routes vs compatibility aliases, shared Brain shell navigation, and lazy-loaded rich pages |
package/docs/CHANGELOG.md CHANGED
@@ -4,6 +4,47 @@ 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
+ ## [7.3.0] - 2026-06-20
8
+
9
+ > Runtime Contract & Retrieval Quality. Turns the next AgentRuntime extraction
10
+ > step and the uploaded roadmap's hybrid-search quality goals into a small,
11
+ > tested release: shared run contracts and deterministic recall regression.
12
+
13
+ ### Added
14
+ - `lattice_brain.runtime.contracts.AgentRunContract`, a serializable
15
+ `agent-run-contract/v1` payload shared by single-agent and multi-agent
16
+ execution paths.
17
+ - Multi-agent API result/run patches now include the shared contract with
18
+ runtime, mode, status, roles, retries, timeline, and terminal-state data.
19
+ - Single-agent runtime exposes the same contract helper for UI/API/storage
20
+ convergence in the next extraction pass.
21
+ - `scripts/brain_quality_eval.py` now runs deterministic hybrid recall/ranking
22
+ regression checks with recall and precision thresholds.
23
+
24
+ ### Changed
25
+ - Package/runtime/static metadata is synchronized to 7.3.0; package publish and
26
+ deployment remain owner-run only.
27
+
28
+ ## [7.2.0] - 2026-06-20
29
+
30
+ > Runtime Trust Baseline. Adds execution preview and registry diagnostics so
31
+ > agent runs and tool permissions become inspectable contracts before action.
32
+
33
+ ### Added
34
+ - `POST /agents/api/run/preview` for AgentRuntime readiness, role selection,
35
+ input keys, retry clamping, and blocking reasons without starting a run.
36
+ - `GET /tools/registry` for the live ToolRegistry manifest across dispatch
37
+ handlers, governance policy, catalog descriptions, and permissions.
38
+ - `GET /tools/registry/diagnostics` for a compact drift check suitable for CI,
39
+ admin views, and runtime health panels.
40
+ - Unit coverage for AgentRuntime preview and ToolRegistry manifest diagnostics.
41
+
42
+ ### Changed
43
+ - Tool governance now covers `read_document`, and the catalog describes
44
+ `create_web_project`, closing the current dispatch/governance/catalog drift.
45
+ - Package/runtime/static metadata is synchronized to 7.2.0; package publish and
46
+ deployment remain owner-run only.
47
+
7
48
  ## [7.1.0] - 2026-06-20
8
49
 
9
50
  > Brain Usability Completion. Completes the 7.1.0 first-run through editor-sync
@@ -0,0 +1,55 @@
1
+ # Strategic Roadmap Recommendations
2
+
3
+ This roadmap captures the 2026-06-20 product direction input and maps it to
4
+ small release-sized work. The operating principle stays unchanged:
5
+
6
+ > Models are temporary. Knowledge is durable. The Brain is the product.
7
+
8
+ ## 7.3.0 Applied Slice
9
+
10
+ 7.3.0 implements the first narrow slice of the roadmap:
11
+
12
+ - Runtime evolution: single-agent and multi-agent execution share
13
+ `agent-run-contract/v1`, making mode/status/timeline evidence inspectable.
14
+ - Hybrid search optimization: the Brain quality gate now includes deterministic
15
+ recall and ranking regression thresholds.
16
+ - Security and trust: run contracts distinguish runtime type and execution mode
17
+ so simulated output is not presented as real execution.
18
+
19
+ ## Near-Term Tracks
20
+
21
+ 1. Retrieval quality and scale
22
+ - Expand fixture datasets for hybrid search.
23
+ - Add latency budgets for large corpora.
24
+ - Track recall, precision, and ranking regressions in CI.
25
+ - Tune semantic/graph/keyword fusion by query class.
26
+
27
+ 2. Incremental ingestion
28
+ - Add background indexing jobs.
29
+ - Detect duplicates and merge memory candidates.
30
+ - Surface conflict resolution for contradictory memories.
31
+
32
+ 3. Runtime convergence
33
+ - Use `agent-run-contract/v1` across single-agent, multi-agent, workflows,
34
+ audit logs, and UI event streams.
35
+ - Keep simulation mode explicit and never record it as product success.
36
+ - Route tools through explicit registry/governance contracts.
37
+
38
+ 4. Brain SDK boundary
39
+ - Continue extracting `lattice_brain` as a reusable Brain Core package.
40
+ - Keep compatibility shims until downstream imports are migrated.
41
+ - Preserve migration rollback paths for graph and memory storage.
42
+
43
+ 5. Trust and operations
44
+ - Extend audit logging around tool execution and retrieval injection.
45
+ - Add dependency vulnerability monitoring.
46
+ - Add Tauri update/rollback planning before enabling auto-update.
47
+
48
+ ## Longer-Term Tracks
49
+
50
+ - Multi-modal ingestion and retrieval for images, audio, and video.
51
+ - Proactive Brain synthesis, contradiction detection, and recommendations.
52
+ - Temporal reasoning over historical graph states.
53
+ - Interoperability with Obsidian, Notion, Email, Calendar, Git, Slack, and Teams.
54
+ - Encrypted Brain Network sharing.
55
+ - Plugin marketplace and public benchmarks.
@@ -481,7 +481,10 @@ export const latticeApi = {
481
481
  unloadModel: (model_id: string) => del(`/models/unload/${encodeURIComponent(model_id)}`, {}),
482
482
  embeddingsStatus: () => get("/api/embeddings/status", {}),
483
483
  agentRuntime: () => get("/agents/api/runtime/status", { runtime: {}, agents: [], runs: [] }),
484
+ agentRunPreview: (goal: string, roles: string[] = []) => post("/agents/api/run/preview", { goal, roles }, { ready: false, blocking_reasons: [] }),
484
485
  runAgent: (goal: string, roles: string[]) => post("/agents/api/run", { goal, roles }, {}),
486
+ toolRegistryDiagnostics: () => get("/tools/registry/diagnostics", { diagnostics: { ready: false } }),
487
+ toolRegistry: () => get("/tools/registry", { status: "unavailable", diagnostics: {}, tools: [] }),
485
488
  agentRun: (id: string) => get(`/agents/api/runs/${encodeURIComponent(id)}`, {}),
486
489
  stopAgentRun: (id: string) => post(`/agents/api/runs/${encodeURIComponent(id)}/stop`, {}, {}),
487
490
  agentRegistry: () => get("/agents/api/registry", { agents: [] }),
@@ -15,7 +15,7 @@ export function AdminConsole({ onBack }: { onBack: () => void }) {
15
15
  const qc = useQueryClient();
16
16
  const language = useAppStore((state) => state.language);
17
17
  const [filters, setFilters] = React.useState<AdminFilterState>({ q: "", actor: "", action: "", severity: "", limit: 50 });
18
- const { summaryQ, statsQ, usersQ, auditQ, securityQ, securityEventsQ, policiesQ, rolesQ, retentionQ, indexQ } = useAdminConsoleData(filters);
18
+ const { summaryQ, statsQ, usersQ, auditQ, securityQ, securityEventsQ, policiesQ, rolesQ, retentionQ, indexQ, agentRuntimeQ, toolRegistryQ } = useAdminConsoleData(filters);
19
19
  const rebuildIndex = useMutation({
20
20
  mutationFn: latticeApi.rebuildIndex,
21
21
  onSuccess: () => void qc.invalidateQueries({ queryKey: ["indexStatus"] }),
@@ -139,6 +139,10 @@ export function AdminConsole({ onBack }: { onBack: () => void }) {
139
139
  </div>
140
140
  </AdminPanel>
141
141
 
142
+ <AdminPanel title={t(language, "admin.panel.runtimeTrust")} eyebrow={t(language, "admin.panel.contracts")}>
143
+ <RuntimeTrustPanel runtime={agentRuntimeQ.data?.data as ApiRecord | undefined} registry={toolRegistryQ.data?.data as ApiRecord | undefined} language={language} />
144
+ </AdminPanel>
145
+
142
146
  </section>
143
147
  </main>
144
148
  );
@@ -164,9 +168,45 @@ function useAdminConsoleData(filters: AdminFilterState) {
164
168
  rolesQ: useQuery({ queryKey: ["adminRoles"], queryFn: latticeApi.adminRoles }),
165
169
  retentionQ: useQuery({ queryKey: ["adminLogRetention"], queryFn: latticeApi.adminLogRetention }),
166
170
  indexQ: useQuery({ queryKey: ["indexStatus"], queryFn: latticeApi.indexStatus }),
171
+ agentRuntimeQ: useQuery({ queryKey: ["agentRuntime"], queryFn: latticeApi.agentRuntime }),
172
+ toolRegistryQ: useQuery({ queryKey: ["toolRegistryDiagnostics"], queryFn: latticeApi.toolRegistryDiagnostics }),
167
173
  };
168
174
  }
169
175
 
176
+ function RuntimeTrustPanel({ runtime, registry, language }: { runtime?: ApiRecord; registry?: ApiRecord; language: "ko" | "en" }) {
177
+ const runtimeInfo = (runtime?.runtime || {}) as ApiRecord;
178
+ const health = (runtime?.health || {}) as ApiRecord;
179
+ const diagnostics = (registry?.diagnostics || {}) as ApiRecord;
180
+ const ready = Boolean(runtimeInfo.ready);
181
+ const registryReady = Boolean(diagnostics.ready);
182
+ const blocking = stringValue(runtimeInfo.unavailable_reason, ready ? t(language, "admin.runtime.readyDetail") : t(language, "admin.runtime.blockedFallback"));
183
+ return (
184
+ <div className="admin-runtime-trust">
185
+ <div className="admin-runtime-row">
186
+ <strong>{t(language, "admin.runtime.agent")}</strong>
187
+ <span className={ready ? "is-ok" : "is-warn"}>{ready ? t(language, "admin.status.ready") : t(language, "admin.status.unavailable")}</span>
188
+ <small>{blocking}</small>
189
+ </div>
190
+ <div className="admin-runtime-row">
191
+ <strong>{t(language, "admin.runtime.tools")}</strong>
192
+ <span className={registryReady ? "is-ok" : "is-warn"}>{registryReady ? t(language, "admin.runtime.aligned") : t(language, "admin.runtime.drift")}</span>
193
+ <small>
194
+ {t(language, "admin.runtime.toolCounts", {
195
+ registered: stringValue(diagnostics.registered_tools, "0"),
196
+ governed: stringValue(diagnostics.governed_tools, "0"),
197
+ described: stringValue(diagnostics.described_tools, "0"),
198
+ })}
199
+ </small>
200
+ </div>
201
+ <div className="admin-policy-strip">
202
+ <span>{t(language, "admin.runtime.mode", { mode: stringValue(runtimeInfo.mode, "unknown") })}</span>
203
+ <span>{t(language, "admin.runtime.execution", { mode: stringValue(runtimeInfo.execution_mode, "unknown") })}</span>
204
+ <span>{t(language, "admin.runtime.health", { status: stringValue(health.status, "unknown") })}</span>
205
+ </div>
206
+ </div>
207
+ );
208
+ }
209
+
170
210
  function AdminLogFilters({
171
211
  filters,
172
212
  matched,
@@ -348,6 +348,8 @@ export const COPY: Record<Language, TextMap> = {
348
348
  "admin.panel.protection": "보호",
349
349
  "admin.panel.brainOps": "Brain 운영",
350
350
  "admin.panel.maintenance": "유지보수",
351
+ "admin.panel.runtimeTrust": "런타임 신뢰도",
352
+ "admin.panel.contracts": "계약",
351
353
  "admin.empty.users": "관리 API가 보고한 사용자가 없습니다.",
352
354
  "admin.empty.roles": "보고된 역할 매트릭스가 없습니다.",
353
355
  "admin.empty.audit": "최근 감사 이벤트가 없습니다.",
@@ -369,6 +371,16 @@ export const COPY: Record<Language, TextMap> = {
369
371
  "admin.brain.summaryFallback": "로컬 Brain 서비스는 사용자 대화와 분리되어 있습니다.",
370
372
  "admin.brain.rebuilding": "재구성 중",
371
373
  "admin.brain.rebuild": "인덱스 재구성",
374
+ "admin.runtime.agent": "AgentRuntime",
375
+ "admin.runtime.tools": "ToolRegistry",
376
+ "admin.runtime.readyDetail": "실행 preview와 실제 실행이 준비되었습니다.",
377
+ "admin.runtime.blockedFallback": "실행 전 preview가 차단 사유를 반환합니다.",
378
+ "admin.runtime.aligned": "정렬됨",
379
+ "admin.runtime.drift": "점검 필요",
380
+ "admin.runtime.toolCounts": "등록 {registered} · 정책 {governed} · 설명 {described}",
381
+ "admin.runtime.mode": "mode {mode}",
382
+ "admin.runtime.execution": "exec {mode}",
383
+ "admin.runtime.health": "health {status}",
372
384
  "admin.policy.fallback": "정책",
373
385
  "admin.policy.quiet": "정책 API 대기 중",
374
386
  "admin.retention.days": "{days}일 보관",
@@ -894,6 +906,8 @@ export const COPY: Record<Language, TextMap> = {
894
906
  "admin.panel.protection": "Protection",
895
907
  "admin.panel.brainOps": "Brain Operations",
896
908
  "admin.panel.maintenance": "Maintenance",
909
+ "admin.panel.runtimeTrust": "Runtime Trust",
910
+ "admin.panel.contracts": "Contracts",
897
911
  "admin.empty.users": "No users reported by the admin API.",
898
912
  "admin.empty.roles": "No role matrix reported.",
899
913
  "admin.empty.audit": "No recent audit events.",
@@ -915,6 +929,16 @@ export const COPY: Record<Language, TextMap> = {
915
929
  "admin.brain.summaryFallback": "Local Brain services are separated from user chat.",
916
930
  "admin.brain.rebuilding": "Rebuilding",
917
931
  "admin.brain.rebuild": "Rebuild index",
932
+ "admin.runtime.agent": "AgentRuntime",
933
+ "admin.runtime.tools": "ToolRegistry",
934
+ "admin.runtime.readyDetail": "Run preview and execution are ready.",
935
+ "admin.runtime.blockedFallback": "Run preview returns the blocking reason before execution.",
936
+ "admin.runtime.aligned": "Aligned",
937
+ "admin.runtime.drift": "Needs review",
938
+ "admin.runtime.toolCounts": "Registered {registered} · governed {governed} · described {described}",
939
+ "admin.runtime.mode": "mode {mode}",
940
+ "admin.runtime.execution": "exec {mode}",
941
+ "admin.runtime.health": "health {status}",
918
942
  "admin.policy.fallback": "Policy",
919
943
  "admin.policy.quiet": "Policy API quiet",
920
944
  "admin.retention.days": "{days} day retention",
@@ -3472,6 +3472,51 @@ body {
3472
3472
  font-size: 0.88rem;
3473
3473
  }
3474
3474
 
3475
+ .admin-runtime-trust {
3476
+ display: grid;
3477
+ gap: 0.72rem;
3478
+ }
3479
+
3480
+ .admin-runtime-row {
3481
+ display: grid;
3482
+ grid-template-columns: minmax(8rem, 1fr) auto;
3483
+ gap: 0.2rem 0.75rem;
3484
+ border: 1px solid hsl(var(--border) / 0.48);
3485
+ border-radius: 8px;
3486
+ background: hsl(var(--bg) / 0.35);
3487
+ padding: 0.72rem;
3488
+ }
3489
+
3490
+ .admin-runtime-row strong {
3491
+ color: hsl(var(--fg));
3492
+ font-size: 0.88rem;
3493
+ }
3494
+
3495
+ .admin-runtime-row > span {
3496
+ align-self: start;
3497
+ border-radius: 999px;
3498
+ padding: 0.18rem 0.48rem;
3499
+ font-size: 0.72rem;
3500
+ font-weight: 700;
3501
+ }
3502
+
3503
+ .admin-runtime-row > small {
3504
+ grid-column: 1 / -1;
3505
+ color: hsl(var(--fg-muted));
3506
+ font-size: 0.78rem;
3507
+ line-height: 1.35;
3508
+ }
3509
+
3510
+ .admin-runtime-row .is-ok {
3511
+ background: hsl(151 65% 36% / 0.13);
3512
+ color: hsl(151 48% 32%);
3513
+ }
3514
+
3515
+ .admin-runtime-row .is-warn {
3516
+ background: hsl(36 88% 52% / 0.14);
3517
+ color: hsl(30 82% 35%);
3518
+ }
3519
+
3475
3520
  .brain-document-input,
3476
3521
  .brain-image-input {
3477
3522
  display: inline-flex;
@@ -26,7 +26,7 @@ from .storage import (
26
26
  storage_from_env,
27
27
  )
28
28
 
29
- __version__ = "7.1.0"
29
+ __version__ = "7.3.0"
30
30
 
31
31
  __all__ = [
32
32
  "AgentRuntime",
@@ -37,6 +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
41
 
41
42
  ROLE_DESCRIPTIONS = {
42
43
  "researcher": "Gathers workspace context and memory for the goal.",
@@ -228,6 +229,57 @@ class AgentRuntime:
228
229
  "runs": runs[:25],
229
230
  }
230
231
 
232
+ def preview(
233
+ self,
234
+ goal: str,
235
+ *,
236
+ roles: Optional[List[str]] = None,
237
+ inputs: Optional[Dict[str, Any]] = None,
238
+ max_retries: int = 2,
239
+ scope: Optional[str] = None,
240
+ ) -> Dict[str, Any]:
241
+ """Return the execution contract without reserving or starting a run.
242
+
243
+ The preview is the product-facing readiness gate for agent execution:
244
+ clients can show whether a real LLM-backed run can start, which roles
245
+ will execute, how retry limits are clamped, and why the run is blocked.
246
+ """
247
+
248
+ requested_roles = list(roles or CORE_PIPELINE)
249
+ unknown_roles = [role for role in requested_roles if role not in AGENT_ROLES]
250
+ health = self.health()
251
+ goal_ready = bool(str(goal or "").strip())
252
+ retry_budget = max(0, min(int(max_retries or 0), self._max_retries_cap))
253
+ blocking_reasons: List[str] = []
254
+ if not goal_ready:
255
+ blocking_reasons.append("goal is required")
256
+ if unknown_roles:
257
+ blocking_reasons.append(f"unknown roles: {', '.join(unknown_roles)}")
258
+ if not health.get("ready"):
259
+ orchestrator = (health.get("checks") or {}).get("orchestrator") or {}
260
+ blocking_reasons.append(str(orchestrator.get("detail") or "agent runtime is unavailable"))
261
+ can_start = not blocking_reasons
262
+ return {
263
+ "ready": can_start,
264
+ "can_start": can_start,
265
+ "blocking_reasons": blocking_reasons,
266
+ "goal": str(goal or "").strip(),
267
+ "roles": requested_roles,
268
+ "unknown_roles": unknown_roles,
269
+ "inputs_keys": sorted((inputs or {}).keys()),
270
+ "max_retries": retry_budget,
271
+ "max_retries_requested": max_retries,
272
+ "scope": scope,
273
+ "execution_mode": self._execution_mode(),
274
+ "runtime": {
275
+ "version": MULTI_AGENT_VERSION,
276
+ "default_pipeline": list(CORE_PIPELINE),
277
+ "max_retries_cap": self._max_retries_cap,
278
+ "simulation_runs_allowed": self._allow_simulation_runs,
279
+ },
280
+ "health": health,
281
+ }
282
+
231
283
  # ── events / state ────────────────────────────────────────────────────
232
284
  def list_runs(self, *, scope: Optional[str] = None) -> Dict[str, Any]:
233
285
  return self._store.list_agents(workspace_id=scope)
@@ -293,6 +345,7 @@ class AgentRuntime:
293
345
  "retry_history": result.retry_history,
294
346
  "memory_snapshots": result.memory_snapshots,
295
347
  "mode": getattr(result, "mode", "simulation"),
348
+ "contract": multi_agent_contract(result=result, goal=goal),
296
349
  "current_role": None,
297
350
  }
298
351
 
@@ -458,6 +511,12 @@ class AgentRuntime:
458
511
  status=updated.get("status") or result.status,
459
512
  )
460
513
  result_payload = result.as_dict()
514
+ result_payload["contract"] = multi_agent_contract(
515
+ result=result,
516
+ goal=goal,
517
+ run_id=run_id,
518
+ current_role=updated.get("current_role") if isinstance(updated, dict) else None,
519
+ )
461
520
  if updated.get("status") == "cancelled":
462
521
  result_payload = {"status": "cancelled", "reason": updated.get("cancel_reason"), "completed_result": result_payload}
463
522
  payload = {"run": updated, "result": result_payload}
@@ -550,7 +609,9 @@ class AgentRuntime:
550
609
  user_email=user_email, workspace_id=scope,
551
610
  )
552
611
 
553
- payload = {"run": run, "result": result.as_dict()}
612
+ result_payload = result.as_dict()
613
+ result_payload["contract"] = multi_agent_contract(result=result, goal=goal, run_id=run.get("id") if isinstance(run, dict) else None)
614
+ payload = {"run": run, "result": result_payload}
554
615
  if pre_dispatch is not None:
555
616
  payload["pre_run_hooks"] = pre_dispatch
556
617
  if post_dispatch is not None:
@@ -0,0 +1,107 @@
1
+ """Shared runtime contracts for agent execution surfaces.
2
+
3
+ These contracts are deliberately small and serializable. The single-agent
4
+ state machine and the multi-agent facade can evolve independently, but callers
5
+ should see the same run identity, mode, status, role, timeline and artifact
6
+ shape across both paths.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from dataclasses import dataclass, field
12
+ from datetime import datetime
13
+ from typing import Any, Dict, List, Optional
14
+
15
+
16
+ def runtime_timestamp() -> str:
17
+ return datetime.now().isoformat(timespec="seconds")
18
+
19
+
20
+ @dataclass(frozen=True)
21
+ class AgentRunContract:
22
+ run_id: Optional[str]
23
+ agent_id: str
24
+ runtime: str
25
+ mode: str
26
+ status: str
27
+ goal: str
28
+ roles: List[str] = field(default_factory=list)
29
+ current_role: Optional[str] = None
30
+ retries: int = 0
31
+ timeline: List[Dict[str, Any]] = field(default_factory=list)
32
+ artifacts: List[Dict[str, Any]] = field(default_factory=list)
33
+ blocking_reasons: List[str] = field(default_factory=list)
34
+ schema_version: str = "agent-run-contract/v1"
35
+
36
+ @property
37
+ def is_terminal(self) -> bool:
38
+ return self.status in {"ok", "retried_ok", "failed", "rejected", "cancelled", "interrupted", "partial", "done"}
39
+
40
+ def as_dict(self) -> Dict[str, Any]:
41
+ return {
42
+ "schema_version": self.schema_version,
43
+ "run_id": self.run_id,
44
+ "agent_id": self.agent_id,
45
+ "runtime": self.runtime,
46
+ "mode": self.mode,
47
+ "status": self.status,
48
+ "goal": self.goal,
49
+ "roles": list(self.roles),
50
+ "current_role": self.current_role,
51
+ "retries": self.retries,
52
+ "timeline": list(self.timeline),
53
+ "artifacts": list(self.artifacts),
54
+ "blocking_reasons": list(self.blocking_reasons),
55
+ "is_terminal": self.is_terminal,
56
+ }
57
+
58
+
59
+ def multi_agent_contract(
60
+ *,
61
+ result: Any,
62
+ goal: str,
63
+ run_id: Optional[str] = None,
64
+ current_role: Optional[str] = None,
65
+ blocking_reasons: Optional[List[str]] = None,
66
+ ) -> Dict[str, Any]:
67
+ return AgentRunContract(
68
+ run_id=run_id,
69
+ agent_id=getattr(result, "agent_id", "agent:executor"),
70
+ runtime="multi_agent",
71
+ mode=getattr(result, "mode", "simulation"),
72
+ status=getattr(result, "status", "unknown"),
73
+ goal=goal,
74
+ roles=list(getattr(result, "roles_run", []) or []),
75
+ current_role=current_role,
76
+ retries=int(getattr(result, "retries", 0) or 0),
77
+ timeline=list(getattr(result, "timeline", []) or []),
78
+ artifacts=[],
79
+ blocking_reasons=list(blocking_reasons or []),
80
+ ).as_dict()
81
+
82
+
83
+ def single_agent_contract(
84
+ *,
85
+ ctx: Any,
86
+ goal: str,
87
+ run_id: Optional[str] = None,
88
+ agent_id: str = "agent:single",
89
+ mode: str = "llm",
90
+ blocking_reasons: Optional[List[str]] = None,
91
+ ) -> Dict[str, Any]:
92
+ state = getattr(getattr(ctx, "state", None), "value", getattr(ctx, "state", "unknown"))
93
+ status = "done" if state == "DONE" else "failed" if state == "FAILED" else str(state or "unknown").lower()
94
+ return AgentRunContract(
95
+ run_id=run_id,
96
+ agent_id=agent_id,
97
+ runtime="single_agent",
98
+ mode=mode,
99
+ status=status,
100
+ goal=goal,
101
+ roles=["planner", "executor", "critic"],
102
+ current_role=str(state or ""),
103
+ retries=int(getattr(ctx, "retry_count", 0) or 0),
104
+ timeline=list(getattr(ctx, "transcript", []) or []),
105
+ artifacts=[],
106
+ blocking_reasons=list(blocking_reasons or []),
107
+ ).as_dict()
@@ -18,8 +18,10 @@ from dataclasses import dataclass, field
18
18
  from datetime import datetime
19
19
  from typing import Any, Callable, Dict, List, Optional
20
20
 
21
+ from .contracts import multi_agent_contract
21
22
 
22
- MULTI_AGENT_VERSION = "7.1.0"
23
+
24
+ MULTI_AGENT_VERSION = "7.3.0"
23
25
 
24
26
  AGENT_ROLES = ("researcher", "planner", "executor", "reviewer", "release")
25
27
  CORE_PIPELINE = ("planner", "executor", "reviewer")
@@ -288,7 +290,7 @@ class AgentRunResult:
288
290
  mode: str = "simulation"
289
291
 
290
292
  def as_dict(self) -> Dict[str, Any]:
291
- return {
293
+ payload = {
292
294
  "agent_id": self.agent_id,
293
295
  "mode": self.mode,
294
296
  "status": self.status,
@@ -305,6 +307,8 @@ class AgentRunResult:
305
307
  "plan_review": self.plan_review,
306
308
  "memory_snapshots": self.memory_snapshots,
307
309
  }
310
+ payload["contract"] = multi_agent_contract(result=self, goal=self.output)
311
+ return payload
308
312
 
309
313
 
310
314
  def default_role_runner(
@@ -1,3 +1,3 @@
1
1
  """Lattice AI - modular server package."""
2
2
 
3
- __version__ = "7.1.0"
3
+ __version__ = "7.3.0"
@@ -192,4 +192,16 @@ def create_agents_router(
192
192
  # A pre_run hook gated this run (e.g. a policy/permission hook).
193
193
  raise HTTPException(status_code=403, detail=str(exc)) from exc
194
194
 
195
+ @router.post("/agents/api/run/preview")
196
+ async def agent_run_preview(req: AgentRunRequest, request: Request):
197
+ require_user(request)
198
+ scope = gate_read(request)
199
+ return runtime.preview(
200
+ req.goal,
201
+ scope=scope,
202
+ roles=req.roles or None,
203
+ inputs=req.inputs,
204
+ max_retries=req.max_retries,
205
+ )
206
+
195
207
  return router
@@ -25,6 +25,8 @@ from latticeai.services.tool_dispatch import (
25
25
  check_tool_role as _check_tool_role,
26
26
  get_tool_permission,
27
27
  list_tool_permissions,
28
+ tool_registry_diagnostics,
29
+ tool_registry_manifest,
28
30
  )
29
31
  from latticeai.services.router_context import ToolRouterContext
30
32
  from latticeai.services.p_reinforce import BRAIN_DIR
@@ -602,6 +604,18 @@ def create_tools_router(
602
604
  """
603
605
  require_user(request)
604
606
  return {"status": "ok", "permissions": list_tool_permissions()}
607
+
608
+ @api_router.get("/tools/registry")
609
+ async def tools_registry(request: Request):
610
+ """Full ToolRegistry contract: handlers, governance, catalog, diagnostics."""
611
+ require_user(request)
612
+ return tool_registry_manifest()
613
+
614
+ @api_router.get("/tools/registry/diagnostics")
615
+ async def tools_registry_diagnostics(request: Request):
616
+ """Small drift check for CI/admin runtime readiness views."""
617
+ require_user(request)
618
+ return {"status": "ok", "diagnostics": tool_registry_diagnostics()}
605
619
 
606
620
 
607
621
  # ── MCP / skills / plugins router (latticeai.api.mcp, v1.3.0) ────────────────