Connection Reuse
Reducing connection overhead by reusing transport connections across multiple commands.
Overview
Connection reuse is automatic and adapter-specific:
- SSH holds a pool of live connections (via
ssh2) keyed by host/port/user, reused across every command run against that target. This is the only adapter with a real, persistent connection to reuse. - Docker and Kubernetes shell out to the
docker/kubectlCLI per command. There is no persistent connection object at the Xec level to pool — whatever connection reuse happens (e.g. the Docker daemon's own socket handling) is internal to those tools, not something Xec manages or exposes. - Local has no connection concept at all.
There is no manual pool object to create or manage for any adapter — you get reuse simply by running more than one command against the same target.
SSH Connection Pooling
Automatic Reuse
import { $ } from '@xec-sh/core';
const remote = $.ssh({ host: 'server.example.com', username: 'deploy' });
// All of these share one pooled connection
await remote`uptime`;
await remote`free -h`;
await remote`df -h`;
await remote`ps aux | head`;
Reuse is keyed by the full connection identity — host, port, username, and a
hash of the credential material (private key or password, never the
credential itself) — so two callers with different credentials for the same
host never share a connection, but two $.ssh(...) calls with the same
target and credentials do, even if they were created separately:
await $.ssh('deploy@server.example.com')`uptime`;
await $.ssh('deploy@server.example.com')`df -h`; // reuses the same connection
Pool Configuration
Pool behavior is set per adapter instance, not per command, through
ExecutionEngineConfig.adapters.ssh (typed as SSHAdapterConfig, exported
from @xec-sh/core):
import { ExecutionEngine } from '@xec-sh/core';
const $ = new ExecutionEngine({
adapters: {
ssh: {
connectionPool: {
enabled: true,
maxConnections: 10,
idleTimeout: 60000, // close idle connections after 1 minute
maxLifetime: 3600000, // force a fresh connection after 1 hour
keepAlive: true,
keepAliveInterval: 10000,
autoReconnect: true,
maxReconnectAttempts: 3,
reconnectDelay: 1000,
},
// SSH multiplexing (ControlMaster-style connection sharing)
multiplexing: {
enabled: true,
controlPath: '~/.ssh/cm_%r@%h:%p',
controlPersist: '10m',
},
},
},
});
Observing the Pool
The adapter emits events rather than exposing a pool object to poll:
$.on('ssh:pool-metrics', ({ metrics }) => {
console.log(
`active=${metrics.activeConnections} idle=${metrics.idleConnections} ` +
`reused=${metrics.reuseCount}`
);
});
$.on('ssh:reconnect', ({ host, attempts, success }) => {
console.log(`reconnect to ${host}: attempt ${attempts}, success=${success}`);
});
Docker and Kubernetes
Neither adapter holds a reusable connection object. Each command is its own
docker exec/docker run or kubectl exec invocation:
const web = $.docker('web');
await web`ps aux`; // separate docker exec invocation
await web`df -h`; // another separate invocation
const pod = $.k8s('production').pod('api-abc123');
await pod.exec`ps aux`; // separate kubectl exec invocation
await pod.exec`df -h`; // another separate invocation
There is no connectionPool/multiplexing-style config for these adapters,
and no API to pre-warm or keep a Docker/Kubernetes connection alive beyond
what the docker/kubectl binaries and their daemons already do on their
own.
Best Practices
// ✅ Keep one target context and reuse it across commands
const ssh = $.ssh({ host: 'server', username: 'deploy' });
await ssh`command1`;
await ssh`command2`;
await ssh`command3`;
// ✅ Size the pool for your fan-out, not the default
const $ = new ExecutionEngine({
adapters: { ssh: { connectionPool: { enabled: true, maxConnections: 50 } } },
});
// ✅ Watch pool health instead of polling a nonexistent pool object
$.on('ssh:pool-metrics', ({ metrics }) => reportMetrics(metrics));
// ❌ Re-resolving the target doesn't defeat pooling (reuse is keyed by
// host/port/user), but constructing it fresh in a hot loop is still
// needless allocation — hoist it once
for (const cmd of commands) {
await $.ssh({ host: 'server', username: 'deploy' })`${cmd}`;
}
// ❌ There is no connection object to close manually for SSH — the pool
// manages its own lifetime via idleTimeout/maxLifetime. Calling methods
// like .disconnect() or .close() on what $.ssh(...) returns does not exist.
Implementation
packages/core/src/adapters/ssh/index.ts— connection pool, multiplexing, reconnect logicpackages/core/src/adapters/ssh/connection-pool-metrics.ts— metrics collection behindssh:pool-metrics