100 lines
5.8 KiB
Markdown
100 lines
5.8 KiB
Markdown
# Multi-session workspace architecture
|
|
|
|
## Status
|
|
|
|
This is the authoritative contract for the bridge-managed multi-session workspace. It supersedes the one-live-agent-per-worktree assumption for new desktop APIs while preserving the legacy v1 surface.
|
|
|
|
## Identity
|
|
|
|
| Identity | Lifetime | Owner | Purpose |
|
|
| --- | --- | --- | --- |
|
|
| canonical `worktreePath` | durable | filesystem/bridge | directory group |
|
|
| `runtimeId` | durable while its tab is desired open | bridge manifest | open-session slot and UI reconciliation |
|
|
| Pi `sessionId` + `sessionPath` | durable history | Pi | saved conversation |
|
|
| `agentId` | one child in one daemon generation | bridge | legacy command and event routing |
|
|
| `bridgeInstanceId` | one daemon generation | bridge | invalidates stale cursors and agent IDs |
|
|
|
|
The UI never persists `agentId`. One canonical session file has at most one live runtime lease. Opening an already-open session returns its existing runtime.
|
|
|
|
## Runtime lifecycle
|
|
|
|
```text
|
|
starting -> idle <-> streaming
|
|
| |
|
|
+-> recovering -> idle | failed
|
|
+-> attention/error
|
|
any open state -> closing -> stopped
|
|
```
|
|
|
|
Each runtime owns one Pi RPC child, command serialization queue, recovery supervisor, event buffer, extension-attention cache, and summary. Several runtimes may share a worktree and session directory, but never a session file. Same-worktree concurrency is allowed and directory summaries expose open/working counts so clients can warn about filesystem and Git collisions.
|
|
|
|
Commands and lifecycle mutations are serialized per runtime. Close removes desired-open intent before stopping the child. A working runtime is aborted first, then stopped with a bounded wait and forced termination fallback. Closing keeps the JSONL history. Forgetting a directory is refused while any of its runtimes remain open.
|
|
|
|
## Persistence ownership
|
|
|
|
The bridge stores `bridge-workspace-v2.json` beneath the owner-only persistent `sessionRoot`. It is versioned, mode `0600`, and replaced atomically through a temporary file and rename. It records desired-open runtime IDs, canonical worktrees, and Pi session identity. Daemon shutdown preserves records; explicit close removes one.
|
|
|
|
On the first v2 migration only the legacy Home runtime is opened. After that migration an explicitly closed Home runtime remains closed and zero live Pi runtimes is valid. Restore launches desired-open runtimes with concurrency two. Six or more open runtimes produces a resource warning but there is no hard cap.
|
|
|
|
Missing, moved, corrupt, cross-directory, or duplicate session records are never replaced with a new session. They are retained/reported as failed dormant runtime intent or as a manifest issue. A corrupt whole manifest is reported and starts no runtimes rather than guessing.
|
|
|
|
The Tauri UI owns presentation persistence (order, selection, drafts, scroll and last-seen state) in owner-only app data. It must not persist transcripts, extension response values, request IDs, or arbitrary extension payloads.
|
|
|
|
Legacy `bridge-agent.json` remains the compatibility default-session pointer. New runtime state is authoritative in the workspace manifest.
|
|
|
|
## Additive v1 compatibility API
|
|
|
|
Existing `list_agents`, `select_agent`, `list_sessions`, `new_session`, `switch_session`, agent commands, and agent-scoped `subscribe` retain their meanings. `select_agent` resolves a compatible default runtime for that directory. The desktop must not implement tabs with legacy session switching.
|
|
|
|
New operations:
|
|
|
|
- `get_workspace`
|
|
- `get_workspace_summary`
|
|
- `create_session_runtime`
|
|
- `open_session_runtime`
|
|
- `close_session_runtime`
|
|
- `list_directory_sessions`
|
|
- `get_session_runtime_snapshot`
|
|
- `subscribe_workspace`
|
|
|
|
All cursors are non-negative integers.
|
|
|
|
## Workspace event and replay contract
|
|
|
|
`subscribe_workspace` is the preferred aggregate subscription. Every event has a global order and contains:
|
|
|
|
```json
|
|
{
|
|
"version": "v1",
|
|
"bridgeInstanceId": "daemon UUID",
|
|
"seq": 42,
|
|
"type": "runtime_event",
|
|
"agentId": "ephemeral when live",
|
|
"data": {
|
|
"runtimeId": "durable slot UUID",
|
|
"worktreePath": "/canonical/path",
|
|
"sessionId": "Pi UUID when known",
|
|
"eventType": "tool",
|
|
"eventData": {}
|
|
}
|
|
}
|
|
```
|
|
|
|
Replay responses include `bridgeInstanceId`, `firstAvailableSeq`, `latestSeq`, `truncated`, and `events`. Clients reload the workspace and runtime snapshots when the daemon ID changes or replay is truncated. Listener installation precedes replay delivery, preserving replay-then-live ordering.
|
|
|
|
## Snapshot and summary contract
|
|
|
|
`get_session_runtime_snapshot` addresses one `runtimeId` and returns runtime summary, state, transcript, statistics, commands, models, cached actionable extension requests, `bridgeInstanceId`, and the latest global sequence. A failed dormant runtime returns its identity/error summary without silently starting another conversation.
|
|
|
|
Runtime labels prioritize explicit Pi session name, first user prompt, then `New session`. Runtime summaries cache state, queue count, active tool, attention, error/recovery state and last activity. Workspace/directory summaries aggregate open, working, recovering, attention and error counts; Noctalia consumes only the aggregate summary and launches the desktop UI.
|
|
|
|
## Safety and recovery
|
|
|
|
- The bridge remains the sole Pi process/session/recovery owner.
|
|
- Each runtime recovery restarts with its exact leased session path.
|
|
- Session identity is refreshed at launch, after legacy session mutation, and after authoritative idle/settled transitions so the first prompt-created file is persisted.
|
|
- Background runtimes continue receiving events and extension attention.
|
|
- Pending extension requests are scoped to a runtime; another tab cannot answer them.
|
|
- A daemon restart changes `bridgeInstanceId` and all `agentId` values while keeping `runtimeId` stable.
|
|
- Restore failures surface explicitly. The bridge never substitutes a different session.
|