ltcai 1.6.0 → 2.0.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 (40) hide show
  1. package/README.md +40 -19
  2. package/docs/CHANGELOG.md +107 -0
  3. package/docs/EDITION_STRATEGY.md +14 -4
  4. package/docs/ENTERPRISE.md +11 -3
  5. package/docs/MULTI_AGENT_RUNTIME.md +410 -0
  6. package/docs/PLUGIN_SDK.md +651 -0
  7. package/docs/REALTIME_COLLABORATION.md +410 -0
  8. package/docs/V2_ARCHITECTURE.md +528 -0
  9. package/docs/WORKFLOW_DESIGNER.md +475 -0
  10. package/latticeai/__init__.py +1 -1
  11. package/latticeai/api/agents.py +98 -0
  12. package/latticeai/api/plugins.py +115 -0
  13. package/latticeai/api/realtime.py +91 -0
  14. package/latticeai/api/workflow_designer.py +207 -0
  15. package/latticeai/core/multi_agent.py +270 -0
  16. package/latticeai/core/plugins.py +400 -0
  17. package/latticeai/core/realtime.py +190 -0
  18. package/latticeai/core/workflow_engine.py +329 -0
  19. package/latticeai/core/workspace_os.py +165 -2
  20. package/latticeai/server_app.py +76 -2
  21. package/latticeai/services/platform_runtime.py +200 -0
  22. package/package.json +17 -2
  23. package/plugins/README.md +35 -0
  24. package/plugins/git-insights/plugin.json +15 -0
  25. package/plugins/hello-world/plugin.json +16 -0
  26. package/plugins/hello-world/skills/hello_skill/SKILL.md +15 -0
  27. package/static/activity.html +70 -0
  28. package/static/admin.html +62 -0
  29. package/static/agents.html +92 -0
  30. package/static/graph.html +7 -1
  31. package/static/lattice-reference.css +184 -0
  32. package/static/platform.css +75 -0
  33. package/static/plugins.html +82 -0
  34. package/static/scripts/admin.js +121 -1
  35. package/static/scripts/graph.js +296 -14
  36. package/static/scripts/platform.js +64 -0
  37. package/static/scripts/workspace.js +107 -10
  38. package/static/workflows.html +121 -0
  39. package/static/workspace.css +73 -0
  40. package/static/workspace.html +18 -2
