Files
IdeA/docs/ticket13-b0-backend-transport-inventory.md
Blomios 5505acc1f6 docs(ticket13): inventaire transport backend et frontend (B0)
Lot B0 du chantier server/client mode : cartographie des surfaces de
transport existantes, préalable à l'extraction du cœur backend commun.

- docs/ticket13-b0-backend-transport-inventory.md : inventaire backend.
- docs/ticket13-f0-frontend-transport-inventory.md : inventaire frontend.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 09:50:10 +02:00

23 KiB

Ticket #13 B0 - Backend transport inventory

Date: 2026-07-15 Branch: feature/ticket13-server-client-mode

Scope: read-only inventory of the current Tauri backend transport surface in crates/app-tauri. No production code change is part of B0.

Architect ticket #13 target: keep desktop Tauri and add server/client mode on a shared backend core. Future web transport is HTTP request/response plus a dedicated WebSocket for PTY/live streams.

Source anchors

  • Tauri command registration: crates/app-tauri/src/lib.rs:153
  • Composition root and AppState: crates/app-tauri/src/state.rs:738, crates/app-tauri/src/state.rs:1058
  • PTY Tauri Channel bridge: crates/app-tauri/src/pty.rs:1
  • Structured chat Tauri Channel bridge: crates/app-tauri/src/chat.rs:1
  • Domain event relay: crates/app-tauri/src/events.rs:23, crates/app-tauri/src/events.rs:1107
  • Desktop window commands/events: crates/app-tauri/src/commands.rs:2325
  • Shutdown/window lifecycle wiring: crates/app-tauri/src/lib.rs:80

Classification rules

  • request/response: one Tauri invoke maps to one backend result. HTTP candidate.
  • stream: command uses tauri::ipc::Channel or controls a live stream/session. WebSocket candidate, sometimes with a small HTTP control equivalent.
  • event: backend push currently emitted through Tauri global events. WebSocket live/event frame candidate unless desktop-specific.
  • desktop-only: OS windows, Tauri path/lifecycle/dialog composition. Not part of the web client transport.

Exposed Tauri commands

Core/projects/permissions

Command Class Current boundary
health request/response State<AppState>.health
create_project request/response create_project use case
open_project request/response open_project, plus open-time reconciliation/wiring
close_project request/response close_project
list_projects request/response list_projects
read_project_context request/response project FS read use case
update_project_context request/response project FS write use case
get_project_permissions request/response permission read use case
update_project_permissions request/response permission write use case
update_agent_permissions request/response permission override write use case
resolve_agent_permissions request/response effective permission read use case

PTY terminals

Command Class Current boundary
open_terminal stream Spawns PTY, registers Channel<PtyChunk>, starts output pump
write_terminal stream/control Writes bytes to live PTY
resize_terminal stream/control Resizes live PTY
close_terminal stream/control Kills PTY and unregisters channel
reattach_terminal stream Reads scrollback, replaces Channel<PtyChunk>, starts new pump

Notes:

  • PtyChunk = Vec<u8> today (crates/app-tauri/src/pty.rs:29).
  • The transport-specific registry is PtyBridge, stored in AppState.
  • High-frequency PTY bytes are explicitly excluded from global Tauri events.

Layouts

Command Class Current boundary
load_layout request/response layout read use case
mutate_layout request/response layout mutation use case, emits LayoutChanged
list_layouts request/response named layout list
create_layout request/response named layout create
rename_layout request/response named layout rename
delete_layout request/response named layout delete
set_active_layout request/response active layout write

First-run, profiles, local models, embedders

Command Class Current boundary
first_run_state request/response first-run read use case
reference_profiles request/response reference profile catalog
detect_profiles request/response runtime detection use case
list_profiles request/response profile store read
save_profile request/response profile upsert
clone_opencode_profile_from_seed request/response profile clone
delete_profile request/response profile delete
configure_profiles request/response first-run save
list_model_servers request/response local model server config read
save_model_server request/response local model server config write
preview_model_server_command request/response pure command preview
delete_model_server request/response local model server config delete
list_embedder_profiles request/response embedder profile read
save_embedder_profile request/response embedder profile write
delete_embedder_profile request/response embedder profile delete
describe_embedder_engines request/response embedder engine capabilities
dismiss_embedder_suggestion request/response suggestion state write

