Files
Keep/.ralph/drivers/DRIVER_INTERFACE.md

15 KiB

Ralph Driver Interface Contract

Overview

The Ralph loop loads a platform driver by sourcing ralph/drivers/${PLATFORM_DRIVER}.sh inside load_platform_driver() (ralph_loop.sh line 296). The PLATFORM_DRIVER variable defaults to "claude-code" and can be overridden via .ralphrc.

After sourcing, ralph_loop.sh immediately calls three functions to populate core globals:

  1. driver_valid_tools -- populates VALID_TOOL_PATTERNS
  2. driver_cli_binary -- stored in CLAUDE_CODE_CMD
  3. driver_display_name -- stored in DRIVER_DISPLAY_NAME

File naming convention: ${PLATFORM_DRIVER}.sh (e.g., claude-code.sh, codex.sh).

Scope: This documents the sourceable driver contract used by ralph_loop.sh. Helper scripts like cursor-agent-wrapper.sh are out of scope.

Calling conventions:

  • Data is returned via stdout (echo).
  • Booleans are returned via exit status (0 = true, 1 = false).
  • Some functions mutate global arrays as side effects.

Required Hooks

Called unconditionally by ralph_loop.sh with no declare -F guard or default stub. Omitting any of these will break the loop at runtime.

driver_name()

driver_name()

No arguments. Echo a short lowercase identifier (e.g., "claude-code", "codex"). Used at line 2382 to gate platform-specific logic.

driver_display_name()

driver_display_name()

No arguments. Echo a human-readable name (e.g., "Claude Code", "OpenAI Codex"). Stored in DRIVER_DISPLAY_NAME, used in log messages and tmux pane titles.

driver_cli_binary()

driver_cli_binary()

No arguments. Echo the CLI executable name or resolved path (e.g., "claude", "codex"). Stored in CLAUDE_CODE_CMD. Most drivers return a static string; cursor resolves dynamically.

driver_valid_tools()

driver_valid_tools()

No arguments. Must populate the global VALID_TOOL_PATTERNS array with the platform's recognized tool name patterns. Used by validate_allowed_tools().

driver_build_command(prompt_file, loop_context, session_id)

driver_build_command "$prompt_file" "$loop_context" "$session_id"

Three string arguments:

Argument Description
$1 prompt_file Path to the prompt file (e.g., .ralph/PROMPT.md)
$2 loop_context Context string for session continuity (may be empty)
$3 session_id Session ID for resume (empty string = new session)

Must populate the global CLAUDE_CMD_ARGS array with the complete CLI command and arguments. Return 0 on success, 1 on failure (e.g., prompt file not found).

Reads globals: CLAUDE_OUTPUT_FORMAT, CLAUDE_PERMISSION_MODE (claude-code only), CLAUDE_ALLOWED_TOOLS (claude-code only), CLAUDE_USE_CONTINUE.


Optional Overrides with Loop Defaults

ralph_loop.sh defines default stubs at lines 284 and 288. All existing drivers override them, but a minimal driver can rely on the defaults.

driver_supports_tool_allowlist()

driver_supports_tool_allowlist()

No arguments. Return 0 if the driver supports --allowedTools filtering, 1 otherwise.

Default: returns 1 (false). Currently only claude-code returns 0.

driver_permission_denial_help()

driver_permission_denial_help()

No arguments. Print platform-specific troubleshooting guidance when the loop detects a permission denial.

Reads: RALPHRC_FILE, DRIVER_DISPLAY_NAME.

Default: generic guidance text.


Optional Capability Hooks

Guarded by declare -F checks or wrapper functions in ralph_loop.sh (lines 1917-1954, 1576-1583). Safe to omit -- documented fallback behavior applies.

driver_supports_sessions()

driver_supports_sessions()

No arguments. Return 0 if the driver supports session resume, 1 otherwise.

If not defined: assumed true (0).

Implemented by all 5 drivers; copilot returns 1.

driver_supports_live_output()

driver_supports_live_output()

No arguments. Return 0 if the driver supports structured streaming output (stream-json or JSONL), 1 otherwise.

If not defined: assumed true (0).

copilot returns 1; all others return 0.

driver_prepare_live_command()

