Transport-level routing for MCP/ACP protocols

We appreciate your patience. The service is temporarily operating in an external runtime environment.

Architecture and Runtime ModelINFORMATIVE

This section describes the internal architecture of the stdio Bus reference implementation. The content is informative (non-normative) and intended to help developers understand the runtime behavior and debug issues.

Non-Normative Content

This page describes the reference implementation architecture. Alternative implementations may use different internal structures while still conforming to the normative specification. For normative requirements, see the Protocol Specification.

High-Level Architecture

stdio Bus operates as a transport layer between external clients and worker processes. The daemon consists of four main components: the event loop, transport layer, router, and process manager.

Loading diagram...

stdio Bus runtime architecture: clients connect via transport layer, messages route to workers
High-level architecture diagram showing external clients, transport modes, and stdio Bus runtime components

Core Components

Event Loop

Single-threaded I/O multiplexing using platform-specific APIs (epoll on Linux, kqueue on macOS). Processes all I/O events, signal handling, and timeout checks in a non-blocking manner.

Transport Layer

Handles NDJSON framing for all connections. Manages input buffering, message extraction, output queuing, and partial write tracking. Implements backpressure when output queues exceed configured limits.

Router

Maintains session and request tables for message routing. Routes client messages to workers based on session affinity, and routes worker responses back to the originating client based on request ID correlation.

Process Manager

Spawns and monitors worker processes according to pool definitions. Handles worker lifecycle including startup, health monitoring, restart with backoff, and graceful shutdown.

Process Model

The stdio Bus runtime follows a deterministic startup sequence and maintains clear ownership of all file descriptors throughout its lifecycle.

Startup Sequence

  1. Argument Parsing: Parse command-line arguments to determine operating mode (stdio, Unix socket, or TCP) and configuration file path.
  2. Configuration Loading: Read and validate the JSON configuration file, extracting pool definitions and operational limits.
  3. Event Loop Creation: Initialize the platform-specific event loop (epoll or kqueue) for I/O multiplexing.
  4. Worker Spawn: For each pool definition, fork/exec the specified number of worker instances with stdin/stdout pipes.
  5. Interface Setup: Based on operating mode, either use stdin/stdout directly or create a listening socket (Unix domain or TCP).
  6. Event Loop Entry: Begin processing I/O events in the main loop until shutdown is signaled.

Worker Spawn Process

Workers are spawned using the standard Unix fork/exec pattern with pipe-based communication:

  1. Parent creates two pipes: stdin_pipe[2] and stdout_pipe[2]
  2. Parent forks a child process
  3. Child: dup2() pipes to stdin/stdout, then execvp() the configured command
  4. Parent: Stores write-end of stdin pipe and read-end of stdout pipe
  5. Parent: Registers worker stdout pipe with event loop for read events

File Descriptor Ownership

OwnerFile Descriptors
stdio Bus RuntimeListening socket (if any), client connection FDs, worker pipe FDs (write-end of stdin, read-end of stdout)
Worker ProcessesTheir stdin/stdout (which are the other ends of stdio Bus's pipes)

Event Loop Abstraction

stdio Bus uses platform-specific I/O multiplexing APIs wrapped in a unified abstraction layer. This allows the same application logic to run on both Linux (using epoll) and macOS (using kqueue).

Loading diagram...

Platform abstraction provides unified interface for epoll (Linux) and kqueue (macOS)
Event loop abstraction diagram showing platform-specific implementations for Linux (epoll) and macOS (kqueue)

Event Loop API

FunctionDescription
stdio_bus_event_loop_create()Create a new event loop instance
stdio_bus_event_loop_add()Register a file descriptor with a callback handler
stdio_bus_event_loop_modify()Change event interest (read/write) for a registered FD
stdio_bus_event_loop_remove()Unregister a file descriptor from the event loop
stdio_bus_event_loop_run_once()Process pending events with optional timeout
stdio_bus_event_loop_destroy()Clean up event loop resources

Main Loop Behavior

The main loop runs with a 1-second timeout, checking for:

  • SIGCHLD: Reap exited worker processes and trigger restart if within restart limits
  • Backpressure timeouts: Close connections that have been stalled beyond the configured timeout
  • Shutdown flag: Initiate graceful shutdown sequence when SIGTERM or SIGINT is received

NDJSON Framing

The transport layer handles NDJSON (Newline-Delimited JSON) framing for all client and worker connections. Each message is a single JSON object followed by a newline character.

Loading diagram...

Transport layer handles NDJSON framing for input and output processing
NDJSON framing diagram showing input processing and output queue management

Input Processing

  1. Read available bytes into per-connection input buffer
  2. Scan for newline characters to identify message boundaries
  3. Extract complete lines as messages (without the trailing newline)
  4. Keep incomplete data in buffer for the next read operation
  5. Close connection if buffer exceeds max_input_buffer limit

Output Processing

  1. Queue message data plus newline as a chunk
  2. Attempt immediate write if the output queue was empty
  3. Track partial writes via sent offset in each chunk
  4. Enable write events when data is queued
  5. Disable write events when queue drains completely

Routing Logic

The router maintains two tables for message routing: a session table that maps session IDs to worker assignments, and a request table that tracks pending requests for response correlation.

Loading diagram...

Client-to-worker routing decision tree
Routing logic diagram showing client to worker message routing decisions

Client → Worker Routing

  1. Extract id, sessionId, and method from the JSON message (minimal parsing)
  2. If sessionId exists in session table → route to the assigned worker
  3. If sessionId is new → assign worker via round-robin, create session entry
  4. If no sessionId → route via round-robin (no session affinity)
  5. If message has id (request) → record in pending request table
  6. Write message plus newline to worker's stdin pipe

Worker → Client Routing

  1. Extract id, sessionId, and result/error from JSON
  2. If result or error present (response) → look up id in pending requests → route to originating client
  3. If sessionId present (notification) → look up session → route to session owner
  4. If neither → log warning and discard the message

Routing Tables

TableKeyValuePurpose
Session TablesessionIdWorker assignment + client FDMaintain session affinity for related messages
Request TableRequest idOriginating client FDCorrelate responses with requesting clients

Backpressure Management

Backpressure prevents memory exhaustion when a slow consumer cannot keep up with message production. stdio Bus implements flow control by pausing reads from the corresponding input source when output queues exceed configured limits.

Loading diagram...

Backpressure flow control: pause reads when queue full, resume when drained
Backpressure management diagram showing flow control between client, stdio Bus, and worker

Backpressure Mechanism

  1. Detection: Output queue exceeds max_output_queue bytes
  2. Action: Pause reading from the corresponding input source (set read_paused flag, record timestamp)
  3. Release: Resume reading when queue drops below 50% of the configured limit
  4. Timeout: Close connection if queue remains full beyond backpressure_timeout_sec

Default Resource Limits

LimitDefault ValueDescription
max_input_buffer1 MB (1,048,576 bytes)Maximum bytes buffered per input FD
max_output_queue4 MB (4,194,304 bytes)Maximum bytes queued per output FD
max_restarts5Maximum worker restarts within time window
restart_window_sec60 secondsTime window for counting restarts
drain_timeout_sec30 secondsGraceful shutdown drain timeout
backpressure_timeout_sec60 secondsClose connection if queue full this long

Configuration Override

All resource limits can be overridden in the configuration file under the limits object. See the Getting Started guide for configuration examples.

stdioBus
© 2026 stdio Bus. All rights reserved.