Agents, live state, chat and resume

Command Class Current boundary
create_agent request/response agent manifest/context creation
list_agents request/response manifest read
get_project_work_state request/response read-model aggregation
read_conversation_page request/response paginated transcript read
list_live_agents request/response in-memory live registry read
attach_live_agent request/response live session rebind use case
stop_live_agent request/response live session stop use case
read_agent_context request/response agent Markdown read
update_agent_context request/response agent Markdown write
delete_agent request/response manifest/context delete
inspect_conversation request/response best-effort conversation inspection
launch_agent stream Spawns/reuses PTY or structured session, may register Channel<PtyChunk>
change_agent_profile request/response hot-swap use case; may affect live session
agent_send stream Structured chat turn, registers Channel<ReplyChunk>, pumps reply stream
cancel_resume request/response cancels scheduled auto-resume
set_resume_at request/response human-set resume time
interrupt_agent request/response orchestrator interrupt
delegation_delivered request/response frontend write-portal ack
set_front_attached request/response frontend mounted/unmounted signal
reattach_agent_chat stream Replaces Channel<ReplyChunk>, returns chat scrollback
close_agent_session request/response closes live agent/structured state
list_resumable_agents request/response resumable agent inventory

Structured chat stream:

  • ReplyChunk is tagged by kind: textDelta, toolActivity, final, error (crates/app-tauri/src/dto.rs:1760).
  • ChatBridge retains bounded scrollback and the current optional channel.
  • agent_send is a turn command plus streaming response; in HTTP/WS terms it should become a WS client frame that starts a turn on an attached session.

Templates

Command Class Current boundary
create_template request/response template create
update_template request/response template update, emits drift/update events
list_templates request/response template list
delete_template request/response template delete
create_agent_from_template request/response agent creation from template
detect_agent_drift request/response drift inventory
sync_agent_with_template request/response sync write

Git

Command Class Current boundary
git_status request/response git status use case
git_stage request/response git stage use case
git_unstage request/response git unstage use case
git_commit request/response git commit use case
git_branches request/response branch list
git_checkout request/response branch checkout
git_log request/response log read
git_init request/response repo init
git_graph request/response graph read

Tickets and sprints

Command Class Current boundary
ticket_create request/response ticket create
ticket_read request/response ticket read
ticket_delete request/response ticket delete
open_ticket_chat request/response ephemeral ticket assistant open
close_ticket_chat request/response ephemeral ticket assistant close
ticket_list request/response ticket list
ticket_update request/response ticket update
ticket_read_carnet request/response carnet read
ticket_update_carnet request/response carnet write
ticket_link request/response link tickets
ticket_unlink request/response unlink tickets
ticket_assign request/response assign/unassign ticket agent
sprint_create request/response sprint create
sprint_list request/response sprint list
sprint_rename request/response sprint rename
sprint_reorder request/response sprint reorder
sprint_delete request/response sprint delete
ticket_assign_sprint request/response ticket sprint assignment
ticket_unassign_sprint request/response ticket sprint removal

Skills and memory

Command Class Current boundary
create_skill request/response skill create
update_skill request/response skill update
list_skills request/response skill list
delete_skill request/response skill delete
assign_skill_to_agent request/response skill assignment
unassign_skill_from_agent request/response skill unassignment
create_memory request/response memory create
update_memory request/response memory update
list_memories request/response memory list
get_memory request/response memory read
delete_memory request/response memory delete
read_memory_index request/response memory index read
recall_memory request/response memory recall
resolve_memory_links request/response memory link resolution

Background tasks

Command Class Current boundary
spawn_background_command request/response creates command-backed background task
cancel_background_task request/response cancels task
retry_background_task request/response retries task
list_background_tasks request/response task read-model list

Lifecycle is pushed through BackgroundTaskChanged domain events; initial list is request/response.

Desktop windows and focus

