docs/agent-mux/archive/design/18-multi-adapter-architecture
Multi-Adapter Architecture Design reference
Extend agent-mux to support multiple adapter types beyond subprocess-based execution. This enables integration with sophisticated tools that provide HTTP APIs, WebSocket interfaces, or direct SDK access.
Continue reading
Nearby pages in the same section.
Multi-Adapter Architecture Design
Archived design document. Preserved for historical context; not part of the current normative
reference/contract.
Overview
Extend agent-mux to support multiple adapter types beyond subprocess-based execution. This enables integration with sophisticated tools that provide HTTP APIs, WebSocket interfaces, or direct SDK access.
Current Architecture (Subprocess-Only)
interface AgentAdapter {
buildSpawnArgs(options: RunOptions): SpawnArgs;
parseEvent(line: string, context: ParseContext): AgentEvent | AgentEvent[] | null;
// ... session, auth, config methods
}**Limitations:**
- Forces all tools into subprocess model
- Cannot leverage HTTP APIs, WebSocket streaming, or native SDKs
- Suboptimal for TUI-first tools (OpenCode), server-based tools (Codex app-server)
Proposed Architecture
1. Adapter Type Hierarchy
// Base interface (shared across all adapter types)
interface BaseAgentAdapter {
// Identity
readonly agent: AgentName;
readonly displayName: string;
readonly adapterType: 'subprocess' | 'remote' | 'programmatic';
// Capabilities (unchanged)
readonly capabilities: AgentCapabilities;
readonly models: ModelCapabilities[];
// Common functionality
detectAuth(): Promise<AuthState>;
getAuthGuidance(): AuthSetupGuidance;
sessionDir(cwd?: string): string;
parseSessionFile(filePath: string): Promise<Session>;
// ... other common methods
}
// Subprocess adapters (current model)
interface SubprocessAdapter extends BaseAgentAdapter {
readonly adapterType: 'subprocess';
readonly cliCommand: string;
buildSpawnArgs(options: RunOptions): SpawnArgs;
parseEvent(line: string, context: ParseContext): AgentEvent | AgentEvent[] | null;
}
// Remote adapters (HTTP, WebSocket, Unix sockets)
interface RemoteAdapter extends BaseAgentAdapter {
readonly adapterType: 'remote';
readonly connectionType: 'http' | 'websocket' | 'unix';
connect(options: RunOptions): Promise<RemoteConnection>;
disconnect(connection: RemoteConnection): Promise<void>;
// Optional: start/stop server if adapter manages it
startServer?(): Promise<ServerInfo>;
stopServer?(serverInfo: ServerInfo): Promise<void>;
}
// Programmatic adapters (direct SDK integration)
interface ProgrammaticAdapter extends BaseAgentAdapter {
readonly adapterType: 'programmatic';
execute(options: RunOptions): AsyncIterableIterator<AgentEvent>;
}2. Connection Abstractions
// Remote connection interface
interface RemoteConnection {
readonly connectionId: string;
readonly connectionType: 'http' | 'websocket' | 'unix';
send(data: unknown): Promise<void>;
receive(): AsyncIterableIterator<AgentEvent>;
close(): Promise<void>;
}
// HTTP-specific connection
interface HttpConnection extends RemoteConnection {
readonly connectionType: 'http';
readonly baseUrl: string;
get(path: string, params?: Record<string, unknown>): Promise<unknown>;
post(path: string, data?: unknown): Promise<unknown>;
stream(path: string, data?: unknown): AsyncIterableIterator<AgentEvent>;
}
// WebSocket-specific connection
interface WebSocketConnection extends RemoteConnection {
readonly connectionType: 'websocket';
readonly websocketUrl: string;
subscribe(channel: string): AsyncIterableIterator<AgentEvent>;
unsubscribe(channel: string): Promise<void>;
}3. Server Management
interface ServerInfo {
readonly serverId: string;
readonly serverType: string;
readonly endpoint: string;
readonly pid?: number;
readonly health?: 'starting' | 'healthy' | 'unhealthy';
}
interface ServerManager {
start(adapter: RemoteAdapter, options?: ServerOptions): Promise<ServerInfo>;
stop(serverId: string): Promise<void>;
health(serverId: string): Promise<ServerInfo>;
list(): Promise<ServerInfo[]>;
}Implementation Plan
Phase 1: Core Architecture
1. **Create new interface hierarchy** in packages/core/src/adapter-types.ts 2. **Extend AgentAdapter** to be union type: SubprocessAdapter | RemoteAdapter | ProgrammaticAdapter 3. **Update BaseAgentAdapter** in adapters package to implement SubprocessAdapter 4. **Maintain backward compatibility** - all existing adapters continue working
Phase 2: Execution Engine Updates
1. **Update RunHandleImpl** to route based on adapter.adapterType 2. **Create RemoteRunner** for HTTP/WebSocket execution 3. **Create ProgrammaticRunner** for direct SDK execution 4. **Add ServerManager** for lifecycle management 5. **Update event streaming** to handle different execution models
Phase 3: New Adapter Implementations
1. **opencode-http**: HTTP server + REST API + SSE streaming 2. **codex-sdk**: Direct SDK integration 3. **codex-websocket**: WebSocket app-server integration 4. **claude-agent-sdk**: Programmatic Claude interface 5. **pi-sdk**: Enhanced programmatic Pi interface
Phase 4: Mock Infrastructure
1. **MockHttpServer**: Simulate HTTP endpoints + SSE 2. **MockWebSocketServer**: Simulate real-time connections 3. **MockSDK**: Simulate direct SDK calls 4. **Enhanced scenarios**: Support all adapter types
Adapter Naming Convention
**Pattern**: {tool}-{type} where type indicates the integration method:
opencode(subprocess, default)opencode-http(HTTP server)codex(subprocess, current)codex-sdk(programmatic SDK)codex-websocket(WebSocket app-server)claude-agent-sdk(programmatic)
Example Adapter Implementations
HTTP Adapter (OpenCode)
class OpenCodeHttpAdapter implements RemoteAdapter {
readonly adapterType = 'remote';
readonly connectionType = 'http';
readonly agent = 'opencode-http';
async connect(options: RunOptions): Promise<HttpConnection> {
// Start 'opencode serve' if needed
const serverInfo = await this.ensureServer();
// Create HTTP connection
return new OpenCodeHttpConnection({
baseUrl: serverInfo.endpoint,
sessionId: options.sessionId,
model: options.model,
});
}
private async ensureServer(): Promise<ServerInfo> {
// Check if server already running
// If not, start via 'opencode serve --port 0'
// Return connection details
}
}
class OpenCodeHttpConnection implements HttpConnection {
async *stream(path: string, data: unknown): AsyncIterableIterator<AgentEvent> {
// POST to /api/chat/stream with SSE
const response = await fetch(`${this.baseUrl}${path}`, {
method: 'POST',
body: JSON.stringify(data),
headers: { 'Accept': 'text/event-stream' }
});
for await (const chunk of response.body) {
yield this.parseServerSentEvent(chunk);
}
}
}SDK Adapter (Codex)
class CodexSdkAdapter implements ProgrammaticAdapter {
readonly adapterType = 'programmatic';
readonly agent = 'codex-sdk';
async *execute(options: RunOptions): AsyncIterableIterator<AgentEvent> {
const sdk = new CodexSDK({
apiKey: process.env.OPENAI_API_KEY,
model: options.model || this.defaultModelId,
});
const stream = await sdk.chat.completions.create({
messages: [{ role: 'user', content: options.prompt }],
stream: true,
});
for await (const chunk of stream) {
yield this.parseCodexChunk(chunk);
}
}
}Migration Strategy
1. **Backward Compatible**: All existing adapters continue working unchanged 2. **Gradual Adoption**: Add new adapter types without breaking existing functionality 3. **Clear Documentation**: Document when to use each adapter type 4. **Mock Support**: Ensure all adapter types have full mock coverage
Benefits
1. **Native Performance**: Direct SDK integration eliminates subprocess overhead 2. **Real-time Streaming**: WebSocket connections enable bidirectional communication 3. **Full Capabilities**: HTTP APIs provide access to complete tool feature sets 4. **Flexible Integration**: Choose the best integration method per tool 5. **Future-Proof**: Support emerging tools with non-CLI interfaces
Claude-Specific Transport Note
Claude now spans multiple distinct integration surfaces that should not be collapsed into a single "server mode" mental model:
claudeCLI can provide a **persistent structured subprocess transport** through--print --input-format stream-json --output-format stream-json, with stdin carrying later user turns and stdout carrying structured events.claude-agent-sdkis a **programmatic persistent transport** with direct callback integration.claude-remote-controlis the **server-managed Claude surface for Claude.ai / Claude app clients**. Agent-mux can launch and observe the bridge honestly, but it does not advertise local stdin-driven chat semantics for it.- Claude channels are **MCP-mediated push/reply integrations into a running Claude host session**, not a standalone replacement for the CLI or SDK transports.
Agent-mux should model these surfaces honestly and only advertise the transport semantics each one actually supports.
Trade-offs
1. **Complexity**: Multiple execution paths increase code complexity 2. **Resource Management**: HTTP servers and connections need lifecycle management 3. **Testing**: More sophisticated mocking required 4. **Dependencies**: Programmatic adapters may require additional npm dependencies
Files to Create/Modify
New Files
packages/core/src/adapter-types.ts- New interface hierarchypackages/core/src/remote-runner.ts- HTTP/WebSocket executionpackages/core/src/programmatic-runner.ts- SDK executionpackages/core/src/server-manager.ts- Server lifecycle managementpackages/adapters/src/remote-adapter-base.ts- Base class for remote adapterspackages/adapters/src/programmatic-adapter-base.ts- Base class for SDK adapters
Modified Files
packages/core/src/adapter.ts- Update AgentAdapter union typepackages/core/src/run-handle-impl.ts- Route by adapter typepackages/adapters/src/base-adapter.ts- Implement SubprocessAdapterpackages/harness-mock/src/index.ts- Add mock infrastructure
This architecture enables agent-mux to evolve beyond subprocess-only integration while maintaining full backward compatibility and providing a clear path for supporting sophisticated AI tools with diverse integration requirements.