@@ -0,0 +1,528 @@
1
+ # Lattice AI v2.0 Architecture — Agentic Workspace Platform
2
+
3
+ Lattice AI v2.0.0 turns the local-first Workspace OS into a full **Agentic
4
+ Workspace Platform**: a single FastAPI application in which plugins, designed
5
+ workflows, multi-agent runs, and a realtime collaboration feed all compose over
6
+ the same local-first JSON store and Knowledge Graph.
7
+
8
+ This document describes how the four v2.0 pillars fit together, the small set of
9
+ **additive integration seams** that wire them, the cross-integration matrix that
10
+ results, and the compatibility surfaces that v1.x callers and data keep relying
11
+ on. Every claim below is grounded in the shipping source:
12
+
13
+ - App assembly: `latticeai/server_app.py`
14
+ - Cross-system wiring: `latticeai/services/platform_runtime.py`
15
+ - State + persistence: `latticeai/core/workspace_os.py`
16
+ - Plugin SDK: `latticeai/core/plugins.py`, `latticeai/api/plugins.py`
17
+ - Workflow engine: `latticeai/core/workflow_engine.py`, `latticeai/api/workflow_designer.py`
18
+ - Multi-Agent runtime: `latticeai/core/multi_agent.py`, `latticeai/api/agents.py`
19
+ - Realtime bus: `latticeai/core/realtime.py`, `latticeai/api/realtime.py`
20
+ - Project conventions: `AGENTS.md`
21
+
22
+ All four subsystems share the same design rules from `AGENTS.md`: dependency
23
+ injection, explicit interfaces, small focused modules, registry-based dispatch,
24
+ and composition over global state. None of them import the FastAPI app; each is
25
+ constructed by `server_app.py` and exposed through a router factory.
26
+
27
+ ---
28
+
29
+ ## 1. The Four v2.0 Pillars
30
+
31
+ The platform version is the single source of truth `WORKSPACE_OS_VERSION =
32
+ "2.0.0"` (`latticeai/core/workspace_os.py`). Each pillar module re-declares the
33
+ same version for its own surface (`PLUGIN_SDK_VERSION`, `WORKFLOW_ENGINE_VERSION`,
34
+ `MULTI_AGENT_VERSION`, `REALTIME_VERSION`).
35
+
36
+ ### 1.1 Plugin SDK (`latticeai.core.plugins`)
37
+
38
+ A plugin is a directory under the configured plugins root that ships a
39
+ `plugin.json` manifest and **extends** the existing Skill / Tool / Workflow
40
+ surfaces rather than replacing them. Installed standalone skills keep working
41
+ untouched; a plugin simply *bundles* skills and declares tools / workflow
42
+ templates / actions under one versioned, permissioned unit.
43
+
44
+ Design rules enforced by the module: no import-time I/O (the filesystem is only
45
+ touched on `discover()`), no FastAPI and no globals (lifecycle state lives in the
46
+ Workspace OS store), and permissions are an allow-list — the execution boundary
47
+ refuses any capability a plugin did not declare *and* was not granted.
48
+
49
+ A validated `plugin.json` manifest:
50
+
51
+ ```json
52
+ {
53
+ "id": "release-notes",
54
+ "name": "Release Notes Assistant",
55
+ "version": "1.0.0",
56
+ "description": "Bundles a release-notes skill and a packaging workflow.",
57
+ "author": "you@example.com",
58
+ "lattice_version": ">=2.0.0",
59
+ "permissions": ["read_workspace", "run_skills", "run_workflows"],
60
+ "provides": {
61
+ "skills": ["release-notes"],
62
+ "workflows": ["package-and-tag"],
63
+ "tools": [],
64
+ "actions": []
65
+ },
66
+ "entrypoint": "",
67
+ "homepage": ""
68
+ }
69
+ ```
70
+
71
+ Constraints from `validate_manifest` and the module constants:
72
+
73
+ - `id`: lowercase alphanumeric with `-`/`_`, 2–64 chars (`_ID_RE`).
74
+ - `version`: semantic version (`_SEMVER_RE`).
75
+ - `permissions` ⊆ `PLUGIN_PERMISSIONS` = `read_workspace`, `write_workspace`,
76
+ `read_graph`, `write_graph`, `run_tools`, `run_skills`, `run_workflows`,
77
+ `run_agents`, `network`, `manage_memory`.
78
+ - `provides` keys ⊆ `PLUGIN_PROVIDES` = `skills`, `tools`, `workflows`,
79
+ `actions`; each value is a list.
80
+ - `lattice_version` is a minimum (major must match host, host must be `>=`),
81
+ checked by `is_compatible(required, current=PLUGIN_SDK_VERSION)`.
82
+
83
+ The permissioned execution boundary:
84
+
85
+ ```python
86
+ def execute_action(
87
+ self,
88
+ plugin_id: str,
89
+ action: str,
90
+ args: Optional[Dict[str, Any]] = None,
91
+ *,
92
+ runners: Optional[Dict[str, Callable[..., Any]]] = None,
93
+ ) -> PluginExecutionResult: ...
94
+ ```
95
+
96
+ `execute_action` maps an action to the capability + permission it needs
97
+ (`run_tool→tools/run_tools`, `run_skill→skills/run_skills`,
98
+ `run_workflow→workflows/run_workflows`, `run_agent→agents/run_agents`). It
99
+ returns a `PluginExecutionResult` with `status` in `ok | blocked | error |
100
+ skipped`:
101
+
102
+ - `blocked` if the plugin is not enabled, did not declare the required
103
+ permission, or the permission was not granted at install time.
104
+ - `skipped` if the host injected no runner for the capability (never crashes the
105
+ caller — safe-by-default).
106
+ - `error` if the runner raised; `ok` otherwise.
107
+
108
+ ### 1.2 Workflow Designer (`latticeai.core.workflow_engine`)
109
+
110
+ A workflow is a small directed graph of typed *nodes* starting from a `trigger`.
111
+ `NODE_TYPES` = `trigger`, `tool`, `skill`, `plugin`, `agent`, `condition`,
112
+ `output`. The engine walks the graph from the trigger, dispatching each
113
+ executable node to an injected runner and recording a step-by-step timeline so a
114
+ run can be inspected, replayed, and linked into the Workspace timeline and
115
+ Knowledge Graph.
116
+
117
+ Key safety properties baked into the engine:
118
+
119
+ - **Bounded execution.** `_MAX_STEPS = 100` is a hard cap, so a mis-wired `next`
120
+ cycle can never hang a run; exceeding it records a `guard` error.
121
+ - **Graceful degradation.** A node whose runner family is not wired is recorded
122
+ as `skipped` with a reason rather than failing the whole run; the run status
123
+ becomes `partial`.
124
+ - **No `eval`.** `condition` nodes use `_evaluate_condition`, which compares a
125
+ context value to a literal via a fixed op set (`==`, `!=`, `>`, `<`, `>=`,
126
+ `<=`, `contains`, `truthy`) and fails closed onto the `false` branch.
127
+
128
+ ```python
129
+ class WorkflowEngine:
130
+ def __init__(self, runners: Optional[Dict[str, Callable[..., Any]]] = None): ...
131
+ def run(self, workflow: Dict[str, Any], *, inputs: Optional[Dict[str, Any]] = None) -> WorkflowRun: ...
132
+ ```
133
+
134
+ A run yields a `WorkflowRun` with `status` in `ok | failed | partial`, a
135
+ `timeline`, and `outputs`. `export_workflow` / `import_workflow` provide a
136
+ portable JSON representation (definition only — no run history or scope).
137
+
138
+ ### 1.3 Multi-Agent Runtime 2.0 (`latticeai.core.multi_agent`)
139
+
140
+ v1.x shipped a single-agent state machine (`latticeai.core.agent.AgentRuntime`:
141
+ PLAN → EXECUTE → VERIFY → DONE). v2.0 adds the **orchestration** layer above it:
142
+ a pipeline of named roles that hand off to one another, retry on a failing
143
+ review, and emit a structured timeline that drops straight into the Workspace
144
+ timeline / Knowledge Graph.
145
+
146
+ `AGENT_ROLES` = `researcher`, `planner`, `executor`, `reviewer`, `release`. The
147
+ `CORE_PIPELINE` is `planner → executor → reviewer`; `researcher` and `release`
148
+ are optional stages. Role ids match `DEFAULT_AGENTS` in
149
+ `latticeai/core/workspace_os.py` (`agent:planner`, `agent:executor`,
150
+ `agent:reviewer`, `agent:researcher`, `agent:release`).
151
+
152
+ ```python
153
+ class MultiAgentOrchestrator:
154
+ def __init__(self, role_runner: Optional[Callable[[str, OrchestrationContext], Dict[str, Any]]] = None): ...
155
+ def run(
156
+ self,
157
+ goal: str,
158
+ *,
159
+ user_email: Optional[str] = None,
160
+ workspace_id: Optional[str] = None,
161
+ inputs: Optional[Dict[str, Any]] = None,
162
+ roles: Optional[List[str]] = None,
163
+ max_retries: int = 2,
164
+ ) -> AgentRunResult: ...
165
+ ```
166
+
167
+ Like the v1 runtime, the orchestrator is pure logic over an injected
168
+ `role_runner` port, so it runs with no LLM and no server. `default_role_runner`
169
+ is deterministic and genuinely useful: the `researcher` pulls workspace context,
170
+ the `planner` decomposes the goal, the `executor` carries out steps (and can
171
+ drive an injected `workflow_runner` / `plugin_runner`), and the `reviewer`
172
+ returns `pass` / `retry`. The reviewer can rewind the pipeline to the executor up
173
+ to `max_retries` times; the final `status` is `ok`, `retried_ok`, or `failed`.
174
+
175
+ ### 1.4 Realtime Collaboration (`latticeai.core.realtime`)
176
+
177
+ An in-process pub/sub bus, presence registry, and activity feed delivered over
178
+ Server-Sent Events (SSE). SSE is chosen deliberately: the codebase already
179
+ streams model output over SSE, it needs no extra dependency, and it works through
180
+ the existing single-port local-first deployment.
181
+
182
+ Guarantees from the module:
183
+
184
+ - **Single-user local mode keeps working.** Publishing with zero subscribers is
185
+ a no-op; a `_FEED_LIMIT`-bounded ring buffer is still maintained so a late
186
+ subscriber catches up via `recent()` / a replay tail on `stream()`.
187
+ - **Workspace isolation preserved.** Every event carries `workspace_id`; a
188
+ subscriber only receives events whose workspace is in its allowed scope set.
189
+ A `None` scope (personal / local view) sees unscoped + personal events.
190
+ - **Backpressure-safe.** Per-subscriber queues are bounded (`_QUEUE_MAX`); on
191
+ overflow the oldest event is dropped rather than blocking the publisher.
192
+
193
+ ```python
194
+ class RealtimeBus:
195
+ def publish(self, event: Dict[str, Any]) -> Dict[str, Any]: ...
196
+ def __call__(self, event: Dict[str, Any]) -> Dict[str, Any]: # stable event_sink alias
197
+ return self.publish(event)
198
+ ```
199
+
200
+ ---
201
+
202
+ ## 2. How the Pillars Compose Into One Platform
203
+
204
+ The four pillars are not parallel silos. They are stitched into one platform by
205
+ exactly **three additive seams**, all introduced without changing any existing
206
+ behavior.
207
+
208
+ ### Seam 1 — Two new state keys with deep-merge backfill
209
+
210
+ `WorkspaceOSStore._default_state()` adds two new top-level keys to the local-first
211
+ JSON state: **`plugin_registry`** (an object, mirroring `skill_registry`) and
212
+ **`workflow_runs`** (a list, alongside the existing `workflows`). The default
213
+ state also adds v2.0 feature flags (`plugin_sdk`, `workflow_designer`,
214
+ `multi_agent_runtime`, `realtime_collaboration`) and a `plugins` navigation area.
215
+
216
+ These are safe for existing data because `load_state()` runs `_deep_merge(default,
217
+ loaded)` on every load. `_deep_merge` walks the default tree and fills in any key
218
+ missing from the loaded file while preserving every value already present:
219
+
220
+ ```python
221
+ def _deep_merge(default: Any, loaded: Any) -> Any:
222
+ if isinstance(default, dict) and isinstance(loaded, dict):
223
+ merged = {key: _deep_merge(value, loaded.get(key)) for key, value in default.items()}
224
+ for key, value in loaded.items():
225
+ if key not in merged:
226
+ merged[key] = value
227
+ return merged
228
+ if loaded is None:
229
+ return default
230
+ return loaded
231
+ ```
232
+
233
+ A v1.x `workspace_os.json` that has no `plugin_registry` / `workflow_runs` is
234
+ therefore upgraded *in memory* on first load — the new keys are backfilled with
235
+ their defaults, every pre-existing snapshot, trace, memory, agent run, workflow,
236
+ and skill entry is preserved, and the file is only rewritten on the next normal
237
+ `save_state`. The Plugin SDK lifecycle helpers (`list_plugin_registry`,
238
+ `set_plugin_enabled`, `mark_plugin_installed`, `mark_plugin_uninstalled`) and
239
+ workflow-run helpers (`record_workflow_run`, `list_workflow_runs`) operate on
240
+ these keys, deliberately mirroring the existing skill-registry contract.
241
+
242
+ ### Seam 2 — A single `event_sink` on `record_timeline_event`
243
+
244
+ `WorkspaceOSStore.__init__` takes one optional `event_sink` hook. Every state
245
+ mutation in the store already funnels through `record_timeline_event(area,
246
+ event_type, payload, workspace_id)`. v2.0 adds a single best-effort call at the
247
+ end of that method:
248
+
249
+ ```python
250
+ def record_timeline_event(self, area, event_type, payload, workspace_id=None):
251
+ ...
252
+ state.setdefault("timeline", []).append(event)
253
+ state["timeline"] = state["timeline"][-500:]
254
+ self.save_state(state)
255
+ if self.event_sink is not None:
256
+ try:
257
+ self.event_sink(event)
258
+ except Exception:
259
+ # Realtime delivery is best-effort and must never break a write.
260
+ pass
261
+ return event
262
+ ```
263
+
264
+ In `server_app.py` the bus is constructed first and injected as that sink:
265
+
266
+ ```python
267
+ REALTIME_BUS = RealtimeBus()
268
+ WORKSPACE_OS = WorkspaceOSStore(DATA_DIR, event_sink=REALTIME_BUS)
269
+ ```
270
+
271
+ Because `RealtimeBus.__call__` aliases `publish`, **every** timeline event —
272
+ workspace, snapshot, memory, graph trace, agent run, workflow run, plugin
273
+ install/enable, skill change, onboarding, presence — fans into the realtime feed
274
+ automatically. There is no per-call instrumentation and no duplicated event
275
+ system. The hook defaults to `None`, so existing callers and tests see zero
276
+ behavior change.
277
+
278
+ ### Seam 3 — `PlatformRuntime` as the one cross-wiring point
279
+
280
+ `latticeai/services/platform_runtime.py` is the single place the four subsystems
281
+ cross-wire to one another and to the workspace. Keeping it out of `server_app`
282
+ honours the `AGENTS.md` preference for small, composable, independently testable
283
+ modules; `server_app` only constructs it and mounts routers.
284
+
285
+ ```python
286
+ PLATFORM = PlatformRuntime(
287
+ store=WORKSPACE_OS,
288
+ workspace_service=WORKSPACE_SERVICE,
289
+ plugin_registry=PLUGIN_REGISTRY,
290
+ get_current_user=get_current_user,
291
+ workspace_graph=_workspace_graph,
292
+ workspace_scope_from_request=_workspace_scope_from_request,
293
+ get_tool_permission=get_tool_permission,
294
+ )
295
+ ```
296
+
297
+ `PlatformRuntime` provides:
298
+
299
+ - **Request gating** — `gate_read` / `gate_write` resolve a caller's workspace
300
+ scope via `WorkspaceService` (raising `403` on `PermissionError`), and
301
+ `allowed_scopes` returns the set of workspaces a user may see (used by the
302
+ realtime router).
303
+ - **Plugin lifecycle hooks** — `register_plugin_skill` marks a bundled skill
304
+ installed in the shared skill registry (`version="plugin:<id>"`), so plugins
305
+ extend the existing skill surface instead of owning a parallel one.
306
+ - **Shared node runners** — `_tool_node_runner`, `_skill_node_runner`,
307
+ `_plugin_node_runner`, `_agent_node_runner`, plus `plugin_capability_runners`
308
+ (the runner map the Plugin SDK boundary dispatches to) and `_context_provider`
309
+ (feeds workspace memory to the agent researcher role).
310
+ - **Cross-system runs** — `run_workflow_by_id` and `run_agent`, plus the
311
+ factories `build_workflow_runners`, `build_orchestrator`, and
312
+ `plugin_capability_runners` that are handed to the routers.
313
+
314
+ The four routers are wired entirely through `PLATFORM`:
315
+
316
+ ```python
317
+ app.include_router(create_plugins_router(
318
+ registry=PLUGIN_REGISTRY,
319
+ register_skill=PLATFORM.register_plugin_skill,
320
+ plugin_runners_factory=lambda: PLATFORM.plugin_capability_runners(None, None),
321
+ ...
322
+ ))
323
+ app.include_router(create_workflow_designer_router(
324
+ store=WORKSPACE_OS,
325
+ gate_read=PLATFORM.gate_read, gate_write=PLATFORM.gate_write,
326
+ build_runners=PLATFORM.build_workflow_runners,
327
+ ...
328
+ ))
329
+ app.include_router(create_agents_router(
330
+ store=WORKSPACE_OS,
331
+ orchestrator_factory=PLATFORM.build_orchestrator,
332
+ gate_read=PLATFORM.gate_read, gate_write=PLATFORM.gate_write,
333
+ ...
334
+ ))
335
+ app.include_router(create_realtime_router(
336
+ bus=REALTIME_BUS,
337
+ allowed_scopes=PLATFORM.allowed_scopes,
338
+ ...
339
+ ))
340
+ ```
341
+
342
+ ---
343
+
344
+ ## 3. Cross-Integration Matrix
345
+
346
+ Because of the seams above, the subsystems can drive one another. The following
347
+ capabilities are all backed by `PlatformRuntime` runners and the engines:
348
+
349
+ | From | Can run | Wired by |
350
+ | --- | --- | --- |
351
+ | **Workflow** node | `tool`, `skill`, `plugin`, `agent` | `build_workflow_runners` → `_tool_node_runner`, `_skill_node_runner`, `_plugin_node_runner`, `_agent_node_runner` |
352
+ | **Agent** run (executor role) | `plugin`, `workflow` | `build_orchestrator` / `run_agent` inject `workflow_runner` + `plugin_runner` into `default_role_runner` |
353
+ | **Plugin** action | `skill`, `tool`, `workflow`, `agent` | `plugin_capability_runners` → `run_skill`, `run_tool`, `run_workflow`, `run_agent` |
354
+ | **Any timeline event** | Realtime feed | `event_sink` → `RealtimeBus.publish` (Seam 2) |
355
+ | **Graph entities** | Link to `workflow_runs`, `agent_runs` (and workflows, memories) | `record_workflow_run`, `record_agent_run`, `create_workflow` call `graph.ingest_event` and store the returned `graph_node_id` |
356
+ | **Workspace timeline** | Reflects all activity | every store mutation calls `record_timeline_event`; `timeline()` also re-projects snapshots, traces, agent runs, and workflows |
357
+
358
+ Concrete flows:
359
+
360
+ - **Workflow → tool/skill/plugin/agent.** The Workflow Designer `run` endpoint
361
+ builds the runner map from `PLATFORM.build_workflow_runners(user, scope)` and
362
+ passes it to `WorkflowEngine`. A `tool` node records the invocation plus its
363
+ governance decision (via `get_tool_permission`) but never silently executes
364
+ exec/destructive tools. A `plugin` node calls `registry.execute_action(...)`
365
+ through the permission boundary. An `agent` node calls `run_agent(...,
366
+ with_workflow=False)`.
367
+
368
+ - **Agent → plugin/workflow.** `run_agent` constructs `default_role_runner` with
369
+ a `workflow_runner` (`run_workflow_by_id(..., with_agent=False)`) and a
370
+ `plugin_runner` (`registry.execute_action(pid, "run_skill", ...)`), so the
371
+ executor role can drive both. The result is persisted with
372
+ `store.record_agent_run`.
373
+
374
+ - **Plugin → workflow/agent.** `plugin_capability_runners` exposes `run_workflow`
375
+ (delegates to `run_workflow_by_id(..., with_agent=False)`) and `run_agent`
376
+ (delegates to `run_agent(..., with_workflow=False)`), each gated by the
377
+ plugin's declared permission.
378
+
379
+ - **Graph linkage.** When the Knowledge Graph is enabled, `record_workflow_run`
380
+ ingests a `WorkflowRun` node, `record_agent_run` ingests an `AgentRun` node,
381
+ and `create_workflow` ingests a `Workflow` node — each storing the returned
382
+ `graph_node_id` on the record so runs are navigable from the graph and via the
383
+ Relationship Explorer.
384
+
385
+ - **Unified timeline.** `WorkspaceOSStore.timeline()` merges live timeline events
386
+ with re-projected snapshots, answer traces, agent runs, and workflows (and
387
+ optional audit events), all scope-filtered, giving one chronological view of
388
+ every subsystem's activity.
389
+
390
+ ---
391
+
392
+ ## 4. Recursion Bounding
393
+
394
+ Cross-system runs could in principle recurse forever (workflow runs an agent that
395
+ runs a workflow that runs an agent…). v2.0 bounds recursion **by construction** in
396
+ `PlatformRuntime`, not by a runtime depth counter:
397
+
398
+ - A workflow's **`agent` node** runs an orchestrator built *without* a workflow
399
+ runner. In `run_agent` the workflow node path calls
400
+ `run_agent(..., with_workflow=False)`, so `default_role_runner` receives
401
+ `workflow_runner=None`.
402
+ - An orchestrator's **workflow runner** runs an engine built *without* an `agent`
403
+ runner. `run_workflow_by_id(..., with_agent=False)` assembles the runner map
404
+ with only `tool`, `skill`, and `plugin` — no `agent` key — so the
405
+ `WorkflowEngine` skips any `agent` node it encounters.
406
+
407
+ The deepest possible chains are therefore:
408
+
409
+ ```text
410
+ agent → workflow → (tool | skill | plugin)
411
+ workflow → agent → plugin
412
+ ```
413
+
414
+ Independently, the `WorkflowEngine` enforces `_MAX_STEPS = 100` per run as a hard
415
+ cap against `next`-pointer cycles, and `MultiAgentOrchestrator` caps reviewer
416
+ retries via `max_retries` (clamped to 0–5 in the agents router). Together these
417
+ guarantee every cross-system run terminates.
418
+
419
+ ---
420
+
421
+ ## 5. HTTP Surface (v2.0 additions)
422
+
423
+ All v2.0 routes are namespaced so they never collide with existing paths
424
+ (`/plugins/registry` vs. the marketplace `/plugins/directory`; `/workflows` vs.
425
+ `/workspace/workflows`; `/agents` plural vs. the single-agent `/agent`).
426
+
427
+ **Plugin SDK** (`latticeai/api/plugins.py`): `GET /plugins/sdk`,
428
+ `GET /plugins/registry`, `GET /plugins/registry/{plugin_id}`,
429
+ `POST /plugins/validate`, `POST /plugins/install` (admin),
430
+ `POST /plugins/uninstall` (admin), `POST /plugins/enable`,
431
+ `POST /plugins/disable`, `POST /plugins/execute`.
432
+
433
+ **Workflow Designer** (`latticeai/api/workflow_designer.py`): `GET /workflows`,
434
+ `GET|POST /workflows/api/definitions`, `GET|PATCH
435
+ /workflows/api/definitions/{id}`, `POST /workflows/api/validate`,
436
+ `POST /workflows/api/definitions/{id}/run`,
437
+ `GET /workflows/api/definitions/{id}/runs`, `GET /workflows/api/runs`,
438
+ `GET /workflows/api/export/{id}`, `POST /workflows/api/import`.
439
+
440
+ **Multi-Agent Runtime** (`latticeai/api/agents.py`): `GET /agents`,
441
+ `GET /agents/api/roles`, `GET /agents/api/runs`, `POST /agents/api/run`.
442
+
443
+ **Realtime Collaboration** (`latticeai/api/realtime.py`): `GET /activity`,
444
+ `GET /realtime/stream` (SSE), `GET /realtime/feed`, `GET /realtime/presence`,
445
+ `POST /realtime/presence/join`, `POST /realtime/presence/leave`.
446
+
447
+ Representative run request/response (Workflow Designer):
448
+
449
+ ```json
450
+ // POST /workflows/api/definitions/{workflow_id}/run
451
+ { "inputs": { "steps": ["analyze", "package"] } }
452
+ ```
453
+
454
+ ```json
455
+ {
456
+ "run": { "id": "workflow-run-…", "status": "ok", "workflow_id": "workflow-…" },
457
+ "result": { "status": "ok", "step_count": 4, "timeline": [ /* per-node */ ], "outputs": {} }
458
+ }
459
+ ```
460
+
461
+ ```json
462
+ // POST /agents/api/run
463
+ { "goal": "Draft v2.0 release notes", "roles": ["planner", "executor", "reviewer"], "max_retries": 2 }
464
+ ```
465
+
466
+ ```json
467
+ {
468
+ "run": { "id": "agent-run-…", "agent_id": "agent:executor", "status": "ok" },
469
+ "result": { "status": "ok", "roles_run": ["planner", "executor", "reviewer"], "retries": 0, "output": "…" }
470
+ }
471
+ ```
472
+
473
+ ---
474
+
475
+ ## 6. Compatibility
476
+
477
+ > **Compatibility note.** v2.0.0 is **additive**. All v1.x data and APIs are
478
+ > preserved; the platform layers new capabilities on top of unchanged surfaces.
479
+
480
+ Preserved surfaces, verified against source:
481
+
482
+ - **ASGI entrypoints.** `server:app` and `latticeai.server_app.app` remain the
483
+ application objects. `server_app.py` still exposes the module-level `app =
484
+ FastAPI(...)` plus the `main()` / `uvicorn.run(app, ...)` entry point.
485
+ - **Version wiring.** `WORKSPACE_OS_VERSION = "2.0.0"` drives both
486
+ `APP_VERSION` (and thus the FastAPI `app.version`) and the `/health`
487
+ response — the health router is constructed with `app_version=APP_VERSION`.
488
+ - **Existing routes.** Every v1.x router (`auth`, `admin`, `security_dashboard`,
489
+ `workspace`, `health`, `models`, `chat`, `tools`, `garden`, `setup`,
490
+ `static_routes`) is still included unchanged in `server_app.py`. The v2.0
491
+ routers are *added* alongside them under non-colliding namespaces.
492
+ - **Data.** The local-first `workspace_os.json` is upgraded by deep-merge
493
+ backfill (Seam 1) and `_migrate_workspaces` (non-destructive workspace
494
+ upgrade); no migration deletes or rewrites existing snapshots, traces,
495
+ memories, agent runs, workflows, or skills.
496
+ - **Skills.** Standalone installed skills keep working; plugins reuse the same
497
+ `skill_registry` via `register_plugin_skill` rather than a parallel store.
498
+ - **Snapshots.** Snapshot files remain immutable JSON under
499
+ `workspace_snapshots/`; `snapshot["version"]` is stamped with
500
+ `WORKSPACE_OS_VERSION` but creation/compare/export behavior is unchanged.
501
+ - **Knowledge Graph.** Graph operations stay additive (`ingest_event`,
502
+ `ingest_message`); v2.0 only *adds* `WorkflowRun` / `AgentRun` linkage on top,
503
+ honoring the `AGENTS.md` rule to preserve legacy read compatibility and avoid
504
+ destructive migrations.
505
+ - **Legacy workflows.** Pre-2.0 workflows persisted as a flat `steps` list still
506
+ validate and run — `workflow_engine.normalize_definition` lifts them into a
507
+ linear `trigger → tool… → output` node chain without rewriting stored history.
508
+ - **Realtime hook is opt-in.** `WorkspaceOSStore(event_sink=...)` defaults to
509
+ `None`; without a bus the store behaves exactly as in v1.x.
510
+
511
+ ---
512
+
513
+ ## 7. Per-Subsystem Reference
514
+
515
+ Each pillar is intentionally a small, self-documenting module. For the
516
+ authoritative, code-level definition of each subsystem, see:
517
+
518
+ - **Plugin SDK** — `latticeai/core/plugins.py` (manifest schema, permissions,
519
+ execution boundary) and `latticeai/api/plugins.py` (HTTP surface).
520
+ - **Workflow Designer** — `latticeai/core/workflow_engine.py` (node types,
521
+ validation, interpreter) and `latticeai/api/workflow_designer.py`.
522
+ - **Multi-Agent Runtime 2.0** — `latticeai/core/multi_agent.py` (roles, pipeline,
523
+ retry) and `latticeai/api/agents.py`.
524
+ - **Realtime Collaboration** — `latticeai/core/realtime.py` (bus, presence, SSE)
525
+ and `latticeai/api/realtime.py`.
526
+ - **Cross-wiring** — `latticeai/services/platform_runtime.py`.
527
+ - **State + persistence** — `latticeai/core/workspace_os.py`.
528
+ - **App assembly** — `latticeai/server_app.py`.