Command Class Current boundary
set_focused_project desktop-only + event Tauri window/panel focus state; emits focused-project://changed
get_focused_project desktop-only Tauri panel state read
list_open_view_windows desktop-only AppHandle.webview_windows()
open_view_window desktop-only WebviewWindowBuilder
close_view_window desktop-only closes Tauri OS window
move_tab_to_new_window desktop-only use case plus WebviewWindowBuilder

These do not migrate as-is to web. The shared backend may still keep workspace topology use cases, but the OS-window adapter remains desktop-only.

Tauri Channel usage

Location Channel type Producer Consumer Web mapping
open_terminal Channel<PtyChunk> PTY output pump xterm view WS output frames after open_terminal
reattach_terminal Channel<PtyChunk> PTY output pump xterm view WS attached + output frames after attach_terminal
launch_agent Channel<PtyChunk> PTY output pump for raw CLI agents xterm/chat terminal view WS output frames; structured sessions do not use this channel
agent_send Channel<ReplyChunk> structured reply pump chat view WS chat.output or generic live frames
reattach_agent_chat Channel<ReplyChunk> structured reply pump chat view WS attached/replay for structured session

Transport-owned registries:

  • PtyBridge: session id -> (generation, Channel<PtyChunk>).
  • ChatBridge: session id -> {generation, Option<Channel<ReplyChunk>>, scrollback}.

These should not descend into domain/application. Their backend-core equivalent should be a transport-neutral outbound sink/subscription registry.

Events emitted today

Global domain event relay

events::spawn_relay subscribes to TokioBroadcastEventBus and emits:

  • Tauri event name domain://event
  • Payload DomainEventDto, tagged by type
  • Skips DomainEvent::PtyOutput; PTY bytes use per-session channels

Current DomainEventDto variants:

  • Project/lifecycle: projectCreated, layoutChanged, remoteConnected
  • Agents: agentLaunched, agentLaunchFailed, agentExited, agentBusyChanged, agentProfileChanged, agentLivenessChanged, agentRateLimited, agentResumeScheduled, agentResumeCancelled, agentResumed, agentRateLimitSuspected
  • Delegation/orchestration: delegationReady, agentReplied, agentAnnouncement, orchestratorRequestProcessed, orchestratorChanged
  • Background work: backgroundTaskChanged, agentInboxChanged, agentWakeChanged
  • Templates/skills: templateUpdated, agentDriftDetected, agentSynced, skillAssigned
  • Git: gitStateChanged
  • Tickets/sprints: issueCreated, issueUpdated, issueDeleted, issueStatusChanged, issuePriorityChanged, issueCarnetUpdated, issueLinked, issueUnlinked, issueAgentAssigned, issueAgentUnassigned, sprintCreated, sprintRenamed, sprintReordered, sprintDeleted, issueSprintChanged
  • Memory/embedder: memorySaved, memoryDeleted, memoryIndexRebuilt, embedderSuggested
  • Model server: modelServerStatusChanged
  • PTY placeholder: ptyOutput exists in DTO but is not emitted by the global relay

Dedicated global events

  • model_server_status_changed: emitted in addition to domain://event for ModelServerStatusChanged.
  • agent_launch_failed: emitted in addition to domain://event for AgentLaunchFailed.
  • view-window://lifecycle: desktop-only, payload {kind, panel, label}.
  • focused-project://changed: desktop-only, payload {project}.

For web, the domain-event set maps naturally to a low-frequency live WS stream. The duplicate dedicated events should be normalized in the shared core contract or kept as compatibility aliases only in the Tauri adapter.

tauri::State<AppState> composition root inventory

AppState::build(app_data_dir) is the current composition root. It constructs concrete adapters and use cases, then Tauri stores it through app.manage.

Concrete adapters and infra state currently built there

  • TokioBroadcastEventBus
  • SystemClock
  • UuidGenerator
  • LocalFileSystem
  • FsProjectStore
  • FsWindowStateStore
  • PortablePtyAdapter with sandbox enforcer
  • TerminalSessions
  • StructuredSessions
  • StructuredSessionFactory
  • LocalProcessSpawner
  • CliAgentRuntime
  • FsProfileStore
  • local model server manager/use cases
  • issue/ticket stores and providers
  • PtyBridge and ChatBridge transport bridges
  • profile/template/git/skill/memory/embedder stores and use cases
  • orchestrator service, watchers and per-project MCP servers
  • session-limit service, resume contexts and turn watcher
  • background task runner/store
  • focused project mutex
  • home/app-data derived paths

