How Apache Maka Manages the PTY Stack and Shell Execution: A Deep Dive into the Runtime Architecture

Apache Maka implements a layered, composable PTY stack that abstracts OS-level pseudo-terminal handling through three core modules—PtyStack, PtyProcessDriver, and PtyScreenCollector—enabling dynamic shell execution with pluggable I/O processing.

The PTY stack and shell execution system in Apache Maka provides a modular foundation for terminal-based workflows. By decoupling the low-level PTY driver from higher-level features like screen capture and output filtering, Maka's runtime layer allows developers to extend terminal behavior without modifying core shell logic. This article examines the implementation details found in the apache/maka repository, focusing on how the runtime constructs, manages, and tears down this architecture.

Core Architecture: Three-Layer PTY Stack Design

Maka's PTY management centers on a stack-based composition pattern implemented across three TypeScript modules in packages/runtime/src/.

PtyStack: The Orchestration Layer

The PtyStack class in [packages/runtime/src/pty-stack.ts](https://github.com/apache/maka/blob/main/packages/runtime/src/pty-stack.ts) maintains an ordered array of PtyLayer objects that process data in sequence.

  • push(layer) — Adds a new layer to the top of the stack at runtime
  • pop() — Removes the topmost layer, disabling its behavior instantly
  • write(data) — Forwards input to the topmost layer, which cascades down to the driver
  • onData events — Propagate from the bottom driver upward through each decorator

This bottom-up construction lets decorators intercept and transform data without the driver knowing they exist.

PtyProcessDriver: The OS Interface

The PtyProcessDriver in [packages/runtime/src/pty-process-driver.ts](https://github.com/apache/maka/blob/main/packages/runtime/src/pty-process-driver.ts) is the only module that directly interacts with the operating system PTY.

It wraps the node-pty library to:

  • Spawn the configured shell (/bin/bash, cmd.exe, zsh, etc.) with user-specified arguments
  • Emit raw byte streams as data events
  • Expose write(), resize(), and kill() methods for stack layers to invoke

The driver remains agnostic to what happens above it in the stack.

PtyScreenCollector: A Decorator Example

The PtyScreenCollector in [packages/runtime/src/pty-screen-collector.ts](https://github.com/apache/maka/blob/main/packages/runtime/src/pty-screen-collector.ts) demonstrates how decorators extend functionality.

  • Parses ANSI escape sequences from driver output
  • Builds a virtual screen model with line-by-line aggregation
  • Exports getScreen() for UI components to retrieve buffer snapshots

Because it's a layer, it can be swapped out at runtime without restarting the shell process.

Execution Flow: From Stack Construction to Shell Teardown

The PTY stack and shell execution lifecycle follows five distinct phases:

  1. Stack Construction — new PtyStack() creates the container; PtyProcessDriver is pushed first as the foundation, followed by optional decorators like PtyScreenCollector

  2. Shell Launch — The driver spawns the user-configured shell with inherited environment variables and working directory

  3. I/O Routing — Input flows down (stack.write() → decorator → driver → PTY stdin), while output bubbles up (driver data event → decorator processing → UI handlers)

  4. Dynamic Layer Management — Features activate or deactivate via push() and pop() without shell interruption

  5. Termination — stack.dispose() triggers kill() on the driver, stream closure, and layer cleanup

Practical Implementation: Creating a Configured PTY Session

The following example from the Maka source demonstrates stack assembly and usage:

import { PtyStack } from '@maka/runtime';
import { PtyProcessDriver } from '@maka/runtime';
import { PtyScreenCollector } from '@maka/runtime';

const stack = new PtyStack();

// Bottom driver – spawns the real shell
const driver = new PtyProcessDriver({
  shell: process.env.SHELL ?? '/bin/bash',
  args: [],
  cols: 80,
  rows: 24,
});
stack.push(driver);

// Optional decorator – captures the screen buffer
const screen = new PtyScreenCollector();
stack.push(screen);

// Write user input (e.g., from a web UI)
stack.write('ls -la\n');

// Listen for processed output (after all decorators)
stack.onData(data => {
  console.log('Terminal output:', data);
});

// Retrieve the current screen snapshot (useful for UI rendering)
const snapshot = screen.getScreen();
console.log('Current screen buffer:', snapshot);

// Clean up when the session ends
stack.dispose();

Key configuration parameters for PtyProcessDriver:

  • shell — Path to the executable (defaults to process.env.SHELL)
  • args — Array of arguments passed to the shell
  • cols / rows — Initial terminal dimensions

Layer Composition Patterns and Use Cases

The stack architecture enables several runtime-modifiable behaviors:

Pattern Implementation Benefit
Screen recording Push PtyScreenCollector Capture full terminal state for replay or debugging
Output filtering Custom decorator layer Sanitize or transform sensitive data before UI display
Command validation Middleware layer Intercept and approve/reject user input
Multi-tenant isolation Driver-per-stack with shared decorators Resource-efficient shell pooling

Because layers are pure decorators, they compose predictably: order matters, but dependencies between layers are explicit through the stack interface.

Testing and Reliability

Maka's test suite validates this architecture in:

The separation of concerns allows isolated unit testing: mock drivers can verify decorator logic without spawning real shells, while driver tests focus solely on node-pty integration.

Summary

  • PtyStack (pty-stack.ts) provides the compositional container for ordered layer execution
  • PtyProcessDriver (pty-process-driver.ts) encapsulates all OS-level PTY and shell management
  • PtyScreenCollector (pty-screen-collector.ts) illustrates the decorator pattern for output processing
  • Dynamic layer manipulation via push()/pop() enables runtime feature toggling without shell restart
  • Bidirectional I/O routing cascades input down and propagates events up through the stack

Frequently Asked Questions

What shell does Maka use by default?

Maka reads the SHELL environment variable and falls back to /bin/bash when unspecified. This default is hardcoded in PtyProcessDriver configuration logic in packages/runtime/src/pty-process-driver.ts.

Can I add or remove PTY layers while a shell is running?

Yes. The PtyStack.push() and PtyStack.pop() methods operate at runtime. Adding a PtyScreenCollector captures subsequent output; removing it immediately stops screen collection without terminating the underlying shell process.

How does Maka handle ANSI escape sequences?

The PtyScreenCollector layer parses ANSI codes to maintain a virtual screen model. Applications reading raw output can access pre-parsed screen state through getScreen(), while onData handlers receive the original byte stream.

Is node-pty the only PTY backend supported?

The current implementation in pty-process-driver.ts exclusively uses node-pty. The stack architecture theoretically allows alternative drivers implementing the PtyLayer interface, though no additional drivers are present in the Apache Maka repository.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →