Skip to content

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()

CLOSED

States

StateDescription
IDLESession created, not yet connected
CONNECTINGConnection attempt in progress
CONNECTEDTCP/TLS connected, handshake pending
ACTIVEHandshake complete, ready for messaging
ERRORConnection failed or error occurred
CLOSEDSession 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 → CLOSED

Persistence

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

CapabilityDescription
messagingRequest/response messaging
streamingServer-sent events / streams
file-transferBinary file uploads/downloads
pubsubPublish/subscribe messaging
rpcRemote 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 hour

See Also

Released under the MIT License.