ltcai 8.2.0 → 8.4.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 (46) hide show
  1. package/README.md +27 -19
  2. package/docs/CHANGELOG.md +59 -0
  3. package/docs/COMMUNITY_AND_PLUGINS.md +43 -0
  4. package/docs/DEVELOPMENT.md +8 -8
  5. package/docs/LEGACY_COMPATIBILITY.md +22 -2
  6. package/docs/ONBOARDING.md +38 -0
  7. package/docs/TRUST_MODEL.md +1 -1
  8. package/docs/WHY_LATTICE.md +5 -4
  9. package/docs/kg-schema.md +3 -3
  10. package/lattice_brain/__init__.py +1 -1
  11. package/lattice_brain/graph/ingest.py +40 -8
  12. package/lattice_brain/runtime/agent_runtime.py +22 -37
  13. package/lattice_brain/runtime/multi_agent.py +1 -1
  14. package/lattice_brain/workflow.py +26 -1
  15. package/latticeai/__init__.py +1 -1
  16. package/latticeai/api/chat.py +230 -51
  17. package/latticeai/api/knowledge_graph.py +33 -0
  18. package/latticeai/api/local_files.py +2 -0
  19. package/latticeai/api/tools.py +1 -0
  20. package/latticeai/api/workflow_designer.py +5 -4
  21. package/latticeai/core/legacy_compatibility.py +174 -0
  22. package/latticeai/core/marketplace.py +1 -1
  23. package/latticeai/core/workspace_os.py +1 -1
  24. package/latticeai/services/architecture_readiness.py +37 -3
  25. package/latticeai/services/model_catalog.py +6 -0
  26. package/latticeai/services/model_engines.py +12 -21
  27. package/latticeai/services/model_runtime.py +17 -23
  28. package/latticeai/services/product_readiness.py +45 -14
  29. package/llm_router.py +7 -28
  30. package/mcp_registry.py +7 -24
  31. package/package.json +1 -1
  32. package/scripts/pts-claudecode-discord-bridge.mjs +2 -1
  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 +10 -10
  37. package/static/app/assets/{Act-D9jIknFd.js → Act-D5mo4tE4.js} +1 -1
  38. package/static/app/assets/{Brain-CFOtWbPN.js → Brain-BVWyQw8A.js} +1 -1
  39. package/static/app/assets/{Capture-Q4WYzwr5.js → Capture-C1R6GT0t.js} +1 -1
  40. package/static/app/assets/{Library-C5Q2yWee.js → Library-C2wIxpTs.js} +1 -1
  41. package/static/app/assets/{System-BLbjdr1_.js → System-DE5GRyQR.js} +1 -1
  42. package/static/app/assets/{index-BqammyNu.js → index-CoiuIFFP.js} +3 -3
  43. package/static/app/assets/{primitives-Br8uSfZ4.js → primitives-BdsUNXa6.js} +1 -1
  44. package/static/app/assets/{textarea-BnhNs1_X.js → textarea-e7qaj6Hm.js} +1 -1
  45. package/static/app/index.html +1 -1
  46. package/static/sw.js +1 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Lattice AI
2
2
 
3
- **Lattice AI 8.0 is the local-first Digital Brain platform with hardened runtime architecture. It keeps your knowledge durable across any AI model, with AgentRuntime, ToolRegistry, central Config, and Knowledge Graph stability tracked by explicit release contracts.**
3
+ **Lattice AI 8.4.0 is the local-first Digital Brain platform with action-aware Brain Chat that creates files through the governed workspace file tool instead of only returning code.**
4
4
 
5
5
  **Lattice AI는 모델이 바뀌어도 내 지식과 맥락을 보존하는 로컬 우선 AI 브레인입니다.**
6
6
 
@@ -11,7 +11,7 @@ downloads, update checks, and other external communication happen only after
11
11
  explicit consent.
12
12
 
13
13
  It is not a ChatGPT clone, a model launcher, a graph database, or a note app.
14
- It is the finished private AI memory layer wrapped in a Living Brain experience — now with the 8.0 runtime architecture contract behind it.
14
+ It is the finished private AI memory layer wrapped in a Living Brain experience — now with an 8.4.0 action-readiness contract behind chat-to-tool file creation, onboarding, orchestration, graph ingestion, and the plugin/community path.
15
15
 
