Files
tutti/docs/architecture/agent-side-conversations.md
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

122 lines
6.5 KiB
Markdown

# Agent Side Conversations
Side is a runtime-only branch of an Agent session. It is deliberately not
Session Fork:
| Property | Session Fork | Side |
| -------- | ------------------------------------------- | ------------------------------------------------ |
| Identity | canonical child Session | runtime-scoped side id |
| Storage | durable history and lineage | no canonical writes |
| Boundary | full session or settled Turn | provider snapshot, including an active live Turn |
| Recovery | saga recovery and provider resume | expires when its provider process is lost |
| Events | optimistic stream plus committed projection | transient stream only |
## Standard provider contract
A provider integrates Side by implementing `SideConversationAdapter`:
1. `SideCapabilities` attests support for the selected runtime and may use
persisted provider metadata when the source connection is idle.
2. `OpenSide` creates the provider-native ephemeral branch and returns its
provider session id.
3. `OpenSide` quiesces callbacks and releases any child it created before
returning an error.
4. The ordinary `Adapter` methods provide Exec, Cancel, and Close after open;
interactive Side flows additionally use `InteractiveAdapter`.
The Controller owns scope isolation, open reservation/buffering, idempotency,
transient event routing, lifecycle reuse, cleanup, and the prohibition on
durable reporting or automatic resume. Agent Host owns the product lifecycle
entry points and keeps Side separate from canonical submit/cancel sagas.
Open holds the source and Side lifecycle identities in stable order until the
provider snapshot is committed or rolled back, so source close cannot overtake
the live fork.
In particular, Side never consumes or repairs the durable
canonical-Turn-to-provider-Turn binding used by Session Fork. Its
`clientSubmitId` is transient provider/event correlation only and must not be
paired with a canonical submit occurrence.
```mermaid
flowchart LR
GUI[AgentGUI Side pane] --> Store[Transient Side controller/store]
Store --> AdapterAPI[Desktop Side transport adapter]
AdapterAPI --> API[tuttid typed Side API]
API --> Host[Agent Host Side lifecycle]
Host --> Controller[Runtime Controller]
Controller --> Scope[Side scope registry and open buffer]
Controller --> Adapter[SideConversationAdapter]
Adapter --> Provider[Provider native ephemeral fork]
Provider --> Adapter
Adapter --> Scope
Scope --> Transient[agent.side.updated bridge]
Transient --> AdapterAPI
AdapterAPI --> Store
Controller -. ordinary Exec / Cancel / Respond / Close .-> Adapter
Controller -- canonical sessions only --> Durable[Durable activity reporter]
Scope -. Side is blocked .-> Durable
```
The Side runtime is an optional, provider-neutral AgentGUI port. Desktop
injects a factory, and each mounted embedded or detached AgentGUI surface owns
and disposes one runtime instance. AgentGUI does not branch on provider names:
it probes the selected live session and exposes `/side` only when the runtime
reports support. The Side pane and its messages live in a separate external
store; they are never dispatched to
`AgentSessionEngine`, never appear in the conversation rail, and are discarded
on explicit close, owning-surface disposal, or event-stream disconnect. A
typed `/side` remains an isolated Side intent even while capability resolution
is pending or unsupported; it must never fall through to the canonical main
conversation submit path.
Once capability discovery has enabled the command, AgentGUI enters the local
`opening` state and renders the Side shell immediately; it does not repeat the
capability RPC before opening. An empty Side renders a dedicated temporary-
conversation explanation while the provider fork completes and before the
first local Turn.
The service boundary is generated from OpenAPI and exposes resolve, open,
send, exact-turn cancel, interactive response, and close operations. Live
events use the workspace-scoped `agent.side.updated` topic with source/Side
identity and a Side-local monotonic sequence. AgentGUI consumes the generated
discriminated payload union directly, and transport adapters must expose
current connection state plus connection-state subscription. The canonical
`agent.activity.updated` bridge is not registered as the Side observer.
## Codex reference adapter
Codex uses the source-owned app-server connection while it is live. For an
idle or historical source, it starts a Side-owned app-server connection and
loads the persisted source by `threadId`. The historical path restores the
source's isolated `CODEX_HOME` from persisted runtime metadata so the new
process can find the original rollout; capability discovery fails closed when
that metadata is absent. The dedicated process is launched with the Side
session identity while the source `threadId` is used only by `thread/fork`, so
resource preparation, logs, and initialization-time events cannot attach to
the canonical source. Both paths send:
1. `thread/fork` with `ephemeral: true`, `excludeTurns: true`, no
`lastTurnId`, and Side-specific developer instructions composed with the
source thread's latest Tutti-owned host context.
2. `thread/inject_items` on the returned child thread to insert a model-visible
Side boundary.
`excludeTurns` hides inherited Turns from the Side-local response and UI; it
does not remove them from the provider fork's model context. Subsequent Side
Turns keep both the inherited Tutti host context and the Side-specific
instructions in their collaboration settings.
The connection has a thread-aware router. A live-source connection is
referenced by both runtime sessions; a historical-source connection is owned
only by Side and closes with it. Notifications for a new child that arrive
before `thread/fork` responds are buffered in a provisional route and replayed
only after lineage validation and Side registration. Closing Side unsubscribes
the ephemeral child and drops only the Side reference; it does not issue
`thread/delete` and cannot close the live parent connection. Losing the Side
connection produces `ErrSideConversationExpired`; Side is never resumed as a
canonical thread.
If a physical close fails, the adapter retains the dedicated connection as a
cleanup owner and retries it through the bounded runtime resource sweeper.
This change establishes the infrastructure, Codex reference vertical slice,
typed daemon API, and AgentGUI Side pane.