Sessions & State
AGENIUM maintains persistent, stateful sessions between agents.
What is a Session?
A session represents a connection between two agents:
typescript
interface Session {
id: string; // Unique session ID
localAgent: AgentID; // This agent
remoteAgent?: AgentID; // Remote agent (after handshake)
state: SessionState; // Current state
createdAt: number; // Timestamp created
lastSeenAt: number; // Last activity
capabilities: Capability[]; // Negotiated capabilities
}Session Lifecycle
Sessions progress through well-defined states:
IDLE
│
│ connect()
▼
CONNECTING ────┐
│ │
│ │ (error)
▼ ▼
CONNECTED ERROR
│
│ handshake complete
▼
ACTIVE
│
│ close()
▼
CLOSEDStates
| State | Description |
|---|---|
IDLE | Session created, not yet connected |
CONNECTING | Connection attempt in progress |
CONNECTED | TCP/TLS connected, handshake pending |
ACTIVE | Handshake complete, ready for messaging |
ERROR | Connection failed or error occurred |
CLOSED | Session gracefully closed |
Creating Sessions
Client-Side (Initiating Connection)
typescript
const agent = createAgent('alice');
await agent.start();
// Connect to remote agent
const result = await agent.connect('agent://bob');
if (result.success) {
const session = result.session;
console.log('Session ID:', session.id);
console.log('State:', session.state); // 'ACTIVE'
}Server-Side (Accepting Connection)
typescript
const agent = createAgent('bob');
await agent.start();
// Listen for incoming connections
agent.on('connection', (info) => {
console.log('New connection from:', info.remoteAgent.name);
console.log('Session ID:', info.sessionId);
console.log('Capabilities:', info.capabilities);
});Session Manager
The SessionManager tracks all sessions:
typescript
import { createSessionManager } from 'agenium';
const manager = createSessionManager(identity);
// Create a session
const session = manager.create({
name: 'bob',
publicKey: 'sLCkXG4FN7N4gH5K...'
});
// Transition states
manager.transition(session.id, SessionEvent.CONNECT);
manager.transition(session.id, SessionEvent.CONNECTED);
manager.transition(session.id, SessionEvent.HANDSHAKE_OK);
// Get session
const s = manager.get(session.id);
// Find by remote agent
const sessions = manager.findByRemote('bob');
// Get all sessions
const all = manager.getAll();
// Get statistics
const stats = manager.getStats();
console.log(stats);
// { total: 5, active: 3, connecting: 1, error: 1 }State Transitions
Valid transitions follow the state machine:
typescript
enum SessionEvent {
CONNECT, // IDLE → CONNECTING
CONNECTED, // CONNECTING → CONNECTED
HANDSHAKE_OK, // CONNECTED → ACTIVE
ERROR, // * → ERROR
CLOSE, // ACTIVE → CLOSED
TIMEOUT // CONNECTING → ERROR
}Example Transitions
typescript
// Successful connection
manager.transition(sessionId, SessionEvent.CONNECT); // IDLE → CONNECTING
manager.transition(sessionId, SessionEvent.CONNECTED); // CONNECTING → CONNECTED
manager.transition(sessionId, SessionEvent.HANDSHAKE_OK); // CONNECTED → ACTIVE
// Error handling
manager.transition(sessionId, SessionEvent.ERROR); // * → ERROR
// Graceful close
manager.transition(sessionId, SessionEvent.CLOSE); // ACTIVE → CLOSEDPersistence
Sessions are persisted to SQLite for resumption after restarts.
Database Schema
sql
CREATE TABLE sessions (
session_id TEXT PRIMARY KEY,
remote_agent_name TEXT NOT NULL,
remote_public_key TEXT NOT NULL,
endpoint TEXT NOT NULL,
host TEXT NOT NULL,
port INTEGER NOT NULL,
state TEXT NOT NULL,
capabilities TEXT NOT NULL,
created_at INTEGER NOT NULL,
last_seen_at INTEGER NOT NULL,
last_error_code TEXT,
protocol_version TEXT NOT NULL
);
CREATE INDEX idx_sessions_remote_name
ON sessions(remote_agent_name);
CREATE INDEX idx_sessions_state
ON sessions(state);Saving Sessions
typescript
db.saveSession({
sessionId: session.id,
remoteAgentName: 'bob',
remotePublicKey: 'sLCk...',
endpoint: 'https://bob.example.com:8443',
host: 'bob.example.com',
port: 8443,
state: 'ACTIVE',
capabilities: JSON.stringify(['messaging']),
createdAt: Date.now(),
lastSeenAt: Date.now(),
protocolVersion: '1.0'
});Loading Sessions
typescript
// Load all persisted sessions
const sessions = db.getAllSessions();
for (const persisted of sessions) {
console.log('Resuming session:', persisted.sessionId);
await resumeManager.resume(persisted);
}Session Resumption
After a restart, AGENIUM automatically resumes sessions:
┌─────────────────────────────────────────────────┐
│ Agent Restart │
└──────────────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────┐
│ Load sessions from SQLite │
└──────────────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────┐
│ For each session: │
│ 1. DNS resolve (verify endpoint) │
│ 2. Verify public key unchanged │
│ 3. Attempt handshake │
│ 4. Restore to ACTIVE state │
└──────────────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────┐
│ Resume complete │
│ - Outbox messages sent │
│ - Agent ready for new messages │
└─────────────────────────────────────────────────┘Resume Manager
typescript
import { createResumeManager } from 'agenium';
const resumeManager = createResumeManager(db);
// Set resume function
resumeManager.setResumeFunction(async (session) => {
console.log('Resuming session:', session.sessionId);
// DNS resolve
const result = await resolver.resolve(`agent://${session.remoteAgentName}`);
// Verify key unchanged
if (result.agent.publicKey !== session.remotePublicKey) {
throw new Error('Public key mismatch during resume');
}
// Reconnect
await reconnect(result.agent.host, result.agent.port);
return true;
});
// Start automatic resumption
resumeManager.start();Resume Events
typescript
resumeManager.on('resumed', (info) => {
console.log('Session resumed:', info.sessionId);
});
resumeManager.on('resume_failed', (info) => {
console.error('Resume failed:', info.sessionId, info.error);
});Capabilities
Sessions negotiate capabilities during handshake:
typescript
// Agent supports these capabilities
const agent = createAgent('alice', {
capabilities: ['messaging', 'streaming', 'file-transfer']
});
// After handshake, negotiated capabilities are stored
const session = manager.get(sessionId);
console.log(session.capabilities);
// ['messaging', 'streaming'] (Bob only supports these two)Common Capabilities
| Capability | Description |
|---|---|
messaging | Request/response messaging |
streaming | Server-sent events / streams |
file-transfer | Binary file uploads/downloads |
pubsub | Publish/subscribe messaging |
rpc | Remote procedure calls |
Checking Capabilities
typescript
function hasCapability(session: Session, cap: string): boolean {
return session.capabilities.includes(cap);
}
if (hasCapability(session, 'streaming')) {
// Enable streaming features
}Session Timeout
Idle sessions can be automatically closed:
typescript
const agent = createAgent('alice', {
connectionIdleMs: 60_000 // Close after 60s of inactivity
});Activity Tracking
typescript
// Update last activity timestamp
session.lastSeenAt = Date.now();
// Check if session is stale
function isStale(session: Session, maxIdleMs: number): boolean {
return Date.now() - session.lastSeenAt > maxIdleMs;
}Multiple Sessions
An agent can have multiple concurrent sessions:
typescript
// Connect to multiple agents
const sessions = await Promise.all([
agent.connect('agent://bob'),
agent.connect('agent://charlie'),
agent.connect('agent://diana')
]);
// Send messages to all
for (const result of sessions) {
if (result.success) {
await agent.request(result.session!.id, 'greet', { name: 'Alice' });
}
}Session Limits
Configure maximum concurrent sessions:
typescript
const agent = createAgent('alice', {
maxConnectionsPerAgent: 4 // Max 4 sessions per remote agent
});Error Handling
Connection Errors
typescript
const result = await agent.connect('agent://bob');
if (!result.success) {
console.error('Connection failed:', result.error);
// Common errors:
// - DNS_NOT_FOUND: Agent not registered
// - TIMEOUT: Connection timed out
// - KEY_MISMATCH: Public key doesn't match DNS
// - HANDSHAKE_FAILED: Handshake rejected
}Session Recovery
typescript
// Monitor session state
const session = manager.get(sessionId);
if (session.state === SessionState.ERROR) {
// Attempt reconnection
const result = await agent.connect(session.remoteAgent!.name);
if (result.success) {
// Update session ID (new session created)
sessionId = result.session!.id;
}
}Best Practices
1. Always Enable Persistence
typescript
const agent = createAgent('alice', {
persistence: true, // ✅ Recommended
dataDir: './data'
});2. Handle Disconnections
typescript
agent.on('disconnected', (info) => {
console.log('Session disconnected:', info.sessionId);
// Attempt reconnection
setTimeout(() => {
agent.connect(info.remoteAgent.name);
}, 5000);
});3. Monitor Session Health
typescript
setInterval(() => {
const stats = manager.getStats();
console.log('Active sessions:', stats.active);
console.log('Error sessions:', stats.error);
// Alert if too many errors
if (stats.error > 5) {
console.warn('High error rate!');
}
}, 60_000);4. Clean Up Closed Sessions
typescript
// Periodically remove old closed sessions
setInterval(() => {
const all = manager.getAll();
const cutoff = Date.now() - 24 * 60 * 60 * 1000; // 24 hours
for (const session of all) {
if (session.state === SessionState.CLOSED &&
session.lastSeenAt < cutoff) {
manager.remove(session.id);
}
}
}, 3600_000); // Every hourSee Also
- Transport Layer - How sessions communicate
- Outbox Pattern - Reliable delivery within sessions
- Configuration - Timeout and limit settings