16
16
  [![PyPI Version](https://img.shields.io/pypi/v/ltcai?label=PyPI)](https://pypi.org/project/ltcai/)
17
17
  [![npm Version](https://img.shields.io/npm/v/ltcai?label=npm)](https://www.npmjs.com/package/ltcai)
@@ -64,9 +64,10 @@ You need Lattice AI when:
64
64
 
65
65
  ## Living Brain Flow
66
66
 
67
- The screenshots below are release evidence captures. The 8.1 line keeps the
68
- first-run flow and refreshes Brain Home so the living Brain, recent memory,
69
- next action, and composer are understandable in one glance.
67
+ The screenshots below are the latest checked-in visual evidence captures from
68
+ the 8.2 Brain Home release. The 8.3 line keeps that first-run flow focused on
69
+ the Brain while adding clearer onboarding, stronger workflow/runtime
70
+ boundaries, and better ingestion proof behind the scenes.
70
71
 
71
72
  ### 1. Wake Brain
72
73
 
@@ -203,27 +204,32 @@ See [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) for developer workflow details.
203
204
 
204
205
  ## Current Release
205
206
 
206
- The current release is **8.2.0 — Brain Brief**:
207
+ The current release is **8.4.0 — Action-Aware Brain Chat**:
207
208
 
208
- - Brain Home now includes an evidence-backed Brain Brief that says what to notice, why it is trustworthy, and what to do next.
209
- - The brief is generated by `MemoryService` from real workspace memory, conversation, graph, vector, and source-health signals instead of frontend-only placeholders.
210
- - Empty Brain states stay honest: the UI suggests adding a source or asking a first question without claiming model-continuity proof.
211
- - Alive Brain states surface recall evidence, connected topics, model-continuity verification, and backup management as direct actions.
212
- - The 8.0 runtime architecture contract remains active: AgentRuntime, ToolRegistry, Config, server decomposition, and KG hardening stay machine-checkable through readiness gates.
213
- - CI and release workflows continue to run frontend lint/typecheck/build gates plus product readiness before a tag can be treated as release-safe.
209
+ - Brain Chat now routes explicit file create/write/save/edit requests through
210
+ the governed workspace file tool instead of treating them as ordinary prose
211
+ generation.
212
+ - Requests such as `hello.md 파일 만들어줘` are executed through
213
+ `write_file`/`edit_file` style tool paths, and created artifacts are returned
214
+ in the chat response.
215
+ - Normal Q&A stays on the low-friction `/chat` generation path; only explicit
216
+ side-effect requests move into the tool-running agent loop.
217
+ - The release keeps the 8.3.0 managed legacy inventory, AgentRuntime/workflow
218
+ boundaries, unified graph ingestion, and workspace-safe duplicate content
219
+ protections active.
214
220
 
215
- Expected artifacts for 8.2.0 release must use exact filenames:
221
+ Expected artifacts for 8.4.0 release must use exact filenames:
216
222
 
217
- - `dist/ltcai-8.2.0-py3-none-any.whl`
218
- - `dist/ltcai-8.2.0.tar.gz`
219
- - `ltcai-8.2.0.tgz`
220
- - `dist/ltcai-8.2.0.vsix`
221
- - `src-tauri/target/release/bundle/dmg/Lattice AI_8.2.0_aarch64.dmg`
223
+ - `dist/ltcai-8.4.0-py3-none-any.whl`
224
+ - `dist/ltcai-8.4.0.tar.gz`
225
+ - `ltcai-8.4.0.tgz`
226
+ - `dist/ltcai-8.4.0.vsix`
227
+ - `src-tauri/target/release/bundle/dmg/Lattice AI_8.4.0_aarch64.dmg`
222
228
 
223
229
  Do not use wildcard artifact uploads. Package registry publishing remains owner-run.
224
230
 
225
231
  See [docs/ROADMAP_RECOMMENDATIONS.md](docs/ROADMAP_RECOMMENDATIONS.md) for the
226
- strategic roadmap slices applied through 8.2.0 and the follow-up tracks.
232
+ strategic roadmap slices applied through 8.4.0 and the follow-up tracks.
227
233
 
228
234
  ## Known Limitations
229
235
 
@@ -240,6 +246,8 @@ strategic roadmap slices applied through 8.2.0 and the follow-up tracks.
240
246
 
241
247
  | Version | Theme |
242
248
  | --- | --- |
249
+ | 8.4.0 | Action-Aware Brain Chat: explicit file create/write/save/edit requests from Brain Chat route into the governed workspace file tool so files are actually created instead of returned as code-only answers |
250
+ | 8.3.0 | Orchestrated Brain Readiness: managed legacy shim inventory, stronger AgentRuntime/workflow boundaries, unified graph ingestion, workspace-safe duplicate content, first-run onboarding, and explicit community/plugin growth path |
243
251
  | 8.2.0 | Brain Brief: evidence-backed home briefing, honest empty-state guidance, recall/graph/model-proof next actions, and continued model/workspace runtime extraction |
244
252
  | 8.1.0 | Intuitive Brain Home: living Brain, recent memory, connected topic, next action, and composer are visible in one product-first screen with refreshed 8.1.0 evidence and artifacts |
245
253
  | 8.0.0 | Runtime Architecture Contract: AgentRuntime, ToolRegistry, central Config, server decomposition, and KG hardening are captured as machine-checkable release boundaries with exact 8.0.0 artifacts |
package/docs/CHANGELOG.md CHANGED
@@ -8,6 +8,65 @@ existed at that release.
8
8
 
9
9
  No unreleased changes.
10
10
 
11
+ ## [8.4.0] - 2026-07-01
12
+
13
+ ### Added
14
+ - Added a chat-to-agent file action gate so explicit file create/write/save/edit
15
+ requests from Brain Chat execute through the governed workspace file tool.
16
+ - Added regression coverage that verifies `/chat` routes file creation intent to
17
+ the workspace file tool and returns created artifact metadata.
18
+
19
+ ### Changed
20
+ - Kept ordinary Q&A on `/chat` while routing only explicit side-effect file
21
+ requests into the planner/executor/reviewer tool loop.
22
+ - Synchronized package/runtime/static/Tauri metadata and current-release docs to
23
+ 8.4.0.
24
+
25
+ ### Fixed
26
+ - Allowed literal `/chat` file writes with user-provided content to execute
27
+ before model loading, while still using a loaded model when content must be
28
+ synthesized.
29
+ - Narrowed file target and content parsing to avoid treating surrounding prose
30
+ as the workspace path or literal file body.
31
+ - Restored model loading dependency exports after the runtime/loading split so
32
+ `/models/load` can prepare local MLX models again.
33
+ - Added the common Gemma 4 26B shorthand alias to the canonical
34
+ `mlx-community/gemma-4-26b-a4b-it-4bit` model id.
35
+ - Updated the local server launcher to run `python -m uvicorn` from the active
36
+ virtual environment, avoiding stale console-script interpreter bindings.
37
+
38
+ ## [8.3.0] - 2026-07-01
39
+
40
+ ### Added
41
+ - Added a managed legacy compatibility report for remaining root shims,
42
+ including owners, replacements, reasons, removal phases, and readiness
43
+ metrics.
44
+ - Added AgentRuntime/workflow maturity evidence through lifecycle helper reuse,
45
+ legacy event compatibility, WorkflowEngine boundary/config inspection, and
46
+ centralized legacy workflow step projection.
47
+ - Added graph ingestion coverage for upload-to-pipeline behavior and
48
+ workspace-safe duplicate content.
49
+ - Added 8.3.0 onboarding and community/plugin growth documentation.
50
+
51
+ ### Changed
52
+ - Routed `/knowledge-graph/ingest` through the unified `IngestionPipeline` when
53
+ available, preserving provenance and hook lifecycle behavior for MCP notes and
54
+ messages.
55
+ - Isolated text/web/note graph node identity by workspace while preserving the
56
+ content hash used for duplicate detection.
57
+ - Converted `mcp_registry.py` and `llm_router.py` into physical module aliases
58
+ for their current implementations.
59
+ - Improved upload client error handling so failed uploads cannot look
60
+ successful.
61
+ - Synchronized package/runtime/static/Tauri metadata and current-release docs to
62
+ 8.3.0.
63
+
64
+ ### Upgrade Notes
65
+ - Existing legacy-global text/web/note graph nodes are not rewritten in place.
66
+ Re-ingesting the same content with a workspace id can create a separate
67
+ workspace-scoped node; re-index existing sources after upgrading when you want
68
+ provenance to converge on workspace scopes.
69
+
11
70
  ## [8.2.0] - 2026-06-27
12
71
 
13
72
  ### Added
@@ -0,0 +1,43 @@
1
+ # Community And Plugins
2
+
3
+ Current release: **8.4.0 - Action-Aware Brain Chat**.
4
+
5
+ LatticeAI 8.4.0 defines the path from a strong local-first framework to a
6
+ product ecosystem. The immediate goal is small and practical: make it clear how
7
+ contributors can extend the Brain without weakening local-first trust,
8
+ workspace scoping, or release quality.
9
+
10
+ ## Community Entry Points
11
+
12
+ - Start with the product docs: `README.md`, `docs/WHY_LATTICE.md`, and
13
+ `docs/ONBOARDING.md`.
14
+ - Use `docs/DEVELOPMENT.md` for local setup, validation, and release gates.
15
+ - Use `docs/PLUGIN_SDK.md` for plugin manifests, compatibility, permissions,
16
+ lifecycle state, catalog endpoints, and install flows.
17
+ - Use `plugins/hello-world/plugin.json` as the minimum working plugin shape.
18
+
19
+ ## Plugin Direction
20
+
21
+ Plugins should add one bounded capability at a time:
22
+
23
+ - new ingestion sources that still route through `IngestionPipeline`;
24
+ - graph-aware tools that preserve workspace scoping;
25
+ - workflow templates that declare permissions explicitly;
26
+ - editor or automation helpers that keep secrets out of logs and audit payloads;
27
+ - local-first integrations that require explicit opt-in before network access.
28
+
29
+ ## Guardrails
30
+
31
+ - No plugin should bypass the ToolRegistry, permission model, or audit hooks.
32
+ - No plugin should write graph data without provenance.
33
+ - No plugin should assume a cloud model, remote registry, or network connection.
34
+ - Compatibility requirements belong in `lattice_version`, and examples should
35
+ use current host-compatible values.
36
+
37
+ ## 8.4.0 Ecosystem Tasks
38
+
39
+ - Keep the hello-world plugin valid and boring.
40
+ - Expand examples only when they exercise real extension seams.
41
+ - Add tests before treating a plugin API as stable.
42
+ - Prefer documentation that helps a first contributor build one useful plugin
43
+ over broad ecosystem promises.
@@ -1,10 +1,10 @@
1
1
  # Lattice AI Development
2
2
 
3
- Current release: **8.2.0 - Brain Brief**.
3
+ Current release: **8.4.0 - Action-Aware Brain Chat**.
4
4
 
5
5
  This document is for contributors working on the local-first Digital Brain
6
6
  codebase. Product positioning and quick start stay in `README.md`; release
7
- history is intentionally limited to 7.0.0-8.2.0 in `docs/CHANGELOG.md` and
7
+ history is intentionally limited to 7.0.0-8.4.0 in `docs/CHANGELOG.md` and
8
8
  `RELEASE.md`.
9
9
 
10
10
  ## Product Contract
@@ -112,10 +112,10 @@ For user-facing, API, runtime, release, or packaging changes, check:
112
112
  Release/publish examples must use exact target-version filenames. Do not
113
113
  document wildcard artifact upload commands.
114
114
 
115
- For 8.2.0 release work, exact artifacts are:
115
+ For 8.4.0 release work, exact artifacts are:
116
116
 
117
- - `dist/ltcai-8.2.0-py3-none-any.whl`
118
- - `dist/ltcai-8.2.0.tar.gz`
119
- - `ltcai-8.2.0.tgz`
120
- - `dist/ltcai-8.2.0.vsix`
121
- - `src-tauri/target/release/bundle/dmg/Lattice AI_8.2.0_aarch64.dmg`
117
+ - `dist/ltcai-8.4.0-py3-none-any.whl`
118
+ - `dist/ltcai-8.4.0.tar.gz`
119
+ - `ltcai-8.4.0.tgz`
120
+ - `dist/ltcai-8.4.0.vsix`
121
+ - `src-tauri/target/release/bundle/dmg/Lattice AI_8.4.0_aarch64.dmg`
@@ -1,12 +1,21 @@
1
1
  # Legacy Compatibility Map
2
2
 
3
- Current release: **8.2.0 - Brain Brief**.
3
+ Current release: **8.4.0 - Action-Aware Brain Chat**.
4
4
 
5
5
  Lattice AI is moving toward a smaller, modular architecture centered on
6
6
  `lattice_brain`, `latticeai.services`, `latticeai.api`, and `latticeai.runtime`.
7
7
  Some root-level modules remain packaged for compatibility with older imports,
8
8
  CLI entrypoints, or extension workflows. Their presence does not define the
9
- current 8.2.0 architecture.
9
+ current 8.4.0 architecture.
10
+
11
+ 8.4.0 also tracks the inner compatibility layers that sit below the root
12
+ modules. The managed inventory lives in `latticeai.core.legacy_compatibility`
13
+ and groups shims by layer:
14
+
15
+ - `root`: historical repo-root imports such as `knowledge_graph.py`.
16
+ - `brain-flat`: pre-graph-package imports such as `lattice_brain.store`.
17
+ - `deprecated-namespace`: older `latticeai.brain.*` package imports.
18
+ - `service-alias`: service paths that now alias Brain runtime modules.
10
19
 
11
20
  ## Current Policy
12
21
 
@@ -17,6 +26,9 @@ current 8.2.0 architecture.
17
26
  - Add deprecation notes before removal.
18
27
  - Avoid breaking package users during a minor release.
19
28
  - Do not silently remove rollback, backup, restore, or migration paths.
29
+ - Track every remaining shim through `latticeai.core.legacy_compatibility` so
30
+ release readiness can report owner, replacement, reason, removal phase, and
31
+ missing files.
20
32
 
21
33
  ## Root Module Map
22
34
 
@@ -35,6 +47,14 @@ current 8.2.0 architecture.
35
47
  | `setup_wizard.py` | setup and model recommendation services | Compatibility for first-run recommendation calls |
36
48
  | `server.py` | lazy proxy to `latticeai.server_app` / `latticeai.app_factory` | Preserves historical `server.app` imports without import-time construction |
37
49
 
50
+ ## Inner Shim Layers
51
+
52
+ | Legacy layer | Example module | Current home / direction | Why it remains |
53
+ | --- | --- | --- | --- |
54
+ | `brain-flat` | `lattice_brain.store`, `lattice_brain.ingest`, `lattice_brain.retrieval` | `lattice_brain.graph.*` | Preserves imports from before graph modules were split into a package |
55
+ | `deprecated-namespace` | `latticeai.brain.store`, `latticeai.brain.ingest` | `lattice_brain.graph.*` | Preserves the old application-package Brain namespace |
56
+ | `service-alias` | `latticeai.services.agent_runtime` | `lattice_brain.runtime.agent_runtime` | Preserves service-layer imports while runtime ownership sits in Brain Core |
57
+
38
58
  ## Packaging Notes
39
59
 
40
60
  `pyproject.toml` and `package.json` still include several root modules because
@@ -0,0 +1,38 @@
1
+ # Lattice AI Onboarding
2
+
3
+ Current release: **8.4.0 - Action-Aware Brain Chat**.
4
+
5
+ The first-run goal is a five-minute path from "I opened the app" to "my Brain
6
+ has a source, a question, and proof." This page is the product contract behind
7
+ that flow; the UI should make these steps obvious without asking a new user to
8
+ read the docs first.
9
+
10
+ ## Five-Minute Flow
11
+
12
+ 1. Wake the Brain and confirm the local owner profile.
13
+ 2. Let Lattice inspect local model/runtime readiness.
14
+ 3. Pick the recommended Brain voice or skip model loading.
15
+ 4. Add one source: upload a file, connect a folder, save a note, or capture a
16
+ browser/source event.
17
+ 5. Ask one grounded question and inspect the answer proof.
18
+ 6. Open the Knowledge Graph only when the user wants source-level evidence.
19
+ 7. Back up the Brain once useful memory exists.
20
+
21
+ ## Product Promises
22
+
23
+ - The user starts in the Brain, not in an admin dashboard.
24
+ - Empty states suggest one concrete next action without claiming proof that does
25
+ not exist yet.
26
+ - Upload, note, browser, and message ingestion all converge through the unified
27
+ ingestion pipeline when it is available.
28
+ - Workspace-scoped content must not leak or overwrite another workspace's graph
29
+ metadata.
30
+ - Advanced runtime, workflow, plugin, and admin surfaces are available, but they
31
+ do not block the first conversation.
32
+
33
+ ## Release Gate
34
+
35
+ 8.4.0 treats onboarding as a release gate, not marketing copy. The current
36
+ machine-checkable product readiness report requires this five-minute contract,
37
+ the Brain Home surface, setup helpers, graph ingestion tests, and exact release
38
+ artifact documentation before the release can be called complete.
@@ -1,6 +1,6 @@
1
1
  # Lattice AI Trust Model
2
2
 
3
- Current release: **8.2.0 - Brain Brief**.
3
+ Current release: **8.4.0 - Action-Aware Brain Chat**.
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: **8.2.0 - Brain Brief**.
3
+ Current release: **8.4.0 - Action-Aware Brain Chat**.
4
4
 
5
5
  **Lattice AI is a local-first Digital Brain that keeps your knowledge durable
6
6
  across any AI model.**
@@ -32,9 +32,10 @@ The graph matters, but it is not the product identity. The product identity is a
32
32
  local-first Digital Brain: a place where the user can talk, add sources, watch
33
33
  memory grow, and inspect the Knowledge Graph when they need proof.
34
34
 
35
- In 8.2.0 the first screen is intentionally not a dashboard. The living Brain,
36
- conversation composer, and evidence-backed Brain Brief appear together so the
37
- user immediately knows what matters and what to do next.
35
+ In 8.4.0 the first screen is intentionally not a dashboard. The living Brain,
36
+ conversation composer, evidence-backed Brain Brief, and five-minute onboarding
37
+ path appear together so the user immediately knows what matters, what to add,
38
+ and why the answer is grounded in local memory.
38
39
 
39
40
  ## Practical Reasons To Use It
40
41
 
package/docs/kg-schema.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Knowledge Graph Schema
2
2
 
3
- Current release: **8.2.0 - Brain Brief**.
3
+ Current release: **8.4.0 - Action-Aware Brain Chat**.
4
4
 
5
5
  명세 출처: `lattice_ai_full_spec.pptx` 슬라이드 20·21·22
6
6
  구현: `kg_schema.py`
@@ -90,7 +90,7 @@ Edge {
90
90
 
91
91
  ## Current Knowledge Graph First 엔티티/관계
92
92
 
93
- 8.2.0 은 "모든 데이터 소스가 Knowledge Graph 로 수렴한다"는 원칙을 1급 스키마로
93
+ 8.4.0 은 "모든 데이터 소스가 Knowledge Graph 로 수렴한다"는 원칙을 1급 스키마로
94
94
  승격한다. 아래 타입은 **추가형(additive)**이다 — 기존 enum/legacy 매핑을 깨지 않고
95
95
  `from_legacy` 가 무손실로 정규화하며, 알 수 없는 타입은 여전히 `CONCEPT`/`MENTIONS` 로
96
96
  폴백한다. 스키마는 **확장 가능**하게 유지한다: 새 도메인 엔티티는 enum 멤버 1개 +
@@ -138,7 +138,7 @@ provenance(source_type, source_uri, content_hash, captured_at, modified_at,
138
138
  - provenance 는 노드 `metadata.provenance` 에 임베드되며, 동시에 감사 가능한
139
139
  `ingestion_provenance` 테이블에 기록된다 (`KnowledgeGraphStore.get_provenance(node_id)`).
140
140
 
141
- 구현: `latticeai/services/ingestion.py` (`IngestionPipeline`) 가 단일 진입점이며,
141
+ 구현: `lattice_brain/ingestion.py` (`IngestionPipeline`) 가 단일 진입점이며,
142
142
  파일/로컬폴더/URL/브라우저 탭/텍스트를 모두 이 형태로 정규화한다.
143
143
 
144
144
  ---
@@ -26,7 +26,7 @@ from .storage import (
26
26
  storage_from_env,
27
27
  )
28
28
 
29
- __version__ = "8.2.0"
29
+ __version__ = "8.4.0"
30
30
 
31
31
  __all__ = [
32
32
  "AgentRuntime",
@@ -490,8 +490,6 @@ class KnowledgeGraphIngestMixin:
490
490
  is derived from (source_type, source_uri | content_hash) so re-ingesting
491
491
  the same origin reuses the same SOURCE node (idempotent).
492
492
  """
493
- key = source_uri or content_hash or content_node_id
494
- source_id = f"source:{_sha256_text(f'{source_type}|{key}')[:24]}"
495
493
  meta = {
496
494
  "source_type": source_type,
497
495
  "source_uri": source_uri,
@@ -499,6 +497,9 @@ class KnowledgeGraphIngestMixin:
499
497
  "captured_at": captured_at or _now(),
500
498
  **(extra or {}),
501
499
  }
500
+ key = source_uri or content_hash or content_node_id
501
+ source_scope = meta.get("workspace_id") or "legacy-global"
502
+ source_id = f"source:{_sha256_text(f'{source_scope}|{source_type}|{key}')[:24]}"
502
503
  label = title or source_uri or source_type
503
504
  self._upsert_node(
504
505
  conn,
@@ -551,7 +552,8 @@ class KnowledgeGraphIngestMixin:
551
552
  )
552
553
  captured_at = captured_at or _now()
553
554
  content_hash = _sha256_text(f"{source_type}|{source_uri or ''}|{text}")
554
- content_id = f"webdoc:{content_hash[:24]}"
555
+ identity_hash = _sha256_text(f"{workspace_id or 'legacy-global'}|{content_hash}")
556
+ content_id = f"webdoc:{identity_hash[:24]}"
555
557
  full_text = f"{title}\n{text}"
556
558
  node_meta = {
557
559
  "source_type": source_type,
@@ -581,6 +583,8 @@ class KnowledgeGraphIngestMixin:
581
583
  summary=(text or title)[:500],
582
584
  metadata=node_meta,
583
585
  raw=node_meta,
586
+ owner=owner,
587
+ workspace_id=workspace_id,
584
588
  )
585
589
  # ── SOURCE 노드 + indexed_from 엣지 (출처 추적) ──────────────────
586
590
  source_node_id = self._attach_source_node(
@@ -597,13 +601,27 @@ class KnowledgeGraphIngestMixin:
597
601
  if owner:
598
602
  person_id = f"person:{_slug(owner)}"
599
603
  self._upsert_node(
600
- conn, person_id, "Person", owner, metadata={"email": owner}
604
+ conn,
605
+ person_id,
606
+ "Person",
607
+ owner,
608
+ metadata={"email": owner, "workspace_id": workspace_id},
609
+ owner=owner,
610
+ workspace_id=workspace_id,
601
611
  )
602
612
  self._upsert_edge(conn, person_id, content_id, "업로드함", weight=1.0)
603
613
  # ── 대화 연결 ───────────────────────────────────────────────────
604
614
  if conversation_id:
605
615
  conv_id = f"conversation:{_slug(conversation_id)}"
606
- self._upsert_node(conn, conv_id, "Chat", conversation_id)
616
+ self._upsert_node(
617
+ conn,
618
+ conv_id,
619
+ "Chat",
620
+ conversation_id,
621
+ metadata={"conversation_id": conversation_id, "workspace_id": workspace_id},
622
+ owner=owner,
623
+ workspace_id=workspace_id,
624
+ )
607
625
  self._upsert_edge(conn, conv_id, content_id, "언급함", weight=0.8)
608
626
  # ── RAG 청크 ────────────────────────────────────────────────────
609
627
  for index, chunk in enumerate(_chunks(text)):
@@ -615,7 +633,9 @@ class KnowledgeGraphIngestMixin:
615
633
  "Chunk",
616
634
  f"{title} chunk {index + 1}",
617
635
  summary=chunk[:500],
618
- metadata={"index": index, "source_node": content_id},
636
+ metadata={"index": index, "source_node": content_id, "workspace_id": workspace_id},
637
+ owner=owner,
638
+ workspace_id=workspace_id,
619
639
  )
620
640
  self._upsert_chunk(
621
641
  conn,
@@ -636,7 +656,13 @@ class KnowledgeGraphIngestMixin:
636
656
  cid,
637
657
  node_t,
638
658
  concept,
639
- metadata={"auto_extracted": True, "source_type": source_type},
659
+ metadata={
660
+ "auto_extracted": True,
661
+ "source_type": source_type,
662
+ "workspace_id": workspace_id,
663
+ },
664
+ owner=owner,
665
+ workspace_id=workspace_id,
640
666
  )
641
667
  self._upsert_edge(conn, content_id, cid, "포함함", weight=0.8)
642
668
  for triple in triples:
@@ -662,8 +688,14 @@ class KnowledgeGraphIngestMixin:
662
688
  sem_type,
663
689
  sem_title,
664
690
  summary=item["summary"],
665
- metadata={"auto_extracted": True, "source_node": content_id},
691
+ metadata={
692
+ "auto_extracted": True,
693
+ "source_node": content_id,
694
+ "workspace_id": workspace_id,
695
+ },
666
696
  raw=item,
697
+ owner=owner,
698
+ workspace_id=workspace_id,
667
699
  )
668
700
  self._upsert_edge(conn, content_id, sem_id, "포함함", weight=0.9)
669
701
 
@@ -266,7 +266,7 @@ class AgentRuntime:
266
266
  unknown_roles = [role for role in requested_roles if role not in AGENT_ROLES]
267
267
  health = self.health()
268
268
  goal_ready = bool(str(goal or "").strip())
269
- retry_budget = max(0, min(int(max_retries or 0), self._max_retries_cap))
269
+ retry_budget = self._clamp_retries(max_retries)
270
270
  blocking_reasons: List[str] = []
271
271
  if not goal_ready:
272
272
  blocking_reasons.append("goal is required")
@@ -324,6 +324,7 @@ class AgentRuntime:
324
324
  def events(self, run_id: str, *, scope: Optional[str] = None) -> Dict[str, Any]:
325
325
  run = self._store.get_agent_run(run_id, workspace_id=scope)
326
326
  status = str(run.get("status") or "")
327
+ contract = extract_contract(run)
327
328
  return {
328
329
  "run_id": run_id,
329
330
  "status": status,
@@ -331,7 +332,7 @@ class AgentRuntime:
331
332
  "current_role": run.get("current_role"),
332
333
  "timeline": run.get("timeline") or [],
333
334
  "handoffs": run.get("handoffs") or [],
334
- "contract": contract_view(run),
335
+ "contract": contract_view(contract) if contract is not None else None,
335
336
  }
336
337
 
337
338
  # ── execution ─────────────────────────────────────────────────────────
@@ -360,6 +361,9 @@ class AgentRuntime:
360
361
  raise PermissionError(pre_dispatch.get("block_reason") or "Agent run blocked by a pre_run hook.")
361
362
  return pre_dispatch
362
363
 
364
+ def _clamp_retries(self, max_retries: int) -> int:
365
+ return max(0, min(int(max_retries or 0), self._max_retries_cap))
366
+
363
367
  @staticmethod
364
368
  def _result_patch(result: Any, goal: str) -> Dict[str, Any]:
365
369
  return {
@@ -488,7 +492,7 @@ class AgentRuntime:
488
492
  workspace_id=scope,
489
493
  inputs=inputs or {},
490
494
  roles=roles or None,
491
- max_retries=max(0, min(int(max_retries or 0), self._max_retries_cap)),
495
+ max_retries=self._clamp_retries(max_retries),
492
496
  )
493
497
  except Exception as exc:
494
498
  failed = self._store.update_agent_run(
@@ -571,25 +575,13 @@ class AgentRuntime:
571
575
  if not str(goal or "").strip():
572
576
  raise ValueError("goal is required")
573
577
 
574
- # ── pre_run hooks ─────────────────────────────────────────────────
575
- # A blocking pre_run hook (e.g. a policy gate) aborts the run before any
576
- # orchestration happens. Hook failures never crash the run (fire_hook
577
- # swallows them); only an explicit block stops it.
578
- pre_dispatch: Optional[Dict[str, Any]] = None
579
- if self._hooks is not None:
580
- pre_dispatch = self._hooks.fire_hook(
581
- "pre_run", "agent.run",
582
- payload={"goal": goal, "roles": roles or None, "max_retries": max_retries},
583
- user_email=user_email, workspace_id=scope,
584
- )
585
- if pre_dispatch.get("blocked"):
586
- self._append_audit_event(
587
- "multi_agent_run_blocked",
588
- user_email=user_email,
589
- reason=pre_dispatch.get("block_reason"),
590
- )
591
- raise PermissionError(pre_dispatch.get("block_reason") or "Agent run blocked by a pre_run hook.")
592
-
578
+ pre_dispatch = self._fire_pre_run(
579
+ goal=goal,
580
+ roles=roles,
581
+ max_retries=max_retries,
582
+ user_email=user_email,
583
+ scope=scope,
584
+ )
593
585
  orchestrator = self._live_orchestrator(user_email, scope)
594
586
  result = orchestrator.run(
595
587
  goal,
@@ -597,7 +589,7 @@ class AgentRuntime:
597
589
  workspace_id=scope,
598
590
  inputs=inputs or {},
599
591
  roles=roles or None,
600
- max_retries=max(0, min(int(max_retries or 0), self._max_retries_cap)),
592
+ max_retries=self._clamp_retries(max_retries),
601
593
  )
602
594
  run = self._store.record_agent_run(
603
595
  agent_id=result.agent_id,
@@ -626,20 +618,13 @@ class AgentRuntime:
626
618
  retries=result.retries,
627
619
  )
628
620
 
629
- # ── post_run hooks ────────────────────────────────────────────────
630
- post_dispatch: Optional[Dict[str, Any]] = None
631
- if self._hooks is not None:
632
- run_id = run.get("id") or run.get("run_id") if isinstance(run, dict) else None
633
- post_dispatch = self._hooks.fire_hook(
634
- "post_run", "agent.run",
635
- payload={
636
- "run_id": run_id,
637
- "agent_id": result.agent_id,
638
- "status": result.status,
639
- "retries": result.retries,
640
- },
641
- user_email=user_email, workspace_id=scope,
642
- )
621
+ run_id = run.get("id") or run.get("run_id") if isinstance(run, dict) else None
622
+ post_dispatch = self._post_run_hooks(
623
+ run_id=run_id,
624
+ result=result,
625
+ user_email=user_email,
626
+ scope=scope,
627
+ )
643
628
 
644
629
  result_payload = result.as_dict()
645
630
  result_payload["contract"] = multi_agent_contract(result=result, goal=goal, run_id=run.get("id") if isinstance(run, dict) else None)
@@ -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 = "8.2.0"
24
+ MULTI_AGENT_VERSION = "8.4.0"
25
25
 
26
26
  AGENT_ROLES = ("researcher", "planner", "executor", "reviewer", "release")
27
27
  CORE_PIPELINE = ("planner", "executor", "reviewer")