Remote Sessions
A remote session is a persistent shell-like channel on a target machine that survives
across many tool calls. Where cmdop connect exec is one-shot, sessionmgr opens a
session, lets the agent issue many commands against it, and tears it down when idle. This
page describes the contract.
What sessionmgr gives you
- Many commands, one channel. The agent can
openonce, thensendandreadrepeatedly within a chat turn — and across turns. - Output buffering. A 1 MiB ring buffer per session captures stdout/stderr so the agent can poll instead of streaming.
- State machine. Every session walks
opening → ready → busy → closing → closed, so the agent can reason about what is happening without races. - Idle reaper. Sessions that go quiet for 30 minutes are closed automatically.
See internal/connect/sessionmgr/manager.go:26–183 and report 05 §2.4.
Lifecycle
Manager defaults:
- Max sessions per process: 64.
- Ring buffer per session: 1 MiB.
- Idle TTL: 30 minutes.
internal/connect/sessionmgr/types.go:18–78 is the source of truth for state transitions.
Public API
The internal API is Manager.Open / Close / Get / List / Count. The agent-facing surface
is the ssh_session builtin tool with operations:
| Operation | Purpose |
|---|---|
open | Reserve a slot; returns a session ID. |
send | Issue a command on an existing session. |
read | Pull buffered output since an offset. |
close | Tear the session down explicitly. |
list | Enumerate live sessions. |
See report 04 §2 for the registered tool entry.
The ring buffer
Long-running commands easily print megabytes. The ring buffer caps memory at 1 MiB by dropping the oldest bytes when full. The contract:
Write(b)— appends, drops oldest on overflow, setstruncated=true.Since(offset) → (data, nextCursor, truncated)— returns everything written sinceoffset, plus a cursor for the next call.truncated=truemeans the requested offset fell outside the current window — the agent has missed bytes between this and the previous read.
Source: internal/connect/sessionmgr/ringbuf.go:14–84.
The agent uses truncated as a signal to back off and read in smaller windows, or to
surface a warning to the user that some output was lost.
Idle TTL and the reaper
A reaper goroutine closes sessions that have been idle for the manager’s TTL (30 min by
default). Per-session override is IdleTTL on Open:
| Use case | IdleTTL |
|---|---|
| Quick interactive shell | leave default (30 min) |
| Long build / deploy | 0 (no auto-close) |
| Tail a slow log | 0 |
| Short-lived health probe | small (5–10 min) |
IdleTTL=0 disables the reaper for that session — close it explicitly when done.
Use IdleTTL=0 for builds, deploys, or any task expected to take longer than 30 minutes.
Driver injection
sessionmgr accepts a Driver interface so production wiring and tests use the same
manager. Production drivers wrap connectclient.Remote:
Execute(ctx, cmd)— unary command (used bysend).AttachStream(ctx)— bidi stream (used by interactive operations).
Tests inject in-memory fakes. See report 05 §2.4.
Truncation handling pattern
When an agent polls Since(offset) and gets truncated=true, the recommended pattern is:
let offset = 0
loop {
let (data, next, truncated) = read(session, offset)
if truncated {
warn("output truncated; consider increasing read frequency")
}
process(data)
offset = next
if done(data) { break }
}The agent exposes truncation in the chat as a warning so the user knows some bytes were dropped. See report 05 open-question 3 for the long-form discussion.
Example flow
// 1. open
{ "tool": "ssh_session", "args": { "hostname": "prod-1", "operation": "open" } }
// → { "session_id": "prod-1:slot3" }
// 2. send
{ "tool": "ssh_session", "args": {
"session_id": "prod-1:slot3",
"operation": "send",
"command": "tail -F /var/log/nginx/access.log"
}}
// 3. read (poll)
{ "tool": "ssh_session", "args": {
"session_id": "prod-1:slot3",
"operation": "read",
"offset": 0
}}
// → { "data": "...", "next_cursor": 4096, "truncated": false }
// 4. close
{ "tool": "ssh_session", "args": {
"session_id": "prod-1:slot3",
"operation": "close"
}}Limits and tuning
- 64 live sessions per daemon process. Board automations that fan out heavily can hit this cap — coalesce or reuse sessions where possible.
- 1 MiB ring per session. Total ringbuf memory at the cap is ~64 MiB.
- Per-session timeouts are independent of the manager’s
IdleTTL.
Persistent sessions reserve memory (1 MiB ring buffer each). Default cap is 64 per process; tune for board automations that fan out heavily.
Related
TAGS: remote-sessions, sessionmgr, ring-buffer, idle-ttl, ssh_session DEPENDS_ON: [sessions, agent-communication, tools]