Boundary that must be extracted

Current shape:

React -> @tauri invoke/listen/Channel
      -> app-tauri commands/events/bridges
      -> AppState concrete composition root
      -> application use cases + infrastructure adapters

Target shape:

React -> TS gateways
      -> Tauri adapter OR HTTP+WS adapter
      -> driving adapter calls shared backend core facade
      -> application use cases + infrastructure adapters

The extraction line is inside app-tauri::state:

  • Move reusable construction into a shared backend core crate/facade, e.g. BackendCore::build(config).
  • Keep Tauri-only concerns in app-tauri: resolving app_data_dir via Tauri path API, app.manage, command registration, AppHandle/WebviewWindow, shutdown hooks, Tauri event emission, Tauri Channel bridges, dialog plugin.
  • The future web server adapter should own HTTP route registration, TLS/pairing integration, WS connection lifecycle, CORS/origin checks and WS sink mapping.
  • The core should expose transport-neutral methods for the request/response use cases and transport-neutral subscription/sink APIs for PTY/chat/live output.

Do not move PtyBridge or ChatBridge as-is. They are Tauri transport adapters. Move the underlying concept: attach/detach a sink to a live session, replay bounded scrollback, and deliver ordered output.

HTTP DTO first draft

Naming below keeps current DTO semantics and camelCase fields. Exact paths are a proposal for B1/B3; they can be namespaced by project where useful.

Request/response examples

GET /api/health
POST /api/projects
GET /api/projects
POST /api/projects/{projectId}/open
POST /api/projects/{projectId}/close
GET /api/projects/{projectId}/context
PUT /api/projects/{projectId}/context
GET /api/projects/{projectId}/layout
POST /api/projects/{projectId}/layout/mutate
GET /api/projects/{projectId}/agents
POST /api/projects/{projectId}/agents
GET /api/projects/{projectId}/agents/{agentId}/context
PUT /api/projects/{projectId}/agents/{agentId}/context
DELETE /api/projects/{projectId}/agents/{agentId}
GET /api/projects/{projectId}/work-state
GET /api/projects/{projectId}/conversation-page
GET /api/projects/{projectId}/git/status
POST /api/projects/{projectId}/git/stage
POST /api/projects/{projectId}/git/commit
GET /api/tickets
POST /api/tickets
GET /api/tickets/{ref}
PATCH /api/tickets/{ref}
GET /api/templates
POST /api/templates
GET /api/background-tasks
POST /api/background-tasks

Errors should preserve ErrorDto:

{
  "code": "NOT_FOUND",
  "message": "project ..."
}

WebSocket first draft

One dedicated WS endpoint is enough for B5 V1:

GET /ws/live?token=...   (token normally via Authorization/cookie, not URL in prod)

Authentication note from ticket #13: pairing/token must travel only over HTTPS and not as a URL secret. The query above is only a sketch; production should use Authorization: Bearer during WS upgrade or an HTTPS-only secure cookie.

Common envelope

Client to server:

{
  "id": "client-msg-uuid",
  "kind": "terminal.input",
  "payload": {}
}

Server to client:

{
  "id": "server-msg-uuid",
  "replyTo": "client-msg-uuid",
  "kind": "terminal.status",
  "payload": {}
}

replyTo is present for command acknowledgements, absent for unsolicited live events/output.

PTY client -> server frames

attach_terminal

{
  "id": "1",
  "kind": "terminal.attach",
  "payload": {
    "sessionId": "uuid",
    "lastSeq": 42
  }
}

open_terminal

{
  "id": "2",
  "kind": "terminal.open",
  "payload": {
    "projectId": "uuid",
    "nodeId": "uuid-or-null",
    "cwd": "/abs/path",
    "rows": 30,
    "cols": 120
  }
}

launch_agent

{
  "id": "3",
  "kind": "agent.launch",
  "payload": {
    "projectId": "uuid",
    "agentId": "uuid",
    "nodeId": "uuid-or-null",
    "rows": 30,
    "cols": 120,
    "conversationId": "engine-conversation-id-or-null"
  }
}

input

{
  "id": "4",
  "kind": "terminal.input",
  "payload": {
    "sessionId": "uuid",
    "bytesBase64": "DQ=="
  }
}

resize

{
  "id": "5",
  "kind": "terminal.resize",
  "payload": {
    "sessionId": "uuid",
    "rows": 40,
    "cols": 140
  }
}

detach

{
  "id": "6",
  "kind": "terminal.detach",
  "payload": {
    "sessionId": "uuid"
  }
}

close

{
  "id": "7",
  "kind": "terminal.close",
  "payload": {
    "sessionId": "uuid"
  }
}

ping

{
  "id": "8",
  "kind": "ping",
  "payload": {
    "atMs": 1784123456789
  }
}

PTY server -> client frames

attached

{
  "kind": "terminal.attached",
  "replyTo": "1",
  "payload": {
    "session": { "sessionId": "uuid", "projectId": "uuid", "nodeId": "uuid", "rows": 30, "cols": 120 },
    "nextSeq": 43,
    "scrollback": [
      { "seq": 1, "bytesBase64": "..." }
    ],
    "gap": false
  }
}

output

{
  "kind": "terminal.output",
  "payload": {
    "sessionId": "uuid",
    "seq": 43,
    "bytesBase64": "..."
  }
}

status

{
  "kind": "terminal.status",
  "payload": {
    "sessionId": "uuid",
    "status": "running"
  }
}

Suggested status values: starting, running, exited, closed, detached, reattached.

error

{
  "kind": "error",
  "replyTo": "7",
  "payload": {
    "code": "NOT_FOUND",
    "message": "terminal session ..."
  }
}

pong

{
  "kind": "pong",
  "replyTo": "8",
  "payload": {
    "atMs": 1784123456789
  }
}

Structured chat frames

The ticket #13 PTY contract does not require structured chat in B5, but the current backend already has a second stream. To avoid a later incompatibility, reserve a parallel namespace:

Client:

{ "id": "9", "kind": "chat.attach", "payload": { "sessionId": "uuid" } }
{ "id": "10", "kind": "chat.send", "payload": { "sessionId": "uuid", "prompt": "..." } }
{ "id": "11", "kind": "chat.detach", "payload": { "sessionId": "uuid" } }

Server:

{
  "kind": "chat.attached",
  "replyTo": "9",
  "payload": {
    "sessionId": "uuid",
    "scrollback": [
      { "kind": "textDelta", "text": "..." }
    ]
  }
}
{
  "kind": "chat.output",
  "payload": {
    "sessionId": "uuid",
    "chunk": { "kind": "final", "content": "..." }
  }
}

Low-frequency live events

Domain events can be pushed on the same WS connection:

{
  "kind": "event.domain",
  "payload": {
    "type": "agentBusyChanged",
    "agentId": "uuid",
    "busy": true
  }
}

This replaces Tauri listen("domain://event") for web. Desktop can keep the existing event relay.

Plan impact / risks found

No finding invalidates the ticket #13 lot plan.

Points to decide early in B1/B2:

  • The duplicate Tauri registration of detect_agent_drift in generate_handler! is harmless for B0 but should be cleaned in a separate fix.
  • open_project currently also triggers open-time reconciliation and per-project runtime side effects. The shared backend core facade should make those explicit methods, not hide them in transport commands.
  • launch_agent has both raw PTY and structured-session branches. B5 PTY V1 can implement raw PTY first, but the WS namespace should reserve chat/live frames.
  • PtyBridge and ChatBridge contain transport scrollback/replay behavior. Extract the behavior as a core subscription contract; do not leak Tauri Channel into the future core.
  • Desktop window/focus commands are cleanly separable as desktop-only. Workspace topology can remain shared, OS-window creation cannot.