driver_prepare_live_command()

No arguments. Transform CLAUDE_CMD_ARGS into LIVE_CMD_ARGS for streaming mode.

If not defined: LIVE_CMD_ARGS is copied from CLAUDE_CMD_ARGS unchanged.

Driver Behavior
claude-code Replaces json with stream-json and adds --verbose --include-partial-messages
codex Copies as-is (output is already suitable)
opencode Copies as-is (output is already suitable)
cursor Replaces json with stream-json

driver_stream_filter()

driver_stream_filter()

No arguments. Echo a jq filter expression that transforms raw streaming events into displayable text.

If not defined: returns "empty" (no output).

Each driver has a platform-specific filter; copilot returns '.' (passthrough).

driver_extract_session_id_from_output(output_file)

driver_extract_session_id_from_output "$output_file"

One argument: path to the CLI output log file. Echo the extracted session ID.

Tried first in the session save chain before the generic jq extractor. Only opencode implements this (uses sed to extract from a "session" JSON object).

driver_fallback_session_id(output_file)

driver_fallback_session_id "$output_file"

One argument: path to the output file (caller passes it at line 1583; the only implementation in opencode ignores it).

Last-resort session ID recovery when both driver-specific and generic extractors fail. Only opencode implements this (queries opencode session list --format json).


Conventional Metadata Hooks

Present in every driver but NOT called by ralph_loop.sh. Consumed by bmalph's TypeScript doctor/preflight checks in src/platform/. A new driver should implement these for bmalph doctor compatibility.

driver_min_version()

driver_min_version()

No arguments. Echo the minimum required CLI version as a semver string.

driver_check_available()

driver_check_available()

No arguments. Return 0 if the CLI binary is installed and reachable, 1 otherwise.


Global Variables

Written by drivers

Variable Written by Type Description
VALID_TOOL_PATTERNS driver_valid_tools() array Valid tool name patterns for allowlist validation
CLAUDE_CMD_ARGS driver_build_command() array Complete CLI command with all arguments
LIVE_CMD_ARGS driver_prepare_live_command() array Modified command for live streaming

Read by drivers (set by ralph_loop.sh or .ralphrc)

Variable Used in Description
CLAUDE_OUTPUT_FORMAT driver_build_command() "json" or "text"
CLAUDE_PERMISSION_MODE driver_build_command() (claude-code) Permission mode flag, default "bypassPermissions"
CLAUDE_ALLOWED_TOOLS driver_build_command() (claude-code) Comma-separated tool allowlist
CLAUDE_USE_CONTINUE driver_build_command() "true" or "false", gates session resume
RALPHRC_FILE driver_permission_denial_help() Path to .ralphrc config file
DRIVER_DISPLAY_NAME driver_permission_denial_help() Human-readable driver name

Environment globals (cursor-specific)

Variable Used in Description
OS, OSTYPE driver_running_on_windows() OS detection
LOCALAPPDATA driver_localappdata_cli_binary() Windows local app data path
PATH driver_find_windows_path_candidate() Manual PATH scanning on Windows

Set by ralph_loop.sh from driver output

Variable Source Description
CLAUDE_CODE_CMD driver_cli_binary() CLI binary name/path
DRIVER_DISPLAY_NAME driver_display_name() Human-readable display name

Capability Matrix

Capability claude-code codex opencode copilot cursor
Tool allowlist (driver_supports_tool_allowlist) yes no no no no
Session continuity (driver_supports_sessions) yes yes yes no yes
Structured live output (driver_supports_live_output) yes yes yes no yes
Live command transform (driver_prepare_live_command) transform passthrough passthrough -- transform
Stream filter (driver_stream_filter) complex jq JSONL select JSONL select passthrough complex jq
Custom session extraction (driver_extract_session_id_from_output) -- -- yes -- --
Fallback session lookup (driver_fallback_session_id) -- -- yes -- --
Dynamic binary resolution (driver_cli_binary) static static static static dynamic

Creating a New Driver

Minimal driver skeleton

#!/usr/bin/env bash
# ralph/drivers/my-platform.sh
# Driver for My Platform CLI
#
# Sourced by ralph_loop.sh via load_platform_driver().
# PLATFORM_DRIVER must be set to "my-platform" in .ralphrc.

# ---------------------------------------------------------------------------
# Required hooks (5) -- omitting any of these breaks the loop
# ---------------------------------------------------------------------------

# Short lowercase identifier used to gate platform-specific logic.
driver_name() {
  echo "my-platform"
}

# Human-readable name for log messages and tmux pane titles.
driver_display_name() {
  echo "My Platform"
}

# CLI executable name or resolved path.
driver_cli_binary() {
  echo "my-platform"
}

# Populate VALID_TOOL_PATTERNS with recognized tool name patterns.
# Used by validate_allowed_tools() to check allowlist entries.
driver_valid_tools() {
  VALID_TOOL_PATTERNS=(
    "Read"
    "Write"
    "Edit"
    "Bash"
    # Add your platform's tool patterns here
  )
}

# Build the complete CLI command array.
# $1 = prompt_file  Path to .ralph/PROMPT.md
# $2 = loop_context Context string for session continuity (may be empty)
# $3 = session_id   Session ID for resume (empty = new session)
driver_build_command() {
  local prompt_file="$1"
  local loop_context="$2"
  local session_id="$3"

  if [[ ! -f "$prompt_file" ]]; then
    return 1
  fi

  CLAUDE_CMD_ARGS=(
    "my-platform"
    "--prompt" "$prompt_file"
    "--output-format" "${CLAUDE_OUTPUT_FORMAT:-json}"
  )

  # Append session resume flag if continuing a session
  if [[ "$CLAUDE_USE_CONTINUE" == "true" && -n "$session_id" ]]; then
    CLAUDE_CMD_ARGS+=("--session" "$session_id")
  fi

  # Append context if provided
  if [[ -n "$loop_context" ]]; then
    CLAUDE_CMD_ARGS+=("--context" "$loop_context")
  fi

  return 0
}

# ---------------------------------------------------------------------------
# Optional overrides (2) -- loop provides default stubs
# ---------------------------------------------------------------------------

# Return 0 if the platform supports --allowedTools filtering, 1 otherwise.
driver_supports_tool_allowlist() {
  return 1
}

# Print troubleshooting guidance on permission denial.
driver_permission_denial_help() {
  echo "Permission denied. Check that $DRIVER_DISPLAY_NAME has the required permissions."
  echo "See $RALPHRC_FILE for configuration options."
}

# ---------------------------------------------------------------------------
# Metadata hooks (2) -- used by bmalph doctor, not called by ralph_loop.sh
# ---------------------------------------------------------------------------

# Minimum required CLI version (semver).
driver_min_version() {
  echo "1.0.0"
}

# Return 0 if the CLI binary is installed and reachable, 1 otherwise.
driver_check_available() {
  command -v my-platform &>/dev/null
}

Checklist

  • All 5 required hooks implemented (driver_name, driver_display_name, driver_cli_binary, driver_valid_tools, driver_build_command)
  • driver_valid_tools populates VALID_TOOL_PATTERNS with your platform's tool names
  • driver_build_command handles all three arguments correctly (prompt_file, loop_context, session_id)
  • driver_check_available returns 0 only when the CLI is installed
  • File named ${platform_id}.sh matching the PLATFORM_DRIVER value in .ralphrc
  • Register corresponding platform definition in src/platform/ for bmalph CLI integration
  • Tested with bmalph doctor

Session ID Recovery Chain

When the loop needs to persist a session ID for resume, it follows a three-step priority chain (ralph_loop.sh lines 1574-1588):

  1. driver_extract_session_id_from_output($output_file) -- Driver-specific extraction. If the function exists (declare -F guard) and echoes a non-empty string, that value is used. Only opencode implements this (uses sed to extract from a "session" JSON object).

  2. extract_session_id_from_output($output_file) -- Generic jq extractor from response_analyzer.sh. Searches the output file for .sessionId, .metadata.session_id, and .session_id in that order.

  3. driver_fallback_session_id($output_file) -- CLI-based last-resort recovery. If the function exists and the previous steps produced nothing, this is called. Only opencode implements this (queries opencode session list --format json).

The first step that returns a non-empty string wins. If all three steps fail, no session ID is saved and the next iteration starts a fresh session.