Skip to Content

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 open once, then send and read repeatedly 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:

OperationPurpose
openReserve a slot; returns a session ID.
sendIssue a command on an existing session.
readPull buffered output since an offset.
closeTear the session down explicitly.
listEnumerate 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, sets truncated=true.
  • Since(offset) → (data, nextCursor, truncated) — returns everything written since offset, plus a cursor for the next call. truncated=true means 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 caseIdleTTL
Quick interactive shellleave default (30 min)
Long build / deploy0 (no auto-close)
Tail a slow log0
Short-lived health probesmall (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 by send).
  • 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.

TAGS: remote-sessions, sessionmgr, ring-buffer, idle-ttl, ssh_session DEPENDS_ON: [sessions, agent-communication, tools]

Last updated on