Documentation / guides
Session Adapter
Adapt a Harness session controller to the TUI Agent contract with optional durable replay and gate indexing.
github.com/looprig/tui/sessionadapter adapts a Harness session.SessionController to the root tui.Agent surface. It delegates session operations, owns the live event subscription bridge, and optionally replays durable Enduring events so the screen can repaint before it starts consuming live deliveries. It does not own a root context, a workspace garbage-collection loop, or a second session lifetime.
Adapter boundary
// The application composes the Harness session. The adapter owns only the
// presentation-facing wrapper and returns a *sessionadapter.Adapter, which
// satisfies tui.Agent.
adapter := sessionadapter.New(sessionController)
var agent tui.Agent = adapter
stream, err := agent.Subscribe(tui.AllLoopsEventFilter())
if err != nil {
return err
}
defer stream.Close()
The plain New constructor is explicit and ephemeral: it has no replay backlog. Use it when the client knows there is no committed primer history to repaint or when a test needs the smallest adapter. A production client that promises enduring delivery should choose one of the replay constructors.
Constructor choices
| Constructor | Session state | Replay behavior |
|---|---|---|
New(sess) | New or headless session. | No cold replay. |
NewWithReplay(ctx, sess, store) | New session with committed primers. | Replays public Enduring history from the beginning. |
Restore(ctx, sess, store) | Restored session. | Reconstructs backlog and open-gate index before return. |
Both replay constructors take a ReplayOpener. The adapter needs only this narrow store seam:
type ReplayOpener interface {
OpenEventReplayer(uuid.UUID, sessionstore.ReplayRequest) (journal.EventReplayer, error)
}
*sessionstore.Store satisfies the interface. Keeping the dependency narrow lets tests provide a scripted journal and keeps session storage ownership in the Harness composition root.
Replay and gates
During construction, the adapter folds visible GateOpened and GateResolved events into a (loopID, toolExecutionID) index. Approve, Deny, and ProvideAnswer use that index to address the Harness gate opened by the correct loop. RespondGate addresses a host-raised gate by its Harness gate.ID directly.
If no matching gate is open, the adapter returns *GateNotOpenError with the loop and tool execution IDs. The error is errors.As-able and fail-secure: no guessed gate is touched.
Ownership
Adapter.Close calls the wrapped session controller’s shutdown exactly once. Subscription readers stop with the adapter. The Harness session owns workspace leases, snapshots, the event journal, and garbage collection. Read Workspaces and Session Presentation for what the screen displays and Session Stores and Durable Replay for the storage seam.