Files
kandev/docs/agent-orchestration-ui.md
T
2026-01-17 19:02:22 +00:00

250 lines
6.6 KiB
Markdown

# Agent Orchestration + Session UI Data Flow
This document describes how a task session is created, how agentctl/agent lifecycle events flow through the backend, and how the session details page consumes those events.
## Overview (Actors)
- **Client UI**: Next.js app with session details page and panels (chat, terminal, git, files).
- **Backend**: Orchestrator + WebSocket gateway + task/session store.
- **agentctl**: Sidecar HTTP/WS server inside agent container (or standalone process) that manages the agent subprocess and exposes ACP + workspace streams.
- **Agent**: ACP-speaking process (JSON-RPC over stdin/stdout) that performs work.
## Orchestration Flow (Task Session Creation)
```
Client UI
| HTTP: POST /api/v1/tasks { start_agent: true, agent_profile_id }
v
Backend Task Service
| create task + first session
| start agentctl orchestration
| create task_session (STARTING)
| launch agentctl (container or standalone)
| wait for /health
| POST /api/v1/start
| POST /api/v1/acp/call initialize
| POST /api/v1/acp/call session/new
| POST /api/v1/acp/call session/prompt
v
agentctl
| spawns agent process
| streams ACP updates + workspace streams
v
Agent
| ACP notifications (session/update)
v
agentctl -> Backend
| ACP update stream -> backend
| backend updates task_session state + messages
| backend publishes WS notifications
v
Client UI
| session.subscribe
| receive session-scoped events
```
## Backend Event Routing (Session Scoped)
Events are now routed primarily by **session_id**. The WS gateway maintains **session subscriber lists**. It broadcasts session events only to clients subscribed to that session.
```
agentctl WS /api/v1/acp/stream
-> backend ACP handler
-> task_session state update
-> message persisted
-> WS broadcast: session.channel
agentctl WS /api/v1/workspace/git-status/stream
-> backend git-status handler
-> attach to session
-> WS broadcast: session.channel
agentctl WS /api/v1/output/stream
-> backend shell handler
-> session resolve (session_id -> task_id)
-> WS broadcast: session.channel
```
Session notifications:
- `session.agentctl_starting`
- `session.agentctl_ready`
- `session.agentctl_error`
- `session.state_changed`
- `session.message.added`, `session.message.updated`
- `session.shell.output`, `session.shell.status`
- `session.git.status`
- `session.workspace.file.changes`
## Frontend Session Page Flow (SSR + WS)
```
Server (SSR)
- fetch session by id
- fetch parent task + workspace context
- hydrate store (tasks, sessions, repos, agent profiles)
Client (Hydrated)
- read sessionId from URL (/s/:sessionId)
- set activeSessionId in store
- subscribe to session events
- child components read from store + hooks
```
### Component Subscriptions
Each component subscribes to the data it needs via hooks. The **WS client deduplicates subscriptions** so multiple components can safely request the same channel.
```
TaskSessionPage (/s/:sessionId)
-> useSession(sessionId)
-> subscribe: session.{sessionId}
TaskChatPanel
-> useSessionMessages(sessionId)
-> subscribe: session.{sessionId}
ShellTerminal
-> useSessionAgentctl(sessionId)
-> subscribe: session.{sessionId}
-> useShellOutput(sessionId)
-> subscribe: session.{sessionId}
TaskChangesPanel (Git)
-> useSessionGitStatus(sessionId)
-> subscribe: session.{sessionId}
TaskFilesPanel
-> useSessionAgentctl(sessionId)
-> wait for readiness
-> useSessionWorktrees(sessionId)
-> subscribe: session.{sessionId}
```
## Event + State Flow Diagram
```
Agent
| ACP session/update
v
agentctl
| WS /api/v1/acp/stream
v
Backend ACP handler
| update session state/messages
| emit session WS events
v
WS Gateway (session subscribers)
| broadcast to session.{sessionId}
v
WebSocket Client (browser)
| dedupe subscription
| route to store slice
v
Zustand Store
| sessions, messages, shell, git, worktrees
v
Session UI Panels
| hook selectors
| render
```
## Concrete Example: New Session -> Message Appears In Chat
Scenario: user starts a task session and the agent replies with a completion message that appears in the chat panel.
### Sequence (Calls + Events)
1) Client -> Backend (HTTP request)
```
POST /api/v1/tasks
body:
title: "Example task"
workspace_id: "workspace-1"
repository_id: "repo-9"
agent_profile_id: "profile-abc"
start_agent: true
```
2) Backend (internal)
- creates task
- creates first task session
- starts agentctl orchestration for the new session
- returns session_id in the HTTP response
3) Backend -> agentctl (HTTP)
- `POST /api/v1/start`
- `POST /api/v1/acp/call` initialize
- `POST /api/v1/acp/call` session/new
- `POST /api/v1/acp/call` session/prompt
4) Backend -> Client (WS notification)
```
action: session.agentctl_starting
payload:
task_id: "task-123"
session_id: "session-789"
```
5) Client subscribes to session
```
action: session.subscribe
payload:
session_id: "session-789"
```
6) agentctl -> Backend (ACP notification)
```
jsonrpc: "2.0"
method: "session/update"
params:
type: "content"
data:
text: "Working on your request..."
session_id: "session-789"
```
7) Backend -> Client (WS notification)
```
action: session.message.added
payload:
session_id: "session-789"
message:
id: "msg-1"
author_type: "agent"
content: "Working on your request..."
```
8) Client store update (message handler)
```
messages.bySession["session-789"] += message
```
9) Chat panel render
```
TaskChatPanel -> useSessionMessages("session-789") -> messages list -> render
```
### Full Event List (Typical Session Start)
- HTTP request: `POST /api/v1/tasks` with `start_agent: true`
- WS notification: `session.agentctl_starting`
- WS request: `session.subscribe`
- WS notification: `session.agentctl_ready`
- ACP notifications from agent:
- `session/update` (content/toolCall/complete/error)
- WS notifications:
- `session.message.added`
- `session.message.updated`
- `session.state_changed`
- `session.shell.status` / `session.shell.output` (if shell active)
- `session.git.status` (if git watcher active)
- `session.workspace.file.changes` (if watcher active)
## Notes / Guardrails
- Session is the source of truth; task id is derived from session when needed.
- No fallback to “active task” or “first message” for session ownership.
- Agentctl readiness is explicit; panels that require a live agent should wait for `agentctl_ready`.
- Store slices are keyed by session id (messages, shell output, git status, worktrees).
- WS subscriptions are **session-scoped** and **deduplicated** at the client level.