> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/ComposioHQ/agent-orchestrator/llms.txt
> Use this file to discover all available pages before exploring further.

# Process Runtime Plugin

> Run agents as child processes with direct stdio control and minimal overhead

The process runtime plugin manages agent execution as Node.js child processes, providing lightweight, headless execution with full output capture.

## Overview

The process runtime spawns agents as direct child processes with piped stdio, offering:

* Minimal overhead (no tmux server required)
* Full stdout/stderr capture in a rolling buffer
* Automatic cleanup when the orchestrator stops
* Process group management for proper child termination

<Warning>
  Process sessions do not persist if the orchestrator crashes. For long-running agents that need to survive orchestrator restarts, use the tmux runtime.
</Warning>

## Configuration

Configure the process runtime in `agent-orchestrator.yaml`:

```yaml theme={null}
plugins:
  runtime: process
```

## Requirements

No external dependencies. The plugin uses Node.js built-in `child_process` module.

## Runtime API

### create(config)

Spawns a new child process with the specified launch command.

<ParamField path="config.sessionId" type="string" required>
  Session identifier (must match `[a-zA-Z0-9_-]+`)
</ParamField>

<ParamField path="config.workspacePath" type="string" required>
  Working directory for the process (`cwd`)
</ParamField>

<ParamField path="config.launchCommand" type="string" required>
  Shell command to execute (runs with `shell: true`)
</ParamField>

<ParamField path="config.environment" type="Record<string, string>">
  Environment variables merged with `process.env`
</ParamField>

<Note>
  The plugin uses `shell: true` intentionally, as launch commands may contain pipes, redirects, or other shell syntax. Ensure launch commands come from trusted sources (YAML config, not user input).
</Note>

### sendMessage(handle, message)

Writes a message to the process's stdin, followed by a newline.

<Warning>
  If stdin is not writable (closed or process exited), this throws an error. Always check `isAlive()` before sending messages.
</Warning>

### getOutput(handle, lines)

Returns the last N lines from the rolling output buffer.

<ParamField path="lines" type="number" default="50">
  Number of lines to retrieve from the buffer (max 1000)
</ParamField>

<Info>
  The plugin maintains a rolling buffer of the last 1000 lines. Older lines are automatically discarded to prevent unbounded memory growth.
</Info>

### destroy(handle)

Terminates the process gracefully (SIGTERM), then forcefully (SIGKILL) after 5 seconds.

<Accordion title="Process termination flow">
  1. Send SIGTERM to the entire process group (via negative PID)
  2. Wait up to 5 seconds for graceful exit
  3. Send SIGKILL to force termination if still running
  4. Remove session from internal map

  Process group termination ensures child commands spawned by the shell are also killed, not orphaned.
</Accordion>

### isAlive(handle)

Checks if the process has exited (`exitCode === null`).

## Usage Examples

### Basic Agent Launch

```typescript theme={null}
import { create } from '@composio/ao-plugin-runtime-process';

const runtime = create();

const handle = await runtime.create({
  sessionId: 'agent-1',
  workspacePath: '/path/to/workspace',
  launchCommand: 'claude --dangerously-skip-permissions',
  environment: {
    AO_SESSION_ID: 'agent-1',
    AO_PROJECT_ID: 'my-project',
  },
});

console.log('Process PID:', handle.data.pid);
```

### Send Messages to Agent

```typescript theme={null}
// Check if agent is still running
if (await runtime.isAlive(handle)) {
  await runtime.sendMessage(handle, 'Fix the type errors in src/index.ts');
}
```

### Monitor Output

```typescript theme={null}
// Get last 100 lines of output
const output = await runtime.getOutput(handle, 100);
console.log(output);

// Check for specific patterns
if (output.includes('ERROR')) {
  console.error('Agent encountered an error');
}
```

### Graceful Shutdown

```typescript theme={null}
// Cleanup when done
await runtime.destroy(handle);

// Verify termination
const alive = await runtime.isAlive(handle);
console.log('Process still running:', alive); // false
```

## Advanced Features

### Rolling Output Buffer

The plugin captures stdout and stderr into a single rolling buffer with:

* Max capacity: 1000 lines
* Automatic trimming when limit exceeded
* Separate partial-line buffers for stdout/stderr to prevent interleaved corruption

```typescript theme={null}
// Implementation detail (informational)
const MAX_OUTPUT_LINES = 1000;

// Buffer auto-trims when it grows beyond max
if (outputBuffer.length > MAX_OUTPUT_LINES) {
  outputBuffer.splice(0, outputBuffer.length - MAX_OUTPUT_LINES);
}
```

### Process Group Management

The plugin uses `detached: true` to spawn the child in its own process group, then kills the entire group (negative PID) on destroy:

```bash theme={null}
# Kills the shell AND any commands it spawned
process.kill(-pid, 'SIGTERM');
```

This prevents orphaned child processes when agents spawn subcommands.

### Exit Handling

The plugin attaches exit handlers immediately (before any `await`) to ensure fast-exiting processes don't slip through:

```typescript theme={null}
child.once('exit', () => {
  entry.outputBuffer.push(`[process exited with code ${child.exitCode}]`);
  processes.delete(handleId);
});
```

### Partial Line Buffering

Each stream (stdout, stderr) maintains its own partial-line buffer to handle split writes:

```typescript theme={null}
function makeAppendOutput(): (data: Buffer) => void {
  let partial = "";
  return (data: Buffer) => {
    const text = partial + data.toString("utf-8");
    const lines = text.split("\n");
    partial = lines.pop()!; // Save incomplete line
    outputBuffer.push(...lines);
  };
}
```

This ensures interleaved stdout/stderr writes don't corrupt each other.

## Troubleshooting

<Accordion title="Process exits immediately after spawn">
  **Cause**: Launch command failed or invalid

  **Solution**:

  1. Check `launchCommand` syntax (test in a shell first)
  2. Verify required binaries are in PATH
  3. Check `workspacePath` exists and is accessible
  4. Inspect exit code in output buffer: `[process exited with code X]`
</Accordion>

<Accordion title="sendMessage throws 'stdin not writable'">
  **Cause**: Process has exited or stdin was closed

  **Solution**:

  ```typescript theme={null}
  // Always check isAlive before sending
  if (await runtime.isAlive(handle)) {
    await runtime.sendMessage(handle, 'message');
  } else {
    console.error('Process has exited');
  }
  ```
</Accordion>

<Accordion title="Duplicate session ID error">
  **Cause**: Trying to create two sessions with the same ID

  **Solution**: Each session must have a unique ID. Call `destroy()` before re-creating:

  ```typescript theme={null}
  // Clean up first
  await runtime.destroy(handle);

  // Now safe to recreate
  const newHandle = await runtime.create({ sessionId: 'agent-1', ... });
  ```
</Accordion>

<Accordion title="Child processes survive after destroy">
  **Cause**: Process group kill failed (rare)

  **Solution**: Manually kill orphaned processes:

  ```bash theme={null}
  # Find orphaned agents
  ps aux | grep claude

  # Kill by PID
  kill -9 <pid>
  ```
</Accordion>

<Accordion title="Output buffer missing early lines">
  **Cause**: Buffer only stores last 1000 lines

  **Solution**:

  * For full output capture, redirect to a file in your launch command:
    ```yaml theme={null}
    launchCommand: "claude 2>&1 | tee session.log"
    ```
  * Increase `MAX_OUTPUT_LINES` in plugin source if needed (requires rebuild)
</Accordion>

## Comparison with Tmux Runtime

| Feature | Process | Tmux |
| - | - | - |
| **Overhead** | Minimal (direct child) | Higher (tmux server) |
| **Persistence** | Tied to orchestrator | Survives orchestrator crashes |
| **Attachment** | No interactive shell | Full terminal via `tmux attach` |
| **Output Capture** | Rolling buffer (1000 lines) | Full scrollback via tmux |
| **Cleanup** | Automatic on exit | Manual (sessions persist) |
| **Use Case** | CI/CD, headless automation | Interactive debugging |

<Info>
  Use process runtime for production and automated workflows. Use tmux for development when you need to attach and debug agents interactively.
</Info>

## Security Considerations

<Warning>
  The plugin uses `shell: true`, which enables shell syntax (pipes, redirects) but also introduces command injection risk if launch commands include untrusted input.
</Warning>

**Safe usage:**

```typescript theme={null}
// ✅ GOOD: Static commands from config
launchCommand: 'claude --dangerously-skip-permissions'

// ✅ GOOD: Dynamic args with proper escaping
import { shellEscape } from '@composio/ao-core';
launchCommand: `claude --model ${shellEscape(userModel)}`

// ❌ BAD: Unescaped user input (shell injection risk)
launchCommand: `claude --message "${userPrompt}"`
```

**Design note:** `shell: true` is intentional for the launchCommand field, which comes from trusted YAML config and may contain pipes or redirects. Never interpolate untrusted user input into launch commands.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.