forked from markturansky/agent-boss
-
Notifications
You must be signed in to change notification settings - Fork 1
docs: add SessionBackend abstraction design specs #50
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
jsell-rh
merged 2 commits into
jsell-rh:main
from
tiwillia:worktree-feat-session-backend-interface
Mar 9, 2026
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,102 @@ | ||
| # Session Backend Abstraction — Design Overview | ||
|
|
||
| ## Spec Documents | ||
|
|
||
| | Doc | Contents | | ||
| |-----|----------| | ||
| | [01-tmux-audit.md](01-tmux-audit.md) | Every tmux touchpoint in the codebase, categorized | | ||
| | [02-session-backend-interface.md](02-session-backend-interface.md) | `SessionBackend` interface, data model changes, migration plan | | ||
| | [03-tmux-backend.md](03-tmux-backend.md) | `TmuxSessionBackend` implementation, method mapping, file layout | | ||
| | [04-ambient-backend.md](04-ambient-backend.md) | `AmbientSessionBackend` implementation, API mapping, behavioral differences | | ||
| | [05-agentcore-feasibility.md](05-agentcore-feasibility.md) | AWS Bedrock AgentCore feasibility analysis | | ||
|
|
||
| ## Summary | ||
|
|
||
| ### What exists today | ||
|
|
||
| - **tmux hardcoded everywhere**: 10+ functions in `tmux.go`, called directly from lifecycle | ||
| handlers, liveness loop, broadcast, introspect, approve, and reply. | ||
| - **`AgentBackend` interface** in `agent_backend.go` (PR #47) with `Spawn/Stop/List/Name`. | ||
| Only used by `handleCreateAgents`. All other code bypasses it. | ||
| - **`AgentUpdate.TmuxSession`** is the field that links an agent to its session. Used across | ||
| types, DB models, handlers, frontend, scripts, and docs. | ||
| - **`tmuxDefaultSession`** (PR #49, open) proposes space-scoped names `{space}-{agent}` — not adopted here. | ||
|
|
||
| ### What this design introduces | ||
|
|
||
| - **`SessionBackend` interface** with 13 methods covering the full surface: identity | ||
| (`Name`, `Available`), lifecycle (`CreateSession`, `KillSession`, `SessionExists`, | ||
| `ListSessions`), status (`GetStatus`), observability (`IsIdle`, `CaptureOutput`, | ||
| `CheckApproval`), interaction (`SendInput`, `Approve`, `Interrupt`), and discovery | ||
| (`DiscoverSessions`). | ||
| - **Role interfaces** (`SessionLifecycle`, `SessionObserver`, `SessionActor`) for | ||
| narrow consumer dependencies and easier testing. | ||
| - **`TmuxSessionBackend`** — wraps existing tmux functions. Preserves current | ||
| `agentdeck_*` naming convention. Zero behavior change. | ||
| - **`AmbientSessionBackend`** — backed by the ACP public API (`POST /sessions`, | ||
| `POST /message`, `GET /output`, `DELETE /sessions/{id}`, `POST /interrupt`, etc.). | ||
| Depends on platform PR #855. | ||
| - **Subsumes `AgentBackend`** — the existing interface from PR #47 is folded into | ||
| `SessionBackend`. `agent_backend.go` is deleted. | ||
| - **`AgentUpdate.SessionID`** + **`AgentUpdate.BackendType`** — replaces `TmuxSession` | ||
| with backend-agnostic fields. No backward-compat shim (clean break). | ||
| - **`Server.backends`** registry — map of backend name to implementation. Agents carry | ||
| their backend type; the server resolves the right implementation per-agent. | ||
| - **`SessionStatus`** enum — unified status model (`unknown`, `pending`, `running`, `idle`, | ||
| `completed`, `failed`, `missing`) that all backends map into. | ||
| - **`BackendOpts interface{}`** — backend-specific creation options. Each backend defines | ||
| its own options struct (`TmuxCreateOpts`, `AmbientCreateOpts`), keeping backend-specific | ||
| code contained within each backend. | ||
|
|
||
| ### Interface at a glance | ||
|
|
||
| ```go | ||
| type SessionBackend interface { | ||
| Name() string | ||
| Available() bool | ||
|
|
||
| CreateSession(ctx context.Context, opts SessionCreateOpts) (string, error) | ||
| KillSession(ctx context.Context, sessionID string) error | ||
| SessionExists(sessionID string) bool | ||
| ListSessions() ([]string, error) | ||
|
|
||
| GetStatus(ctx context.Context, sessionID string) (SessionStatus, error) | ||
|
|
||
| IsIdle(sessionID string) bool | ||
| CaptureOutput(sessionID string, lines int) ([]string, error) | ||
| CheckApproval(sessionID string) ApprovalInfo | ||
|
|
||
| SendInput(sessionID string, text string) error | ||
| Approve(sessionID string) error | ||
| Interrupt(ctx context.Context, sessionID string) error | ||
|
|
||
| DiscoverSessions() (map[string]string, error) | ||
| } | ||
| ``` | ||
|
|
||
| ### Scope of changes | ||
|
|
||
| | Area | Files affected | Nature of change | | ||
| |------|---------------|-----------------| | ||
| | Interface definition | New: `session_backend.go` | New file | | ||
| | Tmux backend | New: `session_backend_tmux.go` | Wraps existing functions | | ||
| | Old backend | Delete: `agent_backend.go` | Superseded (folded into SessionBackend) | | ||
| | Tmux primitives | `tmux.go` | Unchanged (kept as unexported helpers) | | ||
| | Data model | `types.go`, `db/models.go`, `db/convert.go`, `db_adapter.go` | Rename `TmuxSession` -> `SessionID`, add `BackendType` | | ||
| | Server | `server.go` | Add `backends` map, `backendFor()` helper | | ||
| | Lifecycle | `lifecycle.go` | Route through backend | | ||
| | Liveness | `liveness.go` | Route through backend | | ||
| | Handlers | `handlers_agent.go` | Route through backend, rename API endpoint | | ||
| | Broadcast | `tmux.go` (orchestration funcs) | Route through backend | | ||
| | Frontend | `types/index.ts`, `AgentDetail.vue`, `client.ts` | Rename `tmux_session` -> `session_id` | | ||
| | Tests | `server_test.go`, `hierarchy_test.go`, `lifecycle_test.go` | Update field names, add mock backend tests | | ||
|
|
||
| ### Known gaps (deferred) | ||
|
|
||
| | Gap | Notes | | ||
| |-----|-------| | ||
| | Context/tool injection for Ambient | Ambient sessions don't inherit local boss commands. Needs workflow or MCP server approach. Deferred to Phase 2. | | ||
| | Cross-space session name collisions | Current `agentdeck_*` naming doesn't include space. Same agent name in two spaces can collide. PR #49 proposes a fix but is out of scope here. | | ||
| | Session ownership/filtering | `tmuxListSessions` returns all sessions, not just agent-boss. Mitigated by naming convention but not solved. | | ||
| | Idle detection brittleness | `isShellPrompt` relies on PS1 heuristics. Claude Code hooks would be cleaner. | | ||
| | Model switching compaction risk | Switching from opus to haiku with large context triggers compaction. Needs separate evaluation. | | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,129 @@ | ||
| # Tmux Usage Audit | ||
|
|
||
| Every place in the codebase that directly touches tmux, categorized by purpose. | ||
|
|
||
| ## 1. Low-Level Tmux Primitives (`tmux.go`) | ||
|
|
||
| | Function | What it does | Called by | | ||
| |----------|-------------|-----------| | ||
| | `tmuxAvailable()` | Checks if `tmux` binary is in PATH | `TmuxAutoDiscover`, `BroadcastCheckIn`, `SingleAgentCheckIn`, `checkAllSessionLiveness` | | ||
| | `tmuxListSessions()` | Runs `tmux list-sessions -F #S`. **Note:** returns ALL tmux sessions on the machine, not just agent-boss sessions. Needs filtering/tagging mechanism (see §Session Ownership below). | `tmuxSessionExists`, `TmuxAutoDiscover` | | ||
| | `tmuxSessionExists(session)` | Checks if a named session is in the list | `handleAgentSpawn`, `handleAgentStop`, `handleAgentRestart`, `handleAgentIntrospect`, `BroadcastCheckIn`, `handleApproveAgent`, `handleReplyAgent`, `handleSpaceTmuxStatus`, `TmuxBackend.Spawn`, `TmuxBackend.Stop`, `checkAllSessionLiveness` | | ||
| | `tmuxCapturePaneLines(session, n)` | Runs `tmux capture-pane -t session -p`, returns last N non-empty lines | `tmuxIsIdle`, `tmuxCheckApproval`, `handleAgentIntrospect`, `handleSpaceTmuxStatus` | | ||
| | `tmuxCapturePaneLastLine(session)` | Wrapper: captures last 1 line | `handleSpaceTmuxStatus` | | ||
| | `tmuxIsIdle(session)` | Checks last 10 lines for idle indicators (shell prompts, Claude Code `>` prompt, etc.) | `tmuxCheckApproval`, `BroadcastCheckIn`, `checkAllSessionLiveness` | | ||
| | `tmuxCheckApproval(session)` | Scans pane for "Do you want...?" + numbered choices pattern | `checkAllSessionLiveness`, `handleAgentIntrospect`, `handleApproveAgent`, `handleSpaceTmuxStatus` | | ||
| | `tmuxApprove(session)` | Sends `Enter` key to session | `handleApproveAgent` | | ||
| | `tmuxSendKeys(session, text)` | Sends text + `C-m` (Enter) to session | `runAgentCheckIn`, `handleAgentSpawn`, `handleAgentRestart`, `handleReplyAgent`, `handleCreateAgents` (ignite), `TmuxBackend.Spawn` | | ||
| | `parseTmuxAgentName(session)` | Extracts agent name from `agentdeck_{name}_{id}` pattern | `TmuxAutoDiscover` | | ||
|
|
||
| ## 2. Idle Detection Helpers (`tmux.go`) | ||
|
|
||
| | Function | What it does | | ||
| |----------|-------------| | ||
| | `lineIsIdleIndicator(line)` | Returns true if a line matches known idle patterns: `>`, shell `$`/`%`/`#`, Claude Code hints, status bars | | ||
| | `isShellPrompt(line)` | Detects `$`, `%`, `>`, `#` as trailing prompt characters. **Brittle:** assumes PS1 follows convention. A cleaner approach would be using [Claude Code hooks](https://code.claude.com/docs/en/hooks) to emit structured idle/busy signals instead of parsing terminal output. | | ||
| | `waitForIdle(session, timeout)` | Polls `tmuxIsIdle` every 3s until idle or timeout | | ||
| | `waitForBoardPost(space, agent, since, timeout)` | Polls `agentUpdatedAt` every 3s (not tmux-specific, but used exclusively by broadcast which is tmux-only) | | ||
|
|
||
| ## 3. Broadcast / Check-In (`tmux.go`) | ||
|
|
||
| | Function | What it does | | ||
| |----------|-------------| | ||
| | `runAgentCheckIn(space, agent, tmuxSession, checkModel, workModel, result)` | Switches model, sends `/boss.check`, waits for board post, restores model. All via `tmuxSendKeys` + `waitForIdle`. | | ||
| | `BroadcastCheckIn(space, checkModel, workModel)` | Iterates all agents with `TmuxSession`, calls `runAgentCheckIn` concurrently. | | ||
| | `SingleAgentCheckIn(space, agent, checkModel, workModel)` | Single-agent version of broadcast. | | ||
| | `BroadcastResult` + helpers | Result accumulator for sent/skipped/errors. | | ||
|
|
||
| ## 4. Lifecycle Handlers (`lifecycle.go`) | ||
|
|
||
| | Handler | Tmux operations performed | | ||
| |---------|--------------------------| | ||
| | `handleAgentSpawn` | `tmuxSessionExists`, `exec tmux new-session -d`, `tmuxSendKeys` (command), `tmuxSendKeys` (ignite) | | ||
| | `handleAgentStop` | Gets `agent.TmuxSession`, `tmuxSessionExists`, `exec tmux kill-session` | | ||
| | `handleAgentRestart` | Gets `agent.TmuxSession`, `tmuxSessionExists`, `exec tmux kill-session`, `exec tmux new-session`, `tmuxSendKeys` (command + ignite) | | ||
| | `handleAgentIntrospect` | Gets `agent.TmuxSession`, `tmuxSessionExists`, `tmuxIsIdle`, `tmuxCapturePaneLines`, `tmuxCheckApproval` | | ||
| | `isNonTmuxAgent(agent)` | Checks `agent.Registration.AgentType != "tmux"` to gate lifecycle endpoints | | ||
| | `nonTmuxLifecycleError(w, type)` | Returns 422 for non-tmux agents hitting tmux-only endpoints | | ||
| | `inferAgentStatus(exists, idle, needsApproval)` | Pure function mapping booleans to string status (not tmux-specific logic) | | ||
|
|
||
| ## 5. Liveness Loop (`liveness.go`) | ||
|
|
||
| | Function | Tmux operations performed | | ||
| |----------|--------------------------| | ||
| | `checkAllSessionLiveness` | `tmuxAvailable`, iterates all agents with `TmuxSession`, calls `tmuxSessionExists`, `tmuxIsIdle`, `tmuxCheckApproval`. Updates `InferredStatus`, records interrupts, triggers nudges. Broadcasts SSE `tmux_liveness` event. | | ||
| | `executeNudge` | Calls `SingleAgentCheckIn` (which uses tmux) | | ||
|
|
||
| ## 6. Agent Handlers (`handlers_agent.go`) | ||
|
|
||
| | Handler | Tmux operations performed | | ||
| |---------|--------------------------| | ||
| | `handleSpaceAgent` (POST) | Preserves `TmuxSession` as sticky field on agent update | | ||
| | `handleIgnition` (GET) | Accepts `?tmux_session=` query param, stores on agent record, references it in ignition text | | ||
| | `handleSpaceTmuxStatus` (GET) | `TmuxAutoDiscover`, iterates agents, calls `tmuxSessionExists`, `tmuxIsIdle`, `tmuxCapturePaneLastLine`, `tmuxCheckApproval` | | ||
| | `handleApproveAgent` (POST) | Gets `agent.TmuxSession`, `tmuxSessionExists`, `tmuxCheckApproval`, `tmuxApprove` | | ||
| | `handleReplyAgent` (POST) | Gets `agent.TmuxSession`, `tmuxSessionExists`, `tmuxSendKeys` | | ||
| | `handleCreateAgents` (POST) | Uses `AgentBackend` interface for spawn, but then calls `tmuxSendKeys` directly for ignite | | ||
|
|
||
| ## 7. Backend Interface (`agent_backend.go`) — already exists but incomplete | ||
|
|
||
| | Method | `TmuxBackend` impl | `CloudBackend` impl | | ||
| |--------|-------------------|---------------------| | ||
| | `Name()` | `"tmux"` | `"cloud"` | | ||
| | `Spawn(ctx, spec)` | Creates tmux session, sends command | Returns `ErrNotImplemented` | | ||
| | `Stop(ctx, space, name)` | Kills tmux session | Returns `ErrNotImplemented` | | ||
| | `List(ctx, space)` | Lists all tmux sessions | Returns `ErrNotImplemented` | | ||
|
|
||
| **Only used by:** `handleCreateAgents`. All other lifecycle/liveness/broadcast code bypasses this interface entirely. | ||
|
|
||
| ## 8. Data Model References | ||
|
|
||
| | Location | Field | Notes | | ||
| |----------|-------|-------| | ||
| | `types.go:96` | `AgentUpdate.TmuxSession` | JSON tag `tmux_session` | | ||
| | `db/models.go:82` | `Agent.TmuxSession` | SQLite column | | ||
| | `db/convert.go:35` | `AgentRow.TmuxSession` | DB-to-coordinator conversion | | ||
| | `db/convert.go:61,113` | `FromAgentFields(..., tmuxSession, ...)` | Coordinator-to-DB conversion | | ||
| | `db_adapter.go:317,371` | References `TmuxSession` | DB adapter layer | | ||
| | `db/migrate_from_json.go:40` | `jsonAgent.TmuxSession` | JSON migration | | ||
|
|
||
| ## 9. Frontend References | ||
|
|
||
| | File | Usage | | ||
| |------|-------| | ||
| | `frontend/src/types/index.ts:49,118` | `tmux_session?: string` on agent types | | ||
| | `frontend/src/api/client.ts:257,275` | Spawn/restart return `tmux_session` | | ||
| | `frontend/src/components/AgentDetail.vue:133,562,918,921` | Displays tmux session, gates pane/controls sections | | ||
|
|
||
| ## 10. Scripts and Documentation | ||
|
|
||
| | File | Usage | | ||
| |------|-------| | ||
| | `scripts/boss.sh` | `get_tmux_session()`, passes `-e TMUX_SESSION` | | ||
| | `scripts/agent-ignition.sh` | `create_tmux_session()` | | ||
| | `scripts/coordination-client.py` | Passes `tmux_session` to ignition | | ||
| | `commands/boss.ignite.md` | References `?tmux_session=` | | ||
| | `commands/boss.check.md` | Notes `tmux_session` is sticky | | ||
| | `docs/AGENT_PROTOCOL.md` | Documents `tmux_session` field, ignition params | | ||
| | `docs/lifecycle-spec.md` | Spawn/stop/restart reference `tmux_session` | | ||
| | `docs/api-reference.md` | API docs reference `tmux_session` | | ||
| | `docs/hierarchy-design.md` | Compares parent stickiness to `TmuxSession` | | ||
|
|
||
| ## Session Ownership | ||
|
|
||
| `tmuxListSessions()` returns ALL tmux sessions on the machine — not just those | ||
| created by agent-boss. This means discovery and liveness can incorrectly interact | ||
| with unrelated sessions. | ||
|
|
||
| Currently, agent-boss sessions are identified by naming convention only: | ||
| - Legacy: `agentdeck_{name}_{timestamp}` (parsed by `parseTmuxAgentName`) | ||
| - PR #49: `{space}-{agent}` (parsed by space prefix matching) | ||
|
|
||
| Neither convention provides a strong ownership guarantee. Options for improvement: | ||
| - **tmux environment variable**: set `@agent_boss=true` on sessions at creation, | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems like the best proposal |
||
| filter by it during listing | ||
| - **Dedicated tmux server**: use `tmux -L agent-boss` to isolate sessions entirely | ||
| - **Prefix convention**: require a fixed prefix (e.g., `ab-{space}-{agent}`) that | ||
| is unlikely to collide with user sessions | ||
|
|
||
| This is out of scope for the current refactoring but should be addressed. | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that this requires ambient-code/platform#855 to be merged and assumes no major changes to the openapi spec.