Interactive Agent Sessions (HITL)¶
Paseka supports two ways to run a bee:
| Mode | CLI | Use case |
|---|---|---|
| AFK (one-shot) | paseka bee run <role> --body "…" |
Background work; runtime waits for exit, normalizes summary, and may auto-publish INSIGHT/run.summary |
| Interactive (session) | paseka bee chat <role> "…" |
Human-in-the-loop dialogue in one long-lived agent process |
Interactive sessions are a parallel runtime path. They do not change the existing Adapter.Run() contract used for AFK runs.
Honey reserve: paseka invite accept consumes 1 honey from the trace reserve (energy.consume / session.start). Ad-hoc paseka bee chat does not consume honey. Top up with paseka energy add --trace <id> --amount <n> when accept fails with honey exhausted.
See also: architecture overview (adapters, runs layout), Brief (HITL paradigm).
1. Architecture¶
flowchart LR
user[User] --> queenShell[Queen Shell]
queenShell --> sessionManager[Session Manager]
sessionManager --> sessionAdapter[Session Adapter]
sessionAdapter --> pty[PTY Process]
pty --> externalAgent[External Agent]
sessionManager --> runDir[Run Directory]
ghostty[Ghostty UI] --> queenShell
queenConsole[Queen Console] --> sessionManager
Core rules:
Adapter.Run()stays synchronous and non-interactive (agent -pfor Cursor).SessionAdapteronly describes how to launch the external tool; the runtime owns the PTY, lifecycle, transcript, and status files.- Ghostty is an optional attach UI, not the session primitive. Terminal preferences live in machine-local config, not in committed
.paseka/.
Packages:
| Package | Role |
|---|---|
internal/adapters |
SessionAdapter, SessionRequest, SessionHandle |
internal/adapters/cursor |
Interactive agent invocation (no -p) |
internal/adapters/pi |
Interactive pi invocation (no -p, run-local session storage) |
internal/adapters/claude |
Interactive claude invocation (no -p) |
internal/sessions |
PTY process, attach, registry, Ghostty launcher |
internal/runs |
session.json, transcript.ndjson |
internal/colony |
Session registry in state.json, terminal.yaml |
2. Session adapter interface¶
type SessionAdapter interface {
Name() string
SessionCommand(req SessionRequest) (SessionCommand, error)
}
SessionCommand is a plain exec spec: binary, args, env, working directory. The session manager starts it inside a PTY.
AFK adapters keep the existing interface:
type Adapter interface {
Name() string
Run(ctx context.Context, req RunRequest) (*RunResult, error)
}
3. Run directory layout¶
Interactive sessions reuse .paseka/runs/<traceId>/<agentId>/ under the colony root (same as AFK runs). Additional files:
.paseka/runs/<traceId>/<agentId>/
├── prompt.txt
├── system.txt # optional — rendered system_template
├── summary.md
├── meta.json
├── status.json
├── request.json
├── events.ndjson
├── session.json # live session metadata (PID, state, workspace)
└── transcript.ndjson # NDJSON log of user/agent/system turns
| File | Purpose |
|---|---|
session.json |
sessionId, traceId, pid, state (active → completed / failed / cancelled) |
transcript.ndjson |
Audit trail for dialogue (role: user | agent | system) |
sessionId equals agentId in the MVP.
4. Session registry¶
Active sessions are mirrored in machine-local state:
{
"sessions": [
{
"sessionId": "a1b2c3d4",
"traceId": "trace-…",
"agentId": "a1b2c3d4",
"runDir": "/path/to/repo/.paseka/runs/trace-…/a1b2c3d4",
"bee": "scout",
"pid": 12345,
"startedAt": "2026-07-05T09:00:00Z"
}
]
}
Entries are removed when the session exits. paseka session stop can signal the recorded PID if the session is not owned by the current shell process.
5. Queen Shell commands¶
Start a chat¶
# Interactive session in the current terminal
paseka bee chat scout "help me design the auth flow"
# Task via template instead of positional prompt
paseka bee chat builder --body "add retry logic to the NATS client"
# Reuse a trace (e.g. continue work in the same worktree)
paseka bee chat builder --trace trace-abc123 --body "finish the PR"
Session management¶
paseka session list # sessions from state.json
paseka session attach <sessionId> # attach PTY in this process only
paseka session stop <sessionId> # stop local or remote (by PID)
session attach only works for sessions started in the same paseka process. For a separate window, use Ghostty (below) or start the session there directly.
AFK vs chat¶
paseka bee run scout --body "survey the repo" # non-interactive, exits when done
paseka bee chat scout "let's discuss the repo" # interactive PTY session
6. Terminal configuration (Ghostty)¶
Terminal UI choice is machine-local:
CLI override:
Behavior:
terminal |
What happens |
|---|---|
default (or unset) |
Session runs and attaches in the current terminal |
ghostty |
Opens a Ghostty window running paseka session run <role> … — the full session lives in that window |
Ghostty does not own the agent process in the default path: Paseka starts the PTY and attaches stdin/stdout. With Ghostty, the child paseka session run process owns the PTY inside the new window.
If Ghostty is not installed, set terminal: default or omit terminal.yaml.
7. Cursor adapter (interactive)¶
| Input | Maps to agent |
|---|---|
command (optional) |
full argv; overrides params mapping (see architecture overview) |
Workspace |
--workspace <path> |
SystemPrompt + InitialPrompt |
merged into one positional prompt (newline-separated) |
InitialPrompt |
task/kickoff portion before glue (no -p) |
params.model |
--model <id> |
params.force |
--force |
params.plan |
--plan |
| API key | CURSOR_API_KEY or --api-key from home config |
Interactive invocation:
agent --force \
--workspace "$WORKSPACE" \
--model composer-2.5
# positional prompt when system_template and/or task are set:
# "$PROMPT" # system + task, newline-separated
When system_template is set and no task/prompt is given, the session starts without a positional prompt and waits for user input.
Worktrees: if the bee has worktree: true, the session cwd is .paseka/worktrees/<traceId>/ (same as bee run).
8. Pi adapter (interactive)¶
Bees with adapter: pi launch the Pi CLI in its normal interactive UI (no -p, no --mode).
| Input | Maps to pi |
|---|---|
command (optional) |
full argv; overrides params mapping (see architecture overview) |
Workspace |
process cwd |
SystemPrompt |
--append-system-prompt <system.txt> (file path) |
InitialPrompt |
positional prompt when provided |
params.model |
--model <pattern> |
params.provider |
--provider <name> |
params.thinking |
--thinking <level> |
params.plan |
--plan |
params.binary |
CLI binary name (default pi) |
| API key | api_key_env from ~/.config/paseka/<slug>/adapters/pi.yaml → --api-key |
agentId |
--session-id <agentId> |
| run directory | --session-dir <runDir>/pi-sessions |
Interactive invocation:
pi --session-dir "$RUN_DIR/pi-sessions" \
--session-id "$AGENT_ID" \
--append-system-prompt "$RUN_DIR/system.txt" \
--model gemini-2.5-pro \
--provider google
# optional: "$PROMPT"
Pi session artifacts stay under .paseka/runs/<traceId>/<agentId>/pi-sessions/, tied to the current agentId.
Event publishing boundary: interactive Pi output is not parsed into domain bus events. Use paseka event emit --stdin during the session when the bee prompt requires bus events. Prefer --defer for end-of-run handoffs; runtime flushes the pending queue on successful session end (same policy as AFK).
9. Claude adapter (interactive)¶
Bees with adapter: claude launch the Claude Code CLI in its normal interactive TUI (no -p).
| Input | Maps to claude |
|---|---|
command (optional) |
full argv; overrides params mapping (see architecture overview) |
Workspace |
process cwd |
SystemPrompt |
--append-system-prompt-file <system.txt> (file path, not inline) |
InitialPrompt |
positional prompt when provided |
params.model |
--model <id> |
params.plan |
--permission-mode plan |
params.binary |
CLI binary name (default claude) |
| API key | ANTHROPIC_API_KEY or api_key_env from ~/.config/paseka/<slug>/adapters/claude.yaml |
Interactive invocation:
claude --append-system-prompt-file "$RUN_DIR/system.txt" \
--model claude-opus-4-8
# optional positional kickoff when task/prompt provided:
# "$PROMPT"
When system_template is set and no task/prompt is given, the session starts without a positional prompt and waits for user input in the TUI.
10. Lifecycle¶
paseka bee chat <role> [prompt]
│
▼
Resolve colony + bee config
│
▼
Optional worktree for traceId
│
▼
Render templates → prompt.txt (and system.txt when system_template is set)
│
▼
SessionAdapter.SessionCommand()
│
▼
Start PTY process → write session.json (active)
│
▼
Register in state.json
│
▼
Attach terminal (current or Ghostty)
│
▼
User dialogues with agent in one process
│
▼
On exit → update session.json, status.json, transcript
│
▼
Unregister from state.json
11. MVP limitations and next steps¶
| Topic | MVP | Later |
|---|---|---|
| Cross-process attach | session attach same process only; use Ghostty for new window |
Unix socket relay for sessions started outside paseka console |
| Queen Console PTY | WebSocket relay for sessions owned by current paseka console (GET /api/sessions/:id/pty); launch uses interactive agent TUI (no -p); Sessions UI has a Widen/Restore control for full-page terminal layout |
Cross-process browser attach |
SessionAdapter.Send |
Full PTY passthrough; no separate send API | Structured message API for web UI |
Bee YAML mode: interactive |
Use bee chat explicitly |
Optional default per bee |
| NATS events | File-based audit only | Publish session lifecycle to bus |
12. Decisions (locked)¶
| Topic | Decision |
|---|---|
| Session vs AFK | Separate SessionAdapter; do not overload Adapter.Run() |
| Session ID | Same as agentId for MVP |
| Run dir | .paseka/runs/<traceId>/<agentId>/ — shared with AFK IPC |
| Terminal config | ~/.config/paseka/<slug>/terminal.yaml — not committed |
| Ghostty | Optional UI; session run runs full session inside Ghostty window |