Python API Reference

This section documents every public Ralph Workflow subpackage. All modules listed below are part of the maintained Python package under ralph-workflow/ralph/.

Top-Level

The top-level package exposes version metadata, the CLI entrypoint (ralph.main), platform detection helpers, and the runtime verification primitives used by make verify. The package docstring lists the major subpackages a contributor will touch most often. See Developer Internals for the contributor map and Configuration Reference for how layered config and policy defaults are wired together.

ralph

Top-level package for Ralph Workflow.

The public Python package is intentionally small at the root: it exposes version metadata and points users toward the major subpackages that make up the system.

Useful pydoc entry points:

  • ralph.cli for the Typer CLI application

  • ralph.config for configuration models and loading

  • ralph.pipeline for orchestration state and reducer/orchestrator logic

  • ralph.phases for phase dispatch

  • ralph.session_runtime for host-owned mini-pipeline / standalone session runtime helpers

  • ralph.mcp for the MCP bridge and standalone server helpers

  • ralph.git for GitPython-backed repository operations

  • ralph.workspace for filesystem abstractions used by production code and tests

ralph.main

Entry point for python -m ralph.main.

ralph.logging

Logging configuration for Ralph Workflow.

This module configures loguru for structured logging throughout the Ralph Workflow CLI. Log levels map to verbosity as follows:

0 (QUIET)  -> ERROR only
1 (NORMAL) -> WARNING
2 (VERBOSE) -> INFO
3 (FULL)   -> DEBUG
4+ (DEBUG) -> TRACE

Custom levels registered on first configure_logging() call:

SUCCESS (25): between INFO (20) and WARNING (30)
MILESTONE (35): between WARNING (30) and ERROR (40)
class ralph.logging.RalphLogger(base_logger=None)[source]

Bases: object

Structured logger for Ralph Workflow pipeline events.

This class provides convenient methods for common logging scenarios in the Ralph Workflow pipeline.

Parameters:

base_logger (Logger | None)

agent_invoked(agent_name, drain)[source]

Log agent invocation.

Parameters:
  • agent_name (str) – Name of the agent being invoked.

  • drain (str) – Drain name.

Return type:

None

agent_output(drain, line)[source]

Log agent output line.

Parameters:
  • drain (str) – Drain name.

  • line (str) – Output line from agent.

Return type:

None

checkpoint_loaded(path)[source]

Log checkpoint load.

Parameters:

path (str) – Path to checkpoint file.

Return type:

None

checkpoint_saved(path)[source]

Log checkpoint save.

Parameters:

path (str) – Path to checkpoint file.

Return type:

None

phase_complete(phase, drain)[source]

Log the completion of a pipeline phase.

Parameters:
  • phase (str) – Phase name.

  • drain (str) – Drain name.

Return type:

None

phase_start(phase, drain)[source]

Log the start of a pipeline phase.

Parameters:
  • phase (str) – Phase name.

  • drain (str) – Drain name.

Return type:

None

pipeline_error(phase, error)[source]

Log pipeline error.

Parameters:
  • phase (str) – Current phase name.

  • error (str) – Error message.

Return type:

None

policy_loaded(config_dir)[source]

Log policy load.

Parameters:

config_dir (str) – Configuration directory path.

Return type:

None

validation_error(error)[source]

Log validation error.

Parameters:

error (str) – Error message.

Return type:

None

class ralph.logging.WorkerSinkHandle(sink_id, log_path)[source]

Bases: object

Handle returned by bind_worker_sink to identify a per-worker loguru sink.

Parameters:
  • sink_id (int)

  • log_path (Path)

ralph.logging.bind_worker_sink(unit_id, log_dir, run_id='default')[source]

Add a per-worker loguru sink that filters to unit_id and returns its handle.

Parameters:
  • unit_id (str)

  • log_dir (Path)

  • run_id (str)

Return type:

WorkerSinkHandle

ralph.logging.configure_logging(verbosity=1, *, log_directory=None, run_id=None, structured=False, rotation='10 MB')[source]

Configure loguru for Ralph Workflow CLI output.

Removes the default handler and adds a new handler with formatting based on verbosity level. Higher verbosity shows more detail.

Parameters:
  • verbosity (int) – Verbosity level (0=quiet/errors only, 1=normal, 2=verbose, 3=debug, 4+=trace).

  • log_directory (str | Path | None) – Optional base directory for file logging.

  • run_id (str | None) – Optional run identifier for per-run log directories.

  • structured (bool) – Whether to emit JSON structured logs.

  • rotation (str | int | None) – Optional loguru rotation policy for file handlers.

Returns:

Logging session with resolved paths and bound logger helpers.

Return type:

LoggingSession

ralph.logging.get_logger()[source]

Get the configured ralph logger.

Returns:

The loguru logger instance.

Return type:

Logger

ralph.logging.remove_worker_sink(handle)[source]

Remove the per-worker loguru sink identified by handle.

Parameters:

handle (WorkerSinkHandle)

Return type:

None

ralph.onboarding

Shared onboarding copy for CLI, validation, and docs-facing messaging.

ralph.onboarding.fallback_next_steps()[source]

Return rerun guidance after init when files already exist.

Return type:

tuple[str, …]

ralph.onboarding.fresh_workspace_next_steps()[source]

Return the minimal next steps for a completely fresh workspace.

Return type:

tuple[str, …]

ralph.onboarding.getting_started_pointer_sentence()[source]

Return the canonical getting-started docs pointer sentence.

Return type:

str

ralph.onboarding.init_help_text()[source]

Return top-level help text for the canonical init command.

Return type:

str

ralph.onboarding.init_local_config_help_text()[source]

Return top-level help text for the optional local override command.

Return type:

str

ralph.onboarding.init_local_config_override_explanation()[source]

Return the canonical explanation for the local override command.

Return type:

str

ralph.onboarding.missing_prompt_validation_hint()[source]

Return canonical validation guidance when PROMPT.md is missing.

Return type:

str

ralph.onboarding.starter_prompt_template()[source]

Return the canonical starter PROMPT.md template.

Return type:

str

ralph.onboarding.starter_prompt_validation_hint()[source]

Return canonical validation guidance when the starter sentinel is still present.

Return type:

str

ralph.onboarding.welcome_panel_next_steps()[source]

Return the richer onboarding steps shown after initialization succeeds.

Return type:

tuple[str, …]

ralph.install

Installation helpers for Ralph Workflow dev and stable builds.

Two builds are kept deliberately separate so they never shadow each other:

  • Dev build — an editable install of the current checkout into the active environment (install_dev_checkout). Run it with uv run ralph; it registers no global command, so it cannot collide with a stable ralph.

  • Stable build — a pinned release installed as an isolated global ralph command via uv tool (install_stable_release).

See CONTRIBUTING.md (§”Dev build vs stable build”) for the workflow.

class ralph.install.LauncherWriter(*args, **kwargs)[source]

Bases: Protocol

Protocol for writing the dev launcher script to disk.

class ralph.install.RunCommand(*args, **kwargs)[source]

Bases: Protocol

Protocol for the subprocess runner passed to the install helpers.

ralph.install.install_dev_checkout(*, run=<function _run_command>, uv_executable, cwd, launcher_dir, write_launcher=<function write_dev_launcher>)[source]

Set up the current checkout as a dev build via uv sync.

Syncs the project’s own uv environment (.venv) so it holds the editable project plus the dev extra, then writes an rdev launcher into launcher_dir so the dev build has a stable command name. rdev runs the working tree with uv run and never shadows the stable ralph installed via install_stable_release().

Parameters:
Return type:

None

ralph.install.install_stable_release(*, run=<function _run_command>, uv_executable, cwd, version=None)[source]

Install a stable release as the isolated global ralph command.

Uses uv tool install so the stable build lives in its own environment and stays independent of the working tree.

Without version the latest published release is installed, and an already-installed older ralph is upgraded: --force re-runs the install even when the tool is already present (a plain uv tool install is a no-op in that case), and --upgrade (which implies --refresh) re-resolves against PyPI so a newer release is picked up. Pass version to pin a specific release instead.

Parameters:
  • run (RunCommand)

  • uv_executable (str | None)

  • cwd (Path)

  • version (str | None)

Return type:

None

ralph.install.main(argv=None)[source]

Install the dev build by default, or the stable build with --stable.

Parameters:

argv (Sequence[str] | None)

Return type:

int

ralph.install.render_dev_launcher(package_dir)[source]

Return the contents of the rdev launcher for package_dir.

The launcher runs the dev build via uv run against the checkout while staying in the caller’s working directory (the workspace ralph operates on); only the project environment is sourced from package_dir.

Parameters:

package_dir (Path)

Return type:

str

ralph.install.write_dev_launcher(path, content)[source]

Write content to path and mark it executable.

Parameters:
  • path (Path)

  • content (str)

Return type:

None

ralph.platform

Platform detection and OS/architecture identification helpers.

This package detects the host operating system, CPU architecture, Python environment type, and available package manager. Detection results are used by installers, runtime configuration, and diagnostic output.

Main entry points:

  • detect_platform() — full platform detection; returns a PlatformInfo.

  • current_platform() — cached singleton PlatformInfo for the current host.

  • PlatformInfo — composite result (OperatingSystem, Architecture, EnvironmentInfo, package manager string).

  • detect_environment() / EnvironmentInfo — virtualenv/conda/pyenv detection.

  • detect_operating_system() / OperatingSystem — Linux, macOS, or Windows.

  • detect_architecture() / Architecture — x86_64, arm64, etc.

  • detect_package_manager() — identifies the primary package manager (apt, brew, …).

ralph.platform.detection

Helpers for platform and environment detection.

class ralph.platform.detection.DetectPlatformKwargs[source]

Bases: TypedDict

Keyword arguments accepted by detect_platform for dependency injection.

ralph.platform.detection.current_platform()[source]

Return the detected platform for the current runtime.

Return type:

PlatformInfo

ralph.platform.detection.detect_architecture(machine_name=None)[source]

Normalize platform.machine() into a stable architecture enum.

Parameters:

machine_name (str | None)

Return type:

Architecture

ralph.platform.detection.detect_environment(env=None, *, os_name=None, release=None, cgroup_text=None, proc_root=PosixPath('/proc'))[source]

Detect runtime environment markers such as CI, containers, and WSL.

Parameters:
  • env (Mapping[str, str] | None)

  • os_name (OperatingSystem | None)

  • release (str | None)

  • cgroup_text (str | None)

  • proc_root (Path)

Return type:

EnvironmentInfo

ralph.platform.detection.detect_operating_system(system_name=None)[source]

Normalize platform.system() into a stable OS enum.

Parameters:

system_name (str | None)

Return type:

OperatingSystem

ralph.platform.detection.detect_package_manager(os_name=None, *, search_path=None, command_lookup=None)[source]

Detect the first supported package manager available on the current OS.

Parameters:
  • os_name (OperatingSystem | None)

  • search_path (str | None)

  • command_lookup (Callable[[str, str | None], bool] | None)

Return type:

str | None

ralph.platform.detection.detect_platform(**kwargs)[source]

Build a complete platform profile for the current runtime.

Parameters:

kwargs (Unpack[DetectPlatformKwargs])

Return type:

PlatformInfo

ralph.platform.models

Data models for platform detection and platform-specific behavior.

class ralph.platform.models.Architecture(*values)[source]

Bases: StrEnum

Normalized CPU architecture names.

class ralph.platform.models.EnvironmentInfo(ci=False, container=False, wsl=False, codespaces=False, ssh=False)[source]

Bases: object

Detected runtime environment traits.

Parameters:
  • ci (bool)

  • container (bool)

  • wsl (bool)

  • codespaces (bool)

  • ssh (bool)

markers()[source]

Return enabled environment markers in display order.

Return type:

list[str]

class ralph.platform.models.OperatingSystem(*values)[source]

Bases: StrEnum

Normalized operating system names.

class ralph.platform.models.PlatformInfo(os=OperatingSystem.UNKNOWN, architecture=Architecture.UNKNOWN, environment=<factory>, package_manager=None)[source]

Bases: object

Complete platform profile used by Ralph’s Python implementation.

Parameters:
executable_name(command)[source]

Return the executable name for the current platform.

Parameters:

command (str)

Return type:

str

install_command(package)[source]

Return the package installation command for the detected package manager.

Parameters:

package (str)

Return type:

list[str] | None

property is_posix: bool

Return True for POSIX-like platforms.

summary()[source]

Return a concise human-readable platform summary.

Return type:

str

ralph.platform.architecture

Normalized CPU architecture names.

class ralph.platform.architecture.Architecture(*values)[source]

Bases: StrEnum

Normalized CPU architecture names.

ralph.platform.environment_info

Detected runtime environment traits.

class ralph.platform.environment_info.EnvironmentInfo(ci=False, container=False, wsl=False, codespaces=False, ssh=False)[source]

Bases: object

Detected runtime environment traits.

Parameters:
  • ci (bool)

  • container (bool)

  • wsl (bool)

  • codespaces (bool)

  • ssh (bool)

markers()[source]

Return enabled environment markers in display order.

Return type:

list[str]

ralph.platform.operating_system

Normalized operating system names.

class ralph.platform.operating_system.OperatingSystem(*values)[source]

Bases: StrEnum

Normalized operating system names.

ralph.contrib

Contributor workflow helpers for Ralph Workflow maintainers.

ralph.contrib.cla

Validate CLA agreement state for Codeberg and GitHub pull requests.

class ralph.contrib.cla.ClaCheckResult(ok, message)[source]

Bases: object

Outcome of a contributor license agreement verification.

Parameters:
  • ok (bool)

  • message (str)

ralph.contrib.cla.evaluate_body(body)[source]

Return whether a pull request body contains the checked CLA line.

Parameters:

body (str | None)

Return type:

ClaCheckResult

ralph.contrib.cla.evaluate_codeberg_environment(env, *, fetch_json=None)[source]

Evaluate Woodpecker/Codeberg PR metadata, fetching the PR body when needed.

Parameters:
  • env (Mapping[str, str])

  • fetch_json (Callable[[str], object] | None)

Return type:

ClaCheckResult

ralph.contrib.cla.evaluate_current_environment()[source]

Evaluate the current CI environment as GitHub Actions or Woodpecker.

Return type:

ClaCheckResult

ralph.contrib.cla.evaluate_github_event(*, event_name, event)[source]

Evaluate a GitHub Actions webhook payload and skip non-PR events.

Parameters:
  • event_name (str | None)

  • event (Mapping[str, object])

Return type:

ClaCheckResult

ralph.contrib.cla.main()[source]

Run the CLA gate for the current environment and return a shell exit code.

Return type:

int

ralph.verify

Verification command wrapper with explicit AI-agent failure guidance.

This module is the single source of truth for make verify budget enforcement. It owns three ABSOLUTE and IMMUTABLE limits:

  • _TOTAL_TEST_BUDGET_SECONDS — the 60-second combined wall-clock budget for all test suites running sequentially under make verify. This is NOT a per-suite limit. run_verify() tracks cumulative wall-clock time with time.monotonic() across every step whose index is in _BUDGET_TRACKED_STEPS and rejects any tracked step once the running total exceeds 60 seconds. Adding new test suites, splitting existing suites, or renaming targets does not increase the budget — the tracker sums time across all budget-tracked steps.

  • _INTEGRATION_PER_TEST_TIMEOUT_SECONDS — the 1-second per-test cap for tests under tests/integration/. Enforced by SIGALRM in tests/conftest.py. Any integration test that exceeds this cap is a design defect: fix the production coupling, not the timeout.

  • _VERIFY_STEP_TIMEOUT_SECONDS — the per-step timeout for the non-test verification steps (ruff, mypy, the policy/lifecycle audits). Independent of the combined test budget.

Non-circumvention contract:

The 60-second combined budget cannot be raised or bypassed by any of the following. Each is detected by an import-time RuntimeError check (if/raise rather than assert so the checks survive python -O):

  • Splitting tests into more suites (N suites does not yield N x 60 s; the cumulative tracker sums across every tracked step).

  • Adding new test steps without adding their labels to _KNOWN_TEST_STEP_LABELS and their indices to _BUDGET_TRACKED_STEPS (the labels/steps sync invariant).

  • Emptying _KNOWN_TEST_STEP_LABELS to hide test steps from budget tracking, emptying _BUDGET_TRACKED_STEPS to disable tracking, or removing "make test" from _KNOWN_TEST_STEP_LABELS.

  • Raising _TOTAL_TEST_BUDGET_SECONDS or any of the per-step timeouts (an epsilon check pins the 60-second value to 60.0).

Tests marked @pytest.mark.subprocess_e2e are excluded from the main make test suite and do not count against the combined budget. The single allowed skip is tests/test_verify_invariants.py (Python 3.14 + loguru import-order incompatibility; the invariants remain enforced in the main make verify path).

If tests are too slow, fix the test design — replace real I/O with fakes (MemoryWorkspace, tmp_path, MockProcessExecutor), eliminate sleep() and real wall-clock waits, inject a clock abstraction, refactor production code behind an interface, or assert on observable behavior. Do not raise these constants.

ralph.verify.format_verify_failure_banner(*, failed_command)[source]

Return the formatted failure banner text for a failing verify command.

Parameters:

failed_command (str)

Return type:

str

ralph.verify.main(argv=None, *, runner=<function _default_runner>, cwd=None)[source]

Entry point for the ralph.verify command-line tool.

The handler is the __main__-style entry point for python -m ralph.verify. It refuses positional argv (the verify runner is parameter-only), resolves the working directory to <repo-root>/ralph-workflow by default, and delegates to run_verify.

Parameters:
  • argv (Sequence[str] | None) – Reserved for future flags. Passing any value raises SystemExit("ralph.verify does not accept positional arguments") so the public contract is fail-closed.

  • runner (VerifyRunner) – Subprocess runner override (same protocol as run_verify). Production code uses _default_runner; tests inject a fake runner.

  • cwd (Path | None) – Working-directory override. When None, defaults to Path(__file__).parent.parent (the ralph-workflow package root).

Returns:

run_verify’s return code (0 on success, non-zero on failure or budget exhaustion).

Return type:

int

ralph.verify.run_verify(*, cwd, runner=<function _default_runner>)[source]

Run all verification steps and return the first non-zero exit code, or 0.

Cumulative test budget enforcement:
  • Elapsed wall-clock time (time.monotonic()) is tracked across all steps whose indices are in _BUDGET_TRACKED_STEPS.

  • Before each tracked step, the remaining budget is computed. If it is <= 0 the step is skipped and TIMEOUT_EXIT_CODE is returned immediately.

  • The timeout passed to runner() for a tracked step is min(step_timeout, remaining_budget).

  • After a tracked step completes (including on timeout) the actual elapsed time is added to cumulative_test_elapsed.

  • Splitting tests across N suites does NOT give N x 60 s — the combined time of EVERY budget-tracked step is summed and enforced.

Parameters:
  • cwd (Path) – Working directory in which the verify step subprocesses are spawned. The Makefile passes the ralph-workflow package root; tests inject a temporary path.

  • runner (VerifyRunner) – Subprocess runner implementing the VerifyRunner protocol. Production code uses _default_runner (ralph.executor.process.run_process); tests inject a fake runner that records invocations and bypasses the real subprocess layer.

Returns:

0 when every step exits 0, or the first non-zero exit code returned by a failed step. When the cumulative budget is exhausted, TIMEOUT_EXIT_CODE is returned and a high-visibility failure banner is printed to stderr.

Return type:

int

Side effects:

Spawns subprocesses via runner; prints step stdout / stderr as it goes; emits a cumulative_test_elapsed summary line on success. No persistent state is written.

Raises:
  • No exceptions are raised by run_verify itself; subprocess

  • failures are surfaced through the return code and the failure

  • banner.

Parameters:
  • cwd (Path)

  • runner (VerifyRunner)

Return type:

int

ralph.timeout_defaults

Shared numeric defaults for agent timeout policy and child-liveness configuration.

These constants are the single source of truth for all timeout and child-liveness numeric defaults. They are imported by ralph.agents.idle_watchdog.TimeoutPolicy (dataclass field defaults), ralph.agents.invoke (child-liveness TTL module-level constants), and ralph.config.models.GeneralConfig (field defaults), as well as ralph.mcp.websearch.backends.brave / searxng (WEBSEARCH_BACKEND_TIMEOUT_SECONDS) and ralph.mcp.websearch.backends.ddgs / exa / tavily (WEBSEARCH_SDK_TIMEOUT_SECONDS, routed through ralph.mcp.websearch._bounded_sdk_call.with_timeout).

Changing a constant here automatically propagates to all consumers so they cannot drift independently.

ralph.timeout_defaults.AGENT_IDLE_ACTIVITY_EVIDENCE_TTL_SECONDS: float = 30.0

Default per-channel activity evidence TTL. Governs how long after a non-stdout event (MCP tool call, subagent progress, workspace file change) the corresponding channel still counts as live activity for the NO_OUTPUT_DEADLINE verdict. While ANY non-stdout channel is fresher than this TTL, the watchdog defers the NO_OUTPUT_DEADLINE fire and returns CONTINUE so a productive session that emits little stdout is not killed as idle. The default of 30s is well under the 300s idle-timeout default and the 600s no-progress ceiling, so a silent subagent (or silent MCP path) is detected at the regular idle deadline. Set to 0.0 to disable the activity-aware verdict and restore the legacy stdout-only NO_OUTPUT_DEADLINE behavior.

ralph.timeout_defaults.CHILD_EXIT_RECONCILE_SECONDS: float = 5.0

Reconciliation window after stdout EOF for late terminal acks.

ralph.timeout_defaults.CHILD_HEARTBEAT_TTL_SECONDS: float = 15.0

Maximum seconds since last child heartbeat before heartbeat is stale.

ralph.timeout_defaults.CHILD_PROGRESS_TTL_SECONDS: float = 45.0

Maximum seconds since last child progress signal before treated as not-progressing.

ralph.timeout_defaults.CHILD_STALE_LABEL_TTL_SECONDS: float = 10.0

Grace period during which a child label persists after evidence goes stale.

ralph.timeout_defaults.CPU_IDLE_SECONDS: float | None = 60.0

A known descendant PID with 0 user+system CPU time over this rolling window is reported by the read-loop corroborator as alive_by=CPU_IDLE_WHILE_ALIVE. The override short-circuits the OS-descendant-only ceiling and falls back to the no-progress ceiling (180.0 default). The 60s default tolerates up to 60s of sub-step quiescence (I/O wait, GC pause, network call) which is within the typical 95th-percentile sub-step latency. Set to None to disable the CPU probe and rely solely on the OS-descendant-only ceiling.

ralph.timeout_defaults.DEFAULT_AGENT_WORKSPACE_CHANGE_WEIGHTS: dict[str, float] = {'artifact': 0.0, 'cache': 0.0, 'log': 0.0, 'other': 0.0, 'source': 1.0}

Default per-kind workspace file-change weights. Each value is BINARY: weight==0.0 means the change is dropped (does not defer the NO_OUTPUT_DEADLINE verdict); weight==1.0 means the change counts as full activity. Intermediate weights are rejected by the validator today and reserved for a future fractional-TTL feature.

The default policy is conservative: only source-code changes count. Operators who relied on log-file activity to defer the verdict can opt in by overriding this dict (see GeneralConfig.agent_workspace_change_weights and the [general] agent_workspace_change_weights = {...} key in ralph-workflow.toml).

ralph.timeout_defaults.DESCENDANT_WAIT_POLL_SECONDS: float = 0.5

Default poll interval for descendant-wait / process-exit-wait loops.

ralph.timeout_defaults.DESCENDANT_WAIT_TIMEOUT_SECONDS: float = 30.0

Default ceiling for descendant-wait after parent exits.

ralph.timeout_defaults.DRAIN_WINDOW_SECONDS: float = 0.5

Default drain window duration before firing NO_OUTPUT_DEADLINE.

ralph.timeout_defaults.EXEC_DEFAULT_TIMEOUT_MS: int = 90000

Default per-call timeout for the exec MCP tool family (exec/unsafe_exec). Set above the 60s combined make verify budget so an agent running verification (or a slow git op) through exec does not time out on every call. This is the one source of truth: both the exec handler default and the advertised tool-schema default derive from it, so the hint shown to the agent cannot drift from the behavior. Per-call timeout_ms overrides it; the process tree is killed on expiry, so the server stays bounded regardless.

ralph.timeout_defaults.EXEC_MAX_TIMEOUT_MS: int = 300000

Hard upper bound on a per-call exec timeout_ms (and on the suggested retry timeout). An agent may raise timeout_ms to recover from a timeout, but never above this — the MCP client request timeout is derived to exceed it, so a legitimately long exec can never outrun the client (which would re-raise the -32001 Request timed out storm). 5 minutes is generous for any single command.

ralph.timeout_defaults.GIT_OUTPUT_LIMIT_BYTES: int = 10485760

Default cap (bytes) on git stdout/stderr captured by ralph.git.subprocess_runner.run_git when a caller does not opt into a custom value via GitRunOptions.output_limit_bytes. A massive git log / git diff / git status against a huge vendor submodule would otherwise buffer the entire payload in memory. 10 MiB matches the existing SPILL_OUTPUT_LIMIT_BYTES precedent at ralph/mcp/tools/_exec_output_spill.py:33 and is well above any realistic single-file diff. Callers that need to opt OUT can pass output_limit_bytes=None to preserve the unbounded legacy behavior.

ralph.timeout_defaults.GIT_SUBPROCESS_TIMEOUT_SECONDS: float = 120.0

Default bound for git subprocesses invoked via ralph.git.subprocess_runner.run_git when a caller does not specify an explicit timeout. Git is run in non-interactive (batch) mode so a network/credential prompt fails fast rather than hanging; this ceiling is the backstop for a slow-but-non-blocking op (e.g. status over large vendor submodules). The process tree is killed on expiry.

ralph.timeout_defaults.IDLE_POLL_INTERVAL_SECONDS: float = 0.05

Default poll interval for the read loop.

ralph.timeout_defaults.IDLE_TIMEOUT_SECONDS: float = 300.0

maximum seconds without agent output before firing.

Type:

Default idle timeout

ralph.timeout_defaults.KILL_ESCALATION_CEILING_MS: int = 5000

Hard upper bound on the SIGTERM-then-SIGKILL escalation grace. When a child must be terminated, the server first sends SIGTERM and waits at most this long before escalating to SIGKILL. Tuned for fast subprocess shutdown.

ralph.timeout_defaults.LOG_GROWTH_SECONDS: float | None = 30.0

The per-run .agent/raw/{safe_id}.log file is reported as alive_by=LOG_STALE_WHILE_ALIVE when its size has not grown for this many seconds. The override short-circuits the OS-descendant-only ceiling and falls back to the no-progress ceiling. The 30s default is aggressive but appropriate for detecting a wedged subprocess that is not writing any output. Set to None to disable the log-growth probe; the probe gracefully no-ops when the raw log file is absent.

ralph.timeout_defaults.MAX_SESSION_SECONDS: float | None = 3300.0

Default absolute session wall-clock ceiling (hard force-cut). None disables it. Set to 55 minutes so a runaway single invocation cannot run unbounded (the incident that motivated this ran ~5 hours). The soft wrap-up nag fires earlier (see SESSION_SOFT_WRAPUP_SECONDS), leaving a margin under the nominal 1h budget. Per-invocation and config-overridable; recovery continues after a cut.

ralph.timeout_defaults.MAX_WAITING_ON_CHILD_NO_PROGRESS_SECONDS: float | None = 600.0

shorter WAITING ceiling when child is alive but not making forward progress (heartbeat-only, stale-label, or OS-descendant-only). None disables the no-progress ceiling.

Type:

Default no-progress ceiling

ralph.timeout_defaults.MAX_WAITING_ON_CHILD_SECONDS: float = 1800.0

Default hard ceiling on cumulative WAITING_ON_CHILD time.

ralph.timeout_defaults.NO_PROGRESS_QUIET_HEARTBEAT_CEILING_SECONDS: float | None = 240.0

fires NO_PROGRESS_QUIET when a heartbeat-only subagent (AliveBy.FRESH_HEARTBEAT_ONLY – alive per the corroborator but no first-party progress) has been alive for this many seconds. Without this ceiling, a heartbeat-only subagent would bypass NO_PROGRESS_QUIET (which requires alive_by is None) AND STRICTLY_STUCK (which requires a stale alive_by) and only trip the cumulative 600s CHILDREN_PERSIST_TOO_LONG ceiling – too late for a heartbeat-only subagent that emits heartbeats but no real work. The default equals NO_PROGRESS_QUIET_SECONDS (240s) so the heartbeat-only branch fires AT the dumb-kill ceiling (the degenerate equal case permitted by the cross-field validator). Operators can RAISE the ceiling to give heartbeat-only subagents more headroom (e.g. long-running exploration, dispatching subagents) as long as the value stays <= NO_PROGRESS_QUIET_SECONDS when both are set. Must be > 0 when set. When None, the heartbeat-only ceiling is disabled and the watchdog falls back to the cumulative CHILDREN_PERSIST_TOO_LONG ceiling.

Type:

Default heartbeat-only ceiling

ralph.timeout_defaults.NO_PROGRESS_QUIET_MINIMUM_INVOCATION_SECONDS: float | None = 120.0

NO_PROGRESS_QUIET cannot fire within the first N seconds of an agent run. This prevents the watchdog from killing a recently-launched agent that is doing real thinking work (planning, exploration, dispatching subagents) but has not yet produced first-party activity evidence. The 120.0s default matches the OS_DESCENDANT_ONLY_CEILING default. The SESSION_CEILING_EXCEEDED reason is unaffected (operator-set hard cap). Set to None to disable the floor (not recommended).

Type:

Default dumb-kill floor

ralph.timeout_defaults.NO_PROGRESS_QUIET_SECONDS: float | None = 240.0

shorter WAITING ceiling when child is alive but not making forward progress (heartbeat-only, stale-label, or OS-descendant-only) and stdout has been idle. None disables it.

Type:

Default fast no-progress ceiling

ralph.timeout_defaults.NO_PROGRESS_QUIET_STRICTLY_STUCK_SECONDS: float | None = None

orthogonal to no_progress_quiet_seconds, this fires WatchdogFireReason.STRICTLY_STUCK when the corroborator reports a stuck-but-alive state (alive_by in {OS_DESCENDANT_ONLY_STALE_PROGRESS, CPU_IDLE_WHILE_ALIVE, LOG_STALE_WHILE_ALIVE}) AND no first-party channel is fresh for this many seconds. Default is None (disabled) so operators opt in by setting a concrete value; setting it shorter than the existing max_waiting_on_child_no_progress_seconds is the recommended pattern (e.g. 300 s vs the 600 s outer ceiling). Set to None to disable.

Type:

Default STRICTLY_STUCK ceiling

ralph.timeout_defaults.OS_DESCENDANT_ONLY_CEILING_SECONDS: float | None = 300.0

Short ceiling on cumulative WAITING_ON_CHILD time when the only evidence of a running child is its OS-process-tree existence (alive_by=OS_DESCENDANT_ONLY_STALE_PROGRESS with no scoped child evidence and no first-party evidence channels active). Fires CHILDREN_PERSIST_TOO_LONG in ~300s instead of waiting for the 600s no-progress ceiling. The 300s default tolerates the typical 95th-percentile sub-step latency (file reads, MCP startup, model load, multi-step tool calls) so the ceiling does not fire while the agent is genuinely making forward progress; a wedged-but-alive opencode subprocess with zero observable progress signals is still detected well before the 600s no-progress ceiling. The previous 120s default produced the ‘dumb-kill’ regression documented in wt-012 where the watchdog fired at cumulative=159s, idle_elapsed=120s while the agent was reading .agent/CURRENT_PROMPT.md (a legitimate sub-step well below 120s of work). The smart-verdict gate in the watchdog (StuckClassifier) further protects against premature fires by deferring the verdict while any first-party channel is fresh or while the agent is in a waiting state. Set to None to disable the override and fall back to the no-progress ceiling.

ralph.timeout_defaults.OS_DESCENDANT_ONLY_SUSPECT_SECONDS: float | None = 60.0

Earlier SUSPECTED_FROZEN threshold when alive_by is OS_DESCENDANT_ONLY_STALE_PROGRESS. The watchdog fires the suspect event at min(suspect_waiting_on_child_seconds, OS_DESCENDANT_ONLY_SUSPECT_SECONDS) so the operator sees escalation at ~60s instead of waiting for the standard 600s suspicion threshold. Set to None to disable and use the standard suspect threshold.

ralph.timeout_defaults.PARENT_EXIT_GRACE_SECONDS: float = 5.0

Default grace window after parent exits normally.

ralph.timeout_defaults.POST_TOOL_RESULT_PROGRESSION_SECONDS: float | None = 120.0

Default post-tool-result progression budget. When set, the idle watchdog fires STALLED_AFTER_TOOL_RESULT if no follow-up STREAM_DELTA/OUTPUT_LINE activity arrives within this many seconds of a tool result. The default of 120s is generous enough to cover the typical 60s 95th-percentile tool-result-to-output-line latency in production while still detecting the post-tool-result wedge in ~120s rather than waiting for the 300s idle-timeout default. Set to None to opt out and preserve the legacy 300s NO_OUTPUT_DEADLINE-only behavior.

ralph.timeout_defaults.PROCESS_EXIT_WAIT_SECONDS: float = 30.0

Default ceiling for waiting on subprocess exit after stdout closes.

ralph.timeout_defaults.PROCESS_MONITOR_ENABLED: bool = True

Default enabled state for the process monitor. When false, the watchdog does not scan the process tree and subagent liveness is inferred only from progress signals already received by the MCP server.

ralph.timeout_defaults.REPEATED_ERROR_CONSECUTIVE_THRESHOLD: int | None = 5

fire after this many consecutive identical error fingerprints with no intervening forward progress. None disables the rule.

Type:

Repeated-error circuit breaker

ralph.timeout_defaults.REPEATED_ERROR_WINDOW_COUNT: int | None = 8

fire after this many occurrences of one error fingerprint within REPEATED_ERROR_WINDOW_SECONDS (catches loops that interleave cosmetic output). None disables the rule.

Type:

Repeated-error circuit breaker

ralph.timeout_defaults.REPEATED_ERROR_WINDOW_SECONDS: float | None = 600.0

Rolling window for REPEATED_ERROR_WINDOW_COUNT. None disables the window rule.

ralph.timeout_defaults.SESSION_SOFT_WRAPUP_SECONDS: float | None = 3000.0

once a single invocation has run this long, MCP tool results carry a “finish up / declare_complete soon” banner so the agent winds down before the hard MAX_SESSION_SECONDS force-cut. None disables the nag.

Type:

Soft wrap-up threshold

ralph.timeout_defaults.SILENT_SUBAGENT_SECONDS: float | None = 180.0

Default staleness threshold for the SILENT_SUBAGENT diagnostic. The StuckClassifier returns StuckKind.SILENT_SUBAGENT when a subagent channel has evidence (count >= 1) but the most recent signal is older than this threshold. The default of 180.0s is deliberately looser than AGENT_IDLE_ACTIVITY_EVIDENCE_TTL_SECONDS (30.0s) so the diagnostic surfaces after the short-term deferral gate has expired but before the 600s no-progress ceiling.

ralph.timeout_defaults.SSE_DRAIN_CEILING_MS: int = 5000

Hard upper bound on the post-final-frame SSE drain grace (the time the server waits for the final frame’s write to complete before closing the connection). A slow client cannot outrun the dispatch cap by holding the receive buffer open past the dispatch — the server gives the final frame at most this many milliseconds to drain. Tuned for LAN clients.

ralph.timeout_defaults.STUCK_JOB_SUB_CEILING_SECONDS: float = 600.0

Default stuck-job sub-ceiling. The watchdog fires CHILDREN_PERSIST_TOO_LONG when the cumulative WAITING_ON_CHILD time exceeds the standard cumulative ceiling (max_waiting_on_child_seconds, default 1800s) AND corroboration shows the child is alive-but-not-progressing (alive_by in the stale set). The PROMPT trace showed cumulative waiting time climbing to 2365s without the gate firing because classify_stuck never returned STUCK while the corroborator reported an OS_DESCENDANT_ONLY_STALE_PROGRESS (or any stale alive_by). The stuck-job sub-ceiling is a SHORTER, ORTHOGONAL ceiling that fires well before the full cumulative ceiling when the child is alive but not producing fresh evidence. The 600s default is half the cumulative ceiling and matches the typical “I would have noticed if the agent was alive” run budget. Set to None to disable the sub-ceiling (legacy behavior).

ralph.timeout_defaults.SUBAGENT_OUTPUT_CAPTURE_ENABLED: bool = True

Default enabled state for subagent output capture. When false, the watchdog does not poll subagent log streams; subagent output is not treated as first-party evidence.

ralph.timeout_defaults.SUBAGENT_OUTPUT_POLL_INTERVAL_SECONDS: float = 1.0

Default poll interval for subagent output capture. The watchdog polls observable subagent log streams at this cadence and ingests only new lines since the last poll.

ralph.timeout_defaults.SUSPECT_WAITING_ON_CHILD_SECONDS: float | None = 600.0

cumulative WAITING time before SUSPECTED_FROZEN event. None disables suspicion.

Type:

Default suspicion threshold

ralph.timeout_defaults.WAITING_STATUS_INTERVAL_SECONDS: float = 30.0

Default cadence for WAITING_ON_CHILD periodic status events.

ralph.timeout_defaults.WATCHDOG_LOG_THROTTLE_SECONDS: float = 30.0

Default per-(fire_reason, deferred_kind) log throttle for _gate_fire. The PROMPT log showed ~10 DEBUG records/sec at _gate_fire:949 while a fire was deferred (SILENT_SUBAGENT or generic non-STUCK kind); that per-tick emission is log spam. The throttle keeps emissions to at most one per (fire_reason, deferred_kind) key per WATCHDOG_LOG_THROTTLE_SECONDS. The 30s default matches WAITING_STATUS_INTERVAL_SECONDS so the throttle and the structured status cadence are aligned.

ralph.timeout_defaults.WATCHDOG_SUBAGENT_PROGRESS_INTERVAL_SECONDS: float = 30.0

Default cadence for the SUBAGENT_PROGRESS waiting-status event. The watchdog emits a SUBAGENT_PROGRESS event at most once per this many seconds while WAITING_ON_CHILD deferral is active. The 30s default matches the existing PROGRESS cadence so the new event does not introduce additional churn.

ralph.timeout_defaults.WEBSEARCH_BACKEND_TIMEOUT_SECONDS: float = 10.0

Default per-call HTTP timeout for built-in websearch backends (Brave, SearXNG). Sourced by ralph.mcp.websearch.backends.brave and ralph.mcp.websearch.backends.searxng to replace the previously hard-coded _TIMEOUT_SECONDS = 10.0 literals. One source of truth so a 10s wedge cannot drift into a backend. Overridable via the [web_search] block of mcp.toml (WebSearchConfig.web_search_default_timeout_seconds). Must be > 0; the import-time invariant below rejects non-positive values.

ralph.timeout_defaults.WEBSEARCH_SDK_TIMEOUT_SECONDS: float = 30.0

Default per-call timeout for third-party-SDK-backed websearch backends (DDGS, Exa, Tavily). The SDKs wrap their own HTTP client; a hung SDK can otherwise block the dispatch worker for the full client timeout (330s), so the call is routed through ralph.mcp.websearch._bounded_sdk_call.with_timeout. Slightly more generous than the HTTP backend default because the SDKs add their own connection layer. Must be >= WEBSEARCH_BACKEND_TIMEOUT_SECONDS; the import-time invariant below rejects values that violate that ordering.

ralph.verify_timeout

Test timeout enforcement wrapper.

This module runs a pytest suite with per-test and full-suite timeout limits. Per-test limit is DEFAULT_TEST_TIMEOUT_SECONDS (1 s); suite limit is DEFAULT_SUITE_TIMEOUT_SECONDS (60 s). A test that exceeds these limits is a design defect — fix the production coupling, not the timeout.

ralph.__main__

Entry point for python -m ralph.

ralph.instance_status

Workflow instance lifecycle status values.

class ralph.instance_status.InstanceStatus(*values)[source]

Bases: StrEnum

Lifecycle status of a Ralph Workflow instance.

ralph.project_urls

Canonical public repository URLs for Ralph Workflow.

These constants are the maintained source of truth for the public repo surfaces referenced by package metadata, docs config, and regression tests.

ralph.pydantic_validation_errors

Shared Pydantic ValidationError formatter.

Converts pydantic.ValidationError exceptions into agent-friendly, field-level messages. The formatter is consumed by every typed artifact normalizer (plan, issues, fix_result, development_result, etc.) so the hints an agent sees are uniform across all artifact types.

The exported helpers are:

The formatter is plan-agnostic: it only depends on pydantic and the standard library, so it can be imported from any artifact normalizer without creating circular imports.

The decision to use this module (rather than re-implementing formatting in every normalizer) is driven by the recurring failure mode of cheap models: a raw str(exc) of a pydantic ValidationError shows errors like:

7 validation errors for Summary
intent
  String should have at most 200 characters [type=string_too_long, ...]

which does not name the actual length, the actual value, or the valid options for closed-enum fields. The formatter produced here adds those three pieces of information (location, rejected value, allowed shape) to every error line so an agent can act without guessing.

ralph.pydantic_validation_errors.format_validation_error_detail(detail)[source]

Format a single pydantic validation error detail as location: message.

Parameters:

detail (Mapping[str, object]) – One entry from ValidationError.errors().

Returns:

A single "  location: message" string.

Return type:

str

ralph.pydantic_validation_errors.format_validation_error_messages(exc)[source]

Format all pydantic ValidationError errors into human-readable strings.

Parameters:

exc (ValidationError) – The pydantic.ValidationError raised by model_validate.

Returns:

A list of "location: message" strings, one per error in the exception. The location is the dotted field path; the message includes the rejected value and the allowed shape.

Return type:

list[str]

ralph.pydantic_validation_errors.format_validation_location(raw_loc)[source]

Format a pydantic error location tuple to a dotted path string.

Parameters:

raw_loc (object | None) – The loc field from a pydantic error detail. May be a tuple, a list, a string, or None.

Returns:

A dotted path (e.g. "summary.intent") or a sentinel "<root>" when the location is empty/missing.

Return type:

str

ralph.pydantic_validation_errors.format_validation_message(raw_msg)[source]

Return the validation error message string.

Substitutes "<missing message>" if the message is None so callers always get a non-empty string back.

Parameters:

raw_msg (object | None) – The msg field from a pydantic error detail.

Returns:

The message string, "<missing message>", or the string form of the value.

Return type:

str

ralph.pydantic_validation_errors.suggest_canonical_field(unknown_key, candidate_fields, *, cutoff=0.6)[source]

Suggest the closest canonical field name for an unknown key.

Used by callers that detect an extra_forbidden error to point the agent at the field they probably meant. The suggestion is computed via difflib.get_close_matches() with the supplied cutoff (default 0.6, the difflib default).

Parameters:
  • unknown_key (str) – The rejected key (e.g. "design_constraints").

  • candidate_fields (Sequence[str]) – The list of valid field names on the model.

  • cutoff (float) – Match cutoff in the range [0, 1]. Default 0.6.

Returns:

The closest matching field name, or None if no candidate scores above the cutoff.

Return type:

str | None

ralph.logging_models

Data models for Ralph Workflow logging configuration.

class ralph.logging_models.LoggingConfig(verbosity=1, log_directory=None, run_id=None, structured=False, rotation='10 MB')[source]

Bases: object

Logging configuration used to create handlers and run directories.

Parameters:
  • verbosity (int)

  • log_directory (Path | None)

  • run_id (str | None)

  • structured (bool)

  • rotation (str | int | None)

class ralph.logging_models.LoggingPaths(run_directory, text_log_path, structured_log_path)[source]

Bases: object

Resolved file paths for a configured logging session.

Parameters:
  • run_directory (Path | None)

  • text_log_path (Path | None)

  • structured_log_path (Path | None)

class ralph.logging_models.LoggingSession(config, paths, logger, ralph)[source]

Bases: object

Configured logger bundle for a single Ralph Workflow run.

Parameters:

ralph.logging_worker_sink

Per-worker log sink helpers for Ralph Workflow.

class ralph.logging_worker_sink.WorkerSinkHandle(sink_id, log_path)[source]

Bases: object

Handle returned by bind_worker_sink to identify a per-worker loguru sink.

Parameters:
  • sink_id (int)

  • log_path (Path)

ralph.logging_worker_sink.bind_worker_sink(unit_id, log_dir, run_id='default')[source]

Add a per-worker loguru sink that filters to unit_id and returns its handle.

Parameters:
  • unit_id (str)

  • log_dir (Path)

  • run_id (str)

Return type:

WorkerSinkHandle

ralph.logging_worker_sink.remove_worker_sink(handle)[source]

Remove the per-worker loguru sink identified by handle.

Parameters:

handle (WorkerSinkHandle)

Return type:

None

ralph.session_runtime

Public managed agent-session runtime for Ralph-hosted mini workflows.

Exposes a small, reusable runtime seam for tools that need Ralph to supervise a constrained agent session without entering the full policy-driven pipeline. Callers own the higher-level host loop; Ralph owns the MCP bridge, agent invocation wiring, resumable session environment, and optional system-prompt materialization.

Contract:

  • The runtime is created via ManagedAgentSessionRuntime.open (a classmethod) which constructs an AgentSession, starts a session bridge, optionally materializes a system prompt, and returns the runtime. Constructor failures shut down any bridge that was already started so no half-initialized MCP listener is left behind.

  • All collaborators (workspace factory, MCP server starter, agent invoker, system prompt materializer, MCP bridge shutdown) are injectable through ManagedAgentSessionDeps so the runtime is black-box testable without real subprocesses or filesystem operations.

  • ManagedAgentSessionRequest is a frozen dataclass that carries the caller-supplied inputs (session id prefix, drain, optional capabilities, optional pre-resolved SessionMcpPlan, optional system-prompt name). It is the canonical request shape a host loop passes in.

  • The runtime manages the lifecycle of its MCP bridge via close() (also wired through the context-manager protocol). Callers are responsible for invoking close() (or using with) when the host loop is done.

class ralph.session_runtime.ManagedAgentSessionDeps(build_session_mcp_plan=<function _build_session_mcp_plan>, start_mcp_server=<function _start_mcp_server>, invoke_agent=<function _invoke_agent>, materialize_system_prompt=<function _materialize_system_prompt>, workspace_factory=<function _workspace_factory>, shutdown_bridge=<function _shutdown_bridge>)[source]

Bases: object

Injectable dependency bundle for the managed session runtime.

ManagedAgentSessionDeps is the single seam that lets tests replace the default production collaborators used by ralph.session_runtime.ManagedAgentSessionRuntime with fakes or stubs. The production default wraps the canonical implementations in ralph.mcp.session_plan, ralph.mcp.server.lifecycle, ralph.agents.invoke, ralph.prompts.system_prompt, and ralph.workspace.fs.

All fields are public callables; tests may overwrite any subset while leaving the rest at the production defaults. Because the dataclass is frozen=True the bundle itself cannot be mutated after construction, so a runtime that captures a deps value is guaranteed to use the same callables throughout its lifetime.

Parameters:
  • build_session_mcp_plan (BuildSessionMcpPlanFn)

  • start_mcp_server (StartMcpServerFn)

  • invoke_agent (InvokeAgentFn)

  • materialize_system_prompt (MaterializeSystemPromptFn)

  • workspace_factory (WorkspaceFactoryFn)

  • shutdown_bridge (ShutdownBridgeFn)

build_session_mcp_plan

Resolve the per-session MCP plan for a given (transport, drain, workspace_path, agents_policy, model_opts, model_flag) tuple. Side effect: none. The production implementation reads policy and writes nothing.

Type:

BuildSessionMcpPlanFn

start_mcp_server

Launch the MCP server subprocess for a given session and workspace, returning the bridge handle the runtime uses to talk to that server. Side effects: spawns a subprocess and registers the bridge handle for shutdown; the bridge is also exposed for shutdown_bridge.

Type:

StartMcpServerFn

invoke_agent

Run an agent CLI against a prompt file with the given options. Returns an iterable of stdout chunks. Side effects: spawns a subprocess, injects the resolved environment into the agent, and yields streamed output.

Type:

InvokeAgentFn

materialize_system_prompt

Resolve the prompt inputs for a named system prompt and, when the named prompt is supplied, write the materialized system-prompt file the agent will consume. Side effects: reads prompt inputs from workspace_root (and the engine-owned current-prompt mirror under the .agent directory), and may write the materialized system-prompt file under workspace_root at .agent/tmp/<name>_system_prompt.md (or under the worker namespace when one is provided), plus the synchronized current-prompt mirror and any prompt-history snapshot. Returns the filesystem path of the written system-prompt file as a string.

Type:

MaterializeSystemPromptFn

workspace_factory

Build a ralph.workspace.protocol.Workspace rooted at the given path. Side effects: instantiates the workspace implementation; the production default returns ralph.workspace.fs.FsWorkspace.

Type:

WorkspaceFactoryFn

shutdown_bridge

Terminate a running bridge handle, releasing any subprocess it owns. Side effects: stops the MCP server subprocess and frees the bridge handle. The runtime calls this from its cleanup path after Exception is observed, so tests can verify cleanup behavior.

Type:

ShutdownBridgeFn

Example

Replace just one field for a focused unit test:

deps = ManagedAgentSessionDeps(
    start_mcp_server=lambda session, workspace, extras: fake_bridge,
)
runtime = ManagedAgentSessionRuntime.open(
    config=cfg,
    request=req,
    deps=deps,
)
class ralph.session_runtime.ManagedAgentSessionRequest(session_id_prefix, drain, capabilities=None, session_mcp_plan=None, server_env=None, system_prompt_name=None, default_current_prompt=None)[source]

Bases: object

Caller-supplied inputs that shape one Ralph-managed standalone agent session.

This frozen dataclass is the canonical contract that host loops (e.g. the Ralph pipeline, ad-hoc prompt runners, or external tooling) pass to ralph.session_runtime.ManagedAgentSessionRuntime.open() to describe one isolated agent session. It is intentionally a value object: every field is immutable and there is no behavior, so two requests that compare equal produce identical session lifecycles.

Parameters:
  • session_id_prefix (str)

  • drain (str)

  • capabilities (frozenset[str] | None)

  • session_mcp_plan (SessionMcpPlan | None)

  • server_env (dict[str, str] | None)

  • system_prompt_name (str | None)

  • default_current_prompt (str | None)

session_id_prefix

Short, human-meaningful prefix prepended to the generated session id (e.g. "plan" or "verify"). The runtime appends -<uuid4_hex[:8]> to produce the unique session id. The prefix surfaces in log lines and checkpoint files, so prefer lowercase, stable identifiers.

Type:

str

drain

Phase-style label that names the kind of work the session is performing ("planning", "execution", "review", "verification", …). drain flows into ralph.mcp.protocol.session.AgentSession.drain, governs which capabilities are exposed through the MCP bridge, and is used by ralph.mcp.protocol.startup.access_mode_for_drain() to choose read-only vs read/write tool access.

Type:

str

capabilities

Optional explicit set of MCP-bridge capability names to expose. When None the runtime resolves capabilities from the configured AgentsPolicy via ralph.mcp.session_plan.build_session_mcp_plan(). Pass an explicit value when the caller needs to lock capabilities for testing or for hardened isolation modes.

Type:

frozenset[str] | None

session_mcp_plan

Optional pre-resolved SessionMcpPlan that fully describes the session’s MCP capabilities, model identity, and server-side environment. When supplied, capabilities and server_env are ignored and this plan is used verbatim. Useful for hosts that resolve plans ahead of time (e.g. for caching or cross-session reuse).

Type:

SessionMcpPlan | None

server_env

Optional environment variables to merge into the MCP server subprocess environment (in addition to Ralph’s defaults). Reserved names (MCP_ENDPOINT, MCP_RUN_ID, AGENT_LABEL_SCOPE) are managed by the runtime and cannot be overridden here.

Type:

dict[str, str] | None

system_prompt_name

Optional name of a system-prompt template to materialize for the session. When None the agent is invoked without an explicit system prompt. The materializer writes the resolved file under the workspace and returns its path.

Type:

str | None

default_current_prompt

Optional fallback path used when the chosen system-prompt template references a current placeholder that has no other source. Has no effect when system_prompt_name is None.

Type:

str | None

Invariants:
  • The dataclass is frozen; mutating an instance raises dataclasses.FrozenInstanceError.

  • Every field is optional except session_id_prefix and drain; the runtime treats the others as overrides or precomputed hints.

  • Fields are not used directly by the runtime after ManagedAgentSessionRuntime.open() returns; the resolved AgentSession carries the immutable view of the session.

Example

>>> request = ManagedAgentSessionRequest(
...     session_id_prefix="plan",
...     drain="planning",
...     capabilities=frozenset({"read_repo", "list_artifacts"}),
...     system_prompt_name="planning/default",
...     default_current_prompt="your-prompt-file.md",
... )
class ralph.session_runtime.ManagedAgentSessionRuntime(*, config, workspace_root, agent_config, request, bridge, agent_session, system_prompt_file, deps)[source]

Bases: object

Host-owned context for running prompt-like mini workflows through Ralph.

A ManagedAgentSessionRuntime is the reusable runtime seam Ralph exposes for tools that need to drive a single, isolated agent session without entering the full policy-driven pipeline. Callers own the higher-level host loop (e.g. plan/verify helpers, ad-hoc prompt runners, or external tooling); the runtime owns the MCP bridge, the per-session environment, agent invocation wiring, retry handling, and optional system-prompt materialization.

Instances are constructed via open() (a classmethod) so the bridge and session id lifecycle are managed in one place. The runtime is a context manager: with runtime: ... shuts down the bridge on exit (also available directly via close()).

Attributes (set in open(), treated as read-only afterwards):
config: The fully-merged UnifiedConfig driving the

general settings of every invoke_prompt_file() turn.

workspace_root: The repository-relative workspace the agent

subprocess treats as its working directory.

agent_config: The selected AgentConfig identifying the

agent CLI to invoke.

request: The ManagedAgentSessionRequest that named the

session; retained verbatim for diagnostics and checkpoint rehydration.

bridge: The SessionBridgeLike started by open().

Owns the MCP server endpoint the agent reaches as MCP_ENDPOINT.

agent_session: The AgentSession carrying the unique

session id, run id, declared capabilities, and model identity.

system_prompt_file: Resolved path to a system-prompt file when

the request named one; None when the agent runs without an explicit system prompt.

deps: The ManagedAgentSessionDeps dependency bundle in

use (production defaults, or the test stub passed to open()).

Invariants:
  • The class is constructed only via open(); the regular __init__ is reserved for the runtime’s internal use to keep construction injectable.

  • The MCP bridge owned by the runtime is alive between construction and close(); callers must invoke close() (or use the context-manager protocol) regardless of how their host loop exits.

Parameters:
close()[source]

Shut down the MCP bridge owned by this session.

Return type:

None

invoke_prompt_file(prompt_file, *, session_id=None, session_id_sink=None, required_artifact=None, waiting_listener=None, permission_prompt_listener=None, extra_env=None)[source]

Drive one host-owned agent turn and stream the agent’s output.

invoke_prompt_file resolves the per-turn InvokeOptions for the configured agent, injects the MCP endpoint / run-id / agent scope environment variables into the agent subprocess, and yields the agent’s output line-by-line (logging through ralph.agents.invoke.invoke_agent()). The MCP bridge started by open() is reachable as the MCP_ENDPOINT env value, so tools that call back into Ralph resolve back to the same bridge for the lifetime of the turn.

Parameters:
  • prompt_file (str | Path) – Path to the prompt file the agent will be asked to read. Resolved relative to the runtime’s workspace_root by the underlying invocation mechanism; absolute paths are honored as-is.

  • session_id (str | None)

  • session_id_sink (Callable[[str], None] | None)

  • required_artifact (RequiredArtifact | None)

  • waiting_listener (WaitingStatusListener | None)

  • permission_prompt_listener (Callable[[str], None] | None)

  • extra_env (dict[str, str] | None)

Keyword Arguments:
  • session_id – Optional explicit session id forwarded to the agent. When None the agent runtime generates one; the actual id is reported through session_id_sink.

  • session_id_sink – Optional callback invoked as soon as the agent makes its session id observable (i.e. after the first handshake). Receives the resolved session id so the host can store it for resumption, logging, or checkpoint writes.

  • required_artifact – Optional ralph.phases.required_artifacts.RequiredArtifact declaration used by the agent runtime to gate completion; the turn fails fast if no matching artifact is produced. Most host loops leave this None.

  • waiting_listener – Optional callback invoked when the agent reports it is waiting on a tool call. Used by progress UIs to surface the wait state without depending on stdout parsing.

  • permission_prompt_listener – Optional callback invoked when the agent prompts for permission (e.g. before an action that requires operator approval). Implementations should return the agent’s answer or raise to abort the turn.

  • extra_env – Optional additional environment variables merged into the agent subprocess environment, excluding the three reserved names MCP_ENDPOINT, MCP_RUN_ID, and AGENT_LABEL_SCOPE (these are owned by the runtime and always set by open).

Returns:

A lazy iterator over the agent’s streamed output lines. The iterator is wrapped by ralph.agents.invoke._direct_mcp_recovery.iter_with_direct_mcp_recovery(), which transparently retries on direct-MCP failures up to config.general.max_same_agent_retries attempts. Retry events are emitted through loguru.logger.warning().

Return type:

Iterable[str]

Raises:

Exception – Propagated from the underlying agent invocation or from the recovery iterator once retries are exhausted. The MCP bridge started by open() is not shut down on failure; callers are expected to invoke close() (or use the runtime as a context manager) regardless of outcome.

Side Effects:
  • Launches the configured agent CLI as a subprocess.

  • Injects MCP_ENDPOINT, MCP_RUN_ID, and AGENT_LABEL_SCOPE into the subprocess environment so the agent can reach the MCP bridge owned by this runtime.

  • May invoke session_id_sink and the listener callbacks as the turn progresses.

  • On retryable failure, may re-launch the agent subprocess and may reset the bridge’s tool registry (when one is exposed via bridge.reset_tool_registry).

Example

>>> with ManagedAgentSessionRuntime.open(
...     config=config,
...     workspace_root=repo_root,
...     agent_config=agent_config,
...     request=ManagedAgentSessionRequest(
...         session_id_prefix="plan", drain="planning"
...     ),
... ) as runtime:
...     for line in runtime.invoke_prompt_file("your-prompt-file.md"):
...         print(line)
classmethod open(*, config, workspace_root, agent_config, request, deps=None, agents_policy=None)[source]

Construct a managed Ralph session ready for invoke_prompt_file().

open allocates the session id, starts the MCP bridge that the agent will talk to, and (optionally) materializes a system-prompt file. It is the only sanctioned way to build a ManagedAgentSessionRuntime; the regular __init__ is reserved for the runtime’s internal use so it can be reasoned about as a pure dependency-injected bundle.

Keyword Arguments:
  • config – The fully-merged ralph.config.models.UnifiedConfig that drives general settings (verbosity, retry limits, JSON parser). Reused for every invoke_prompt_file() call made through the runtime.

  • workspace_root – Filesystem location the agent session will treat as its working directory. Forwarded to the workspace factory in deps to produce a Workspace that the MCP bridge will hand to tools.

  • agent_config – Selected ralph.config.models.AgentConfig that names which agent CLI to invoke (e.g. Claude, Codex, OpenCode), which transport to use, and which optional model flag to pass through.

  • request – Caller-supplied ManagedAgentSessionRequest that names the session id prefix, drain, capabilities, system prompt, and any pre-resolved session plan.

  • deps – Optional ManagedAgentSessionDeps bundle overriding one or more collaborator boundaries (workspace factory, MCP server starter, agent invoker, system-prompt materializer, bridge shutdown). Pass a stubbed bundle in tests to avoid real subprocesses and filesystem access. When None the production defaults from ManagedAgentSessionDeps are used.

  • agents_policy – Optional ralph.policy.models.AgentsPolicy used to resolve MCP capabilities and access modes when request.capabilities and request.session_mcp_plan are both None. Falls back to the policy embedded in config when omitted.

Returns:

A fully wired ManagedAgentSessionRuntime whose MCP bridge is already listening on a private endpoint. The caller owns the returned runtime and must invoke close() (or use it as a context manager) so the bridge shuts down on exit.

Raises:

Exception – Any failure during bridge start or system-prompt materialization is re-raised after any partially-started bridge has been shut down via deps.shutdown_bridge, so the caller never inherits a half-initialized MCP listener.

Parameters:
Return type:

ManagedAgentSessionRuntime

Side Effects:
  • Starts an MCP server subprocess (or in-memory bridge, if the deps.start_mcp_server override returns one) bound to a private endpoint.

  • Captures a fresh run_id (UUID4) that is exposed through MCP_RUN_ID_ENV and AGENT_LABEL_SCOPE_ENV to the agent subprocess.

  • May write a system-prompt file under workspace_root via deps.materialize_system_prompt.

ralph.rich_protocols

Protocol shims for lazily imported Rich classes.

class ralph.rich_protocols.RichGroupProto(*args, **kwargs)[source]

Bases: Protocol

Protocol for rich.Group class.

class ralph.rich_protocols.RichPanelProto(*args, **kwargs)[source]

Bases: Protocol

Protocol for rich.Panel class.

class ralph.rich_protocols.RichTextProto(*args, **kwargs)[source]

Bases: Protocol

Protocol for rich.Text class.

ralph.pydantic_compat

First-party Pydantic typing compatibility helpers.

Ralph intentionally keeps strict mypy enabled without enabling the pydantic.mypy plugin. Some upstream Pydantic surfaces still expose Any in ways that trip disallow_any_explicit / disallow_any_expr when a module subclasses pydantic.BaseModel directly.

RalphBaseModel keeps runtime behavior identical to pydantic.BaseModel while providing an Any-free type-checking facade for the small subset of the BaseModel API that Ralph actually relies on.

ralph.test_suites

Run the maintained pytest verification suite under the current interpreter.

Note

The 60-second ABSOLUTE and IMMUTABLE combined test budget is enforced UPSTREAM by ralph/verify.py:_TOTAL_TEST_BUDGET_SECONDS via cumulative time.monotonic() tracking, not by this module. This module provides per-suite timeout wrapping only. Splitting tests into more suites or adding new test targets does NOT increase the combined budget.

CLI

The CLI is a Typer application built on CLI Reference. It exposes every subcommand a user runs day to day: init, diagnose, run, commit, cleanup, explain, prompt-helper, smoke, and star — plus policy helpers (check-policy, contribute). Each command lives in its own submodule so the CLI surface stays discoverable and individual commands can be tested in isolation.

ralph.cli

Public CLI package.

This package exposes the Typer application used by the ralph console script. For most CLI-oriented pydoc usage, start with ralph.cli.main.

ralph.cli.main

Ralph Workflow CLI entry point - typer application with rich-click help styling.

This module provides the main CLI application for Ralph Workflow, using typer for argument parsing and rich-click for enhanced help output.

ralph.cli.main.RunPipelineOpts

alias of _RunPipelineOpts

ralph.cli.main.bootstrap_global_configs(*, display_context)

Create user-global config files from bundled templates if they don’t exist.

Parameters:

display_context (DisplayContext)

Return type:

None

ralph.cli.main.build_cli_overrides(input)

Build CLI overrides dictionary from CLIOverrideInput.

Parameters:

input (CLIOverrideInput)

Return type:

dict[str, object]

ralph.cli.main.configure_logging(verbosity)

Configure logging based on verbosity level.

Parameters:

verbosity (Verbosity)

Return type:

None

ralph.cli.main.handle_check_config(config, cli_overrides, check_config, *, console=None, display_context=None)[source]

Public wrapper for the --check-config short-circuit; accepts legacy console= and new display_context= kwargs.

Parameters:
  • config (str | None)

  • cli_overrides (dict[str, object])

  • check_config (bool)

  • console (Console | None)

  • display_context (DisplayContext | None)

Return type:

int | None

ralph.cli.main.handle_check_mcp(check_mcp, *, console=None, display_context=None)[source]

Public wrapper that accepts a legacy console kwarg for backward compat.

Parameters:
  • check_mcp (bool)

  • console (Console | None)

  • display_context (DisplayContext | None)

Return type:

int | None

ralph.cli.main.handle_commit_plumbing(options, *, display_context)

Handle commit plumbing commands; returns exit code or None to continue.

Parameters:
Return type:

int | None

ralph.cli.main.handle_list_agents(config, cli_overrides, list_agents, *, display_context)

Handle –list-agents flag; returns exit code or None to continue.

Parameters:
  • config (str | None)

  • cli_overrides (dict[str, object])

  • list_agents (bool)

  • display_context (DisplayContext)

Return type:

int | None

ralph.cli.main.handle_list_providers(list_providers, *, display_context)

Handle –list-providers flag; returns exit code or None to continue.

Parameters:
Return type:

int | None

ralph.cli.main.inject_quick_prompt(args)

Inject –prompt before bare positional text when -Q/–quick is present.

Parameters:

args (list[str])

Return type:

list[str]

ralph.cli.main.invoke_pipeline(config, opts, *, display_context)

Run the main pipeline.

Parameters:
  • config (str | None)

  • opts (_RunPipelineOpts)

  • display_context (DisplayContext)

Return type:

int

ralph.cli.main.main(ctx, prompt=None, config=None, developer_iters=None, quick=False, thorough=False, counter=None, developer_agent=None, developer_model=None, verbosity=Verbosity.VERBOSE, quiet=False, debug=False, resume=False, no_resume=False, unsafe_mode=None, inspect_checkpoint=False, dry_run=False, list_agents=False, list_providers=False, diagnose=False, check_config=False, check_mcp=False, init=None, regenerate_config=False, force_init_skills=False, generate_local_config=False, generate_commit_msg=False, generate_commit=False, show_commit_msg=False, git_user_name=None, git_user_email=None, version=False, explain_policy=False, explain_policy_dir=None, parallel_worker_manifest=None, check_policy=False, prompt_helper=False)[source]

Run the Ralph Workflow multi-agent pipeline or execute a sub-operation.

The handler is the ralph console script entry point declared in pyproject.toml (ralph = ralph.cli.main:app). It is the single Typer callback that fans out to ~12 early-exit branches (--version, --init, --diagnose, --check-mcp, --check-config, --init-local-config, --inspect-checkpoint, --list-agents, --list-providers, --generate-commit*, --explain-policy, --check-policy, --prompt-helper) and then to the main pipeline invocation.

Primary flags:

  • --init [PATH] — scaffold .agent/ + PROMPT.md in the target directory.

  • --diagnose / -d — pre-flight check of agent CLIs, MCP servers, and capability bundles; never starts a real run.

  • --generate-commit / --generate-commit-msg — build the commit artifact from the latest development_result; --generate-commit applies the commit. Always dogfood this for the AGENTS.md commit rule rather than hand-rolling git commit.

  • --quick / -Q and --thorough / -T — depth presets that map to developer-iteration counts (1 and 10 respectively).

  • --developer-iters / -D, --reviewer-reviews / -R — explicit iteration caps (overridden by the depth presets).

  • --resume / -r and --no-resume — checkpoint handling.

  • --counter NAME=VALUE (repeatable) — override a policy-declared budget counter; the name must be declared in pipeline.toml or the run is rejected.

Pipeline-invocation side effect: when none of the early-exit branches fire, the handler builds a CLIOverrides bundle, calls bootstrap_global_configs + configure_logging, resolves the effective developer-iteration count, and dispatches to run_pipeline. The run writes .agent/checkpoint.json and emits a finish-receipt on success.

Parameters:
  • ctx (Context) – Typer context (carries the global CLI state; not directly consumed by this handler).

  • prompt (Annotated[str | None, <typer.models.OptionInfo object at 0x10eff0cd0>]) – --prompt / -P inline prompt text (must be used with --quick).

  • config (Annotated[str | None, <typer.models.OptionInfo object at 0x10eff0e10>]) – --config / -c path to an explicit configuration file.

  • developer_iters (Annotated[int | None, <typer.models.OptionInfo object at 0x10eff0f50>]) – --developer-iters / -D developer-agent iteration cap.

  • quick (Annotated[bool, <typer.models.OptionInfo object at 0x10eff1090>]) – --quick / -Q single-iteration preset.

  • thorough (Annotated[bool, <typer.models.OptionInfo object at 0x10eff11d0>]) – --thorough / -T ten-iteration preset.

  • counter (Annotated[list[str] | None, <typer.models.OptionInfo object at 0x10eff1310>]) – --counter repeatable NAME=VALUE overrides.

  • developer_agent (Annotated[str | None, <typer.models.OptionInfo object at 0x10eff1450>]) – --developer-agent / -a agent name.

  • developer_model (Annotated[str | None, <typer.models.OptionInfo object at 0x10eff1590>]) – --developer-model model flag.

  • verbosity (Annotated[Verbosity, <typer.models.OptionInfo object at 0x10eff16d0>]) – --verbosity / -v output verbosity (quiet / normal / verbose / full / debug).

  • quiet (Annotated[bool, <typer.models.OptionInfo object at 0x10eff1810>]) – --quiet / -q suppress non-error output.

  • debug (Annotated[bool, <typer.models.OptionInfo object at 0x10eff1950>]) – --debug enable debug output.

  • resume (Annotated[bool, <typer.models.OptionInfo object at 0x10eff1a90>]) – --resume / -r resume from checkpoint.

  • no_resume (Annotated[bool, <typer.models.OptionInfo object at 0x10eff1bd0>]) – --no-resume ignore any existing checkpoint.

  • unsafe_mode (Annotated[bool | None, <typer.models.OptionInfo object at 0x10eff1d10>]) – --unsafe-mode merge Ralph Workflow’s MCP config into the agent’s existing config instead of overwriting.

  • inspect_checkpoint (Annotated[bool, <typer.models.OptionInfo object at 0x10eff1e50>]) – --inspect-checkpoint print checkpoint JSON and exit.

  • dry_run (Annotated[bool, <typer.models.OptionInfo object at 0x10eff1f90>]) – --dry-run run without invoking agents.

  • list_agents (Annotated[bool, <typer.models.OptionInfo object at 0x10eff20d0>]) – --list-agents print configured agents and exit.

  • list_providers (Annotated[bool, <typer.models.OptionInfo object at 0x10eff2210>]) – --list-providers print providers and exit.

  • diagnose (Annotated[bool, <typer.models.OptionInfo object at 0x10eff2350>]) – --diagnose / -d pre-flight check.

  • check_config (Annotated[bool, <typer.models.OptionInfo object at 0x10eff2490>]) – --check-config / -C validate config.

  • check_mcp (Annotated[bool, <typer.models.OptionInfo object at 0x10eff25d0>]) – --check-mcp validate custom MCP servers.

  • init (Annotated[str | None, <typer.models.OptionInfo object at 0x10eff2710>]) – --init [PATH] scaffold .agent/ + PROMPT.md.

  • regenerate_config (Annotated[bool, <typer.models.OptionInfo object at 0x10eff2850>]) – --regenerate-config rewrite config from bundled defaults (backs up to <name>.bak).

  • force_init_skills (Annotated[bool, <typer.models.OptionInfo object at 0x10eff2990>]) – --force-init-skills re-run baseline skill install.

  • generate_local_config (Annotated[bool, <typer.models.OptionInfo object at 0x10eff2ad0>]) – --init-local-config / --generate-local-config write a project-local ralph-workflow.toml.

  • generate_commit_msg (Annotated[bool, <typer.models.OptionInfo object at 0x10eff2c10>]) – --generate-commit-msg build commit message artifact.

  • generate_commit (Annotated[bool, <typer.models.OptionInfo object at 0x10eff2d50>]) – --generate-commit build and apply commit.

  • show_commit_msg (Annotated[bool, <typer.models.OptionInfo object at 0x10eff2e90>]) – --show-commit-mg show the commit message.

  • git_user_name (Annotated[str | None, <typer.models.OptionInfo object at 0x10eff2fd0>]) – --git-user-name git user name for commits.

  • git_user_email (Annotated[str | None, <typer.models.OptionInfo object at 0x10eff3110>]) – --git-user-email git user email for commits.

  • version (Annotated[bool, <typer.models.OptionInfo object at 0x10eff3250>]) – --version / -V print version and exit.

  • explain_policy (Annotated[bool, <typer.models.OptionInfo object at 0x10eff3390>]) – --explain-policy print human-readable policy and exit.

  • explain_policy_dir (Annotated[str | None, <typer.models.OptionInfo object at 0x10eff34d0>]) – --explain-policy-dir (hidden) policy directory to explain.

  • parallel_worker_manifest (Annotated[str | None, <typer.models.OptionInfo object at 0x10eff3610>]) – --parallel-worker-manifest (hidden) internal worker bootstrap manifest path.

  • check_policy (Annotated[bool, <typer.models.OptionInfo object at 0x10eff3750>]) – --check-policy validate active policy and exit.

  • prompt_helper (Annotated[bool, <typer.models.OptionInfo object at 0x10eff3890>]) – --prompt-helper launch the interactive prompt-refinement helper.

Returns:

None. The handler exits via typer.Exit or via the underlying run_pipeline return code; it never returns normally on success.

Return type:

None

Side effects:

Bootstrap global config / MCP config / policy configs; write .agent/checkpoint.json; spawn the configured agent CLI; write artifact files via the canonical MCP path; emit declare_complete on success. Bounded subprocesses are routed through ralph.process.manager.

ralph.cli.main.parse_counter_overrides(raw_entries)

Parse NAME=VALUE counter override strings; raises UsageError on malformed input.

Parameters:

raw_entries (list[str])

Return type:

dict[str, int]

ralph.cli.main.prepare_init_args(args)

Normalize –init and -Q positional text before Click parsing.

Parameters:

args (Sequence[str] | None)

Return type:

list[str] | None

ralph.cli.main.record_cli_command(ctx)

Forward the invoked subcommand (or the literal pipeline) as a privacy-safe tag.

This is the single CLI chokepoint for the command telemetry tag. The value is drawn from a closed vocabulary: either ctx.invoked_subcommand (a registered Typer command name — a developer-defined identifier, not user-supplied free text) or the literal "pipeline" when no subcommand is invoked (the default run path). The if ctx.invoked_subcommand: return guard later short-circuits subcommand dispatch, so this single call covers both paths. Opt-out-aware and fail-soft.

Parameters:

ctx (Context)

Return type:

None

ralph.cli.main.resolve_effective_verbosity(verbosity, *, quiet, debug)[source]

Compute the verbosity to use for the run.

--quiet and --debug take precedence. Absent those, the default is Verbosity.VERBOSE so Ralph Workflow is visibly active by default. The legacy --verbosity normal input is mapped to VERBOSE to preserve wrapper scripts that passed normal explicitly.

Parameters:
  • verbosity (Verbosity)

  • quiet (bool)

  • debug (bool)

Return type:

Verbosity

ralph.cli.main.smoke_interactive_agy(agent=<typer.models.OptionInfo object>)[source]

Run the manual PTY smoke test for Google Anti Gravity.

Parameters:

agent (str)

Return type:

None

ralph.cli.main.smoke_interactive_claude()[source]

Run the manual PTY/TUI smoke test for interactive Claude using claude/haiku.

Return type:

None

ralph.cli.main.smoke_interactive_cursor(agent=<typer.models.OptionInfo object>)[source]

Run the manual end-to-end smoke test for the Cursor Agent CLI.

Parameters:

agent (str)

Return type:

None

ralph.cli.main.smoke_interactive_nanocoder(agent=<typer.models.OptionInfo object>)[source]

Run the manual PTY smoke test for Nanocoder interactive mode.

Parameters:

agent (str)

Return type:

None

ralph.cli.main.version_callback(version, ctx=None)[source]

Print version information.

Parameters:
Return type:

None

ralph.cli.commands

Ralph CLI commands package.

Re-exports the top-level entry points for each CLI sub-command so callers can import them from ralph.cli.commands without knowing the submodule layout. The main CLI wiring lives in ralph.cli.main; each sub-command is implemented in its own submodule under this package.

Public exports:

  • commit_plumbing - drives ralph --generate-commit

  • diagnose_command - drives ralph diagnose

  • init_command - drives ralph init

  • run_pipeline - drives ralph run (the primary workflow entry point)

  • smoke_interactive_agy_command - drives the manual AGY end-to-end smoke test

  • smoke_interactive_claude_command - drives the manual PTY parity smoke test

ralph.cli.commands.cleanup

Cleanup command — remove stale parallel worker namespaces after a hard-kill.

ralph.cli.commands.cleanup.cleanup(dry_run=False, force=False)[source]

Remove stale per-worker namespaces under .agent/workers/ after a hard-kill.

In same-workspace parallel mode, each worker writes to .agent/workers/<unit_id>/. These directories are normally cleaned up automatically, but a hard-kill may leave them behind.

Parameters:
  • dry_run (Annotated[bool, <typer.models.OptionInfo object at 0x10f4fcb90>])

  • force (Annotated[bool, <typer.models.OptionInfo object at 0x10f4fccd0>])

Return type:

None

ralph.cli.commands.commit

Commit plumbing commands for Ralph CLI.

This module is the thin CLI surface for commit and --generate-commit. All chain-iteration, retry-classification, and session-resume logic lives in ralph.pipeline.plumbing.commit_plumbing and is invoked via run_commit_plumbing(). The CLI surface only owns:

  • option parsing (CommitPlumbingOptions),

  • output formatting (Rich text rendering, exit codes),

  • shell entry point (commit_plumbing).

exception ralph.cli.commands.commit.AgentInvocationError(agent_name, returncode, stderr='', parsed_output=None)[source]

Bases: Exception

Raised when agent invocation fails.

Parameters:
  • agent_name (str)

  • returncode (int)

  • stderr (str)

  • parsed_output (list[str] | None)

Return type:

None

agent_name

Name of the agent that failed.

returncode

Process exit code.

stderr

Standard error output.

class ralph.cli.commands.commit.CommitAgentResult(message='', skipped=False, failure_details=<factory>, session_id=None, last_error=None, output=<factory>)[source]

Bases: object

Aggregated result returned after all commit-message agent attempts complete.

The CLI surface consumes this dataclass for output formatting and exit code derivation. The new output field exposes the captured agent lines so the CLI can render transcript content when verbose is enabled.

Parameters:
  • message (str)

  • skipped (bool)

  • failure_details (list[str])

  • session_id (str | None)

  • last_error (Exception | None)

  • output (list[str])

class ralph.cli.commands.commit.CommitAttemptContext(repo_root, verbose, extra_env, general_config=None, bridge=None)[source]

Bases: object

Runtime context threaded into each commit agent invocation attempt.

Parameters:
class ralph.cli.commands.commit.CommitPlumbingOptions(generate_commit_msg=False, generate_commit=False, show_commit_msg=False, config_path=None, cli_overrides=None)[source]

Bases: object

Options for commit plumbing operations.

Parameters:
  • generate_commit_msg (bool)

  • generate_commit (bool)

  • show_commit_msg (bool)

  • config_path (Path | None)

  • cli_overrides (dict[str, object] | None)

ralph.cli.commands.commit.collect_commit_agent_output(lines, *, parser_type, agent_name, verbose, display_context, session_id_sink=None)[source]

Consume agent output lines, returning (parsed_lines, raw_lines, resume_session_id).

Parameters:
  • lines (Iterable[object])

  • parser_type (str)

  • agent_name (str)

  • verbose (bool)

  • display_context (DisplayContext)

  • session_id_sink (Callable[[str], None] | None)

Return type:

tuple[list[str], list[str], str | None]

ralph.cli.commands.commit.commit_plumbing(*, options=None, display_context=None, pro_hooks=None, model_identity=None)[source]

Handle commit plumbing operations.

Parameters:
Return type:

None

ralph.cli.commands.commit.invoke_agent(config, prompt_file, *, options=None, _clock=None)[source]

Invoke agent, yield parsed output lines as they arrive.

Parameters:
  • config (AgentConfig) – Agent configuration specifying command and flags.

  • prompt_file (str) – Path to PROMPT.md file to pass to agent.

  • options (InvokeOptions | None) – Optional invocation options.

  • _clock (Clock | None) – Injectable Clock for testing; production callers omit this.

Yields:

Raw agent output lines (before parsing).

Raises:

AgentInvocationError – If agent exits with non-zero code.

Return type:

Iterator[str]

ralph.cli.commands.commit.invoke_commit_agent_attempt(agent, *, prompt_file, attempt_context, session_id=None, display_context, session_id_sink=None, materializer=None)[source]

Run one commit-agent invocation attempt and return its result.

Deprecated since version Kept: as a thin late-binding wrapper for tests that patch ralph.cli.commands.commit.{materialize_system_prompt,invoke_agent, delete_commit_message_artifacts,read_commit_message_artifact}. New code should call execute_agent_effect() through _run_commit_agent_attempt_with_recovery().

Parameters:
Return type:

CommitAgentAttempt

ralph.cli.commands.commit.materialize_system_prompt(*, workspace_root, name, default_current_prompt=None, worker_namespace=None)[source]

Write a system prompt file for the named agent and return its path.

Parameters:
  • workspace_root (Path)

  • name (str)

  • default_current_prompt (str | None)

  • worker_namespace (Path | None)

Return type:

str

ralph.cli.commands.commit.submit_artifact_tool_name_for_transport(transport)[source]

Return the submit-artifact tool name for the given transport.

Parameters:

transport (AgentTransport | None)

Return type:

str

ralph.cli.commands.contribute

star:

ralph contribute — open the Codeberg repo to star and fork Ralph Workflow.

This is a lightweight community-support command. It opens the canonical Codeberg repository in the default browser so you can star the project, watch for releases, or fork it — all from a single CLI invocation.

Alias: ralph star is a shortcut that does the same thing.

No git repository, configuration, or authentication is required.

ralph.cli.commands.contribute.contribute(source='codeberg')[source]

Open the Ralph Workflow repo in your browser so you can star it.

Opens the Codeberg project page (default) or GitHub mirror so you can star, watch, or fork — then come back and keep working.

Examples

ralph contribute # Open Codeberg ralph contribute –source github # Open GitHub mirror

Parameters:

source (Annotated[str, <typer.models.OptionInfo object at 0x10f4fd090>])

Return type:

None

ralph.cli.commands.diagnose

Diagnose command for Ralph Workflow CLI.

This module implements diagnostic commands to check the environment and configuration.

ralph.cli.commands.diagnose.build_next_steps(*, validation_ok, agent_missing, prompt_exists, prompt_has_sentinel)[source]

Build the list of remediation steps based on current diagnostic state.

Parameters:
  • validation_ok (bool) – Whether pre-flight validation passed.

  • agent_missing (bool) – Whether any configured agent is missing from PATH.

  • prompt_exists (bool) – Whether PROMPT.md exists in the workspace.

  • prompt_has_sentinel (bool) – Whether PROMPT.md still contains the starter sentinel.

Returns:

List of human-readable remediation lines.

Return type:

list[str]

ralph.cli.commands.diagnose.check_agents(cli_overrides, *, display_context=None)[source]

Check agent availability and return True if any agent is missing from PATH.

Public wrapper: resolves the active display from display_context and delegates to _check_agents_impl().

Parameters:
  • cli_overrides (dict[str, object] | None)

  • display_context (DisplayContext | None)

Return type:

bool

ralph.cli.commands.diagnose.check_configuration(config_path, cli_overrides, *, display_context=None)[source]

Public check helper that resolves an active display from a context.

Parameters:
  • config_path (Path | None)

  • cli_overrides (dict[str, object] | None)

  • display_context (DisplayContext | None)

Return type:

bool

ralph.cli.commands.diagnose.check_git_repo(*, display_context=None)[source]

Public check helper that resolves an active display from a context.

Parameters:

display_context (DisplayContext | None)

Return type:

bool

ralph.cli.commands.diagnose.check_mcp_servers(workspace_scope, *, display_context=None)[source]

Public check helper that resolves an active display from a context.

Parameters:
Return type:

bool

ralph.cli.commands.diagnose.check_workspace_files(*, display_context=None)[source]

Public check helper that resolves an active display from a context.

Parameters:

display_context (DisplayContext | None)

Return type:

bool

ralph.cli.commands.diagnose.diagnose_command(config_path=None, cli_overrides=None, *, display_context=None)[source]

Run diagnostics on the Ralph Workflow environment.

Parameters:
  • config_path (Path | None) – Optional path to config file.

  • cli_overrides (dict[str, object] | None) – CLI flag overrides.

  • display_context (DisplayContext | None) – Display context for consistent rendering. If None, a default context is created using make_display_context().

Returns:

Exit code (0 for success, 1 for errors, 2 for validation failures).

Return type:

int

ralph.cli.commands.init

Init command for Ralph Workflow CLI.

This module implements the initialization command that sets up Ralph Workflow in a repository.

AUTO-SKILL-INSTALL CONTRACT

ralph –init ALWAYS invokes the baseline skill installer on every run, including the re-run path where every bootstrap result is skipped. This guarantees that the bundled skill bundle is materialized at ~/.claude/skills/ and symlinked into every registered sibling agent root, regardless of whether other config files needed creation. The installer failures (e.g. sibling-conflict-*) are surfaced to the user on both the first-run and re-run paths.

ralph.cli.commands.init.init_command(template=None, config_path=None, *, display_context=None)[source]

Initialize Ralph Workflow in the current working directory.

Parameters:
  • template (str | None) – Optional template name (e.g. ‘default’). All labels currently produce the same starter content.

  • config_path (Path | None) – Optional path for config file.

  • display_context (DisplayContext | None) – Display context for consistent rendering. If None, a default context is created using make_display_context().

Return type:

None

ralph.cli.commands.run

Run pipeline command for Ralph Workflow CLI.

This module implements the main pipeline execution command.

class ralph.cli.commands.run.RunPipelineRequest(config_path=None, cli_overrides=None, dry_run=False, resume=False, verbosity=None, counter_overrides=None, inline_prompt=None, parallel_worker_manifest=None, pro_hooks=None, model_identity=None)[source]

Bases: NamedTuple

Parameters for a pipeline run request.

Parameters:
  • config_path (Path | None)

  • cli_overrides (ConfigOverrides | None)

  • dry_run (bool)

  • resume (bool)

  • verbosity (Verbosity | None)

  • counter_overrides (dict[str, int] | None)

  • inline_prompt (str | None)

  • parallel_worker_manifest (Path | None)

  • pro_hooks (ProPipelineHooks | None)

  • model_identity (MultimodalModelIdentity | None)

cli_overrides: ConfigOverrides | None

Alias for field number 1

config_path: Path | None

Alias for field number 0

counter_overrides: dict[str, int] | None

Alias for field number 5

dry_run: bool

Alias for field number 2

inline_prompt: str | None

Alias for field number 6

model_identity: MultimodalModelIdentity | None

Alias for field number 9

parallel_worker_manifest: Path | None

Alias for field number 7

pro_hooks: ProPipelineHooks | None

Alias for field number 8

resume: bool

Alias for field number 3

verbosity: Verbosity | None

Alias for field number 4

ralph.cli.commands.run.print_dry_run(initial_state, config, policy_bundle, *, display_context)[source]

Print dry-run information.

Parameters:
Return type:

None

ralph.cli.commands.run.run_pipeline(request=None, *, display_context=None, pro_hooks=None, model_identity=None, **kwargs)[source]

Run the Ralph Workflow pipeline (backward compatibility wrapper).

Parameters:
  • request (RunPipelineRequest | None) – RunPipelineRequest namedtuple with all pipeline options.

  • display_context (DisplayContext | None) – Display context for consistent rendering. If None, a default context is created using make_display_context().

  • **kwargs (Unpack[_LegacyRunPipelineKwargs]) – Additional keyword arguments for backward compatibility. Accepted keys: config_path, cli_overrides, dry_run, resume, verbosity, counter_overrides, inline_prompt.

  • pro_hooks (ProPipelineHooks | None)

  • model_identity (MultimodalModelIdentity | None)

  • **kwargs

Returns:

Exit code (0 for success, non-zero for failure).

Return type:

int

ralph.cli.commands.run.validate_loaded_policy_bundle(policy_bundle)

Validate cross-drain policy contracts for an already loaded bundle.

Parameters:

policy_bundle (PolicyBundle)

Return type:

None

ralph.cli.commands.smoke

Manual smoke tests for expensive agent-runtime checks.

These smoke tests are intentionally excluded from the verify pipeline because they consume live agent tokens. They exist to validate the real invoke_agent pipeline against a live agent runtime, especially interactive-Claude parity, when changing the runtime. A smoke fix is only valid when it improves the shared runtime path, not when it special-cases this command alone.

The orchestration core lives in ralph.pipeline.plumbing.smoke_plumbing; this module is the thin CLI surface (option setup, report rendering, exit codes).

class ralph.cli.commands.smoke.SmokeRunParams(agent_name, config, unified_config, workspace_root, prompt_file, output_file, options, display_context, bridge=None, pipeline_deps=None)[source]

Bases: object

Grouped parameters for a smoke run.

Parameters:
class ralph.cli.commands.smoke.SmokeRunResult(agent_name, transport, output_file, file_created, session_id, explicit_completion_seen, raw_line_count, parsed_event_count, tool_activity_seen, artifact_submitted, meaningful_output_lines, errors)[source]

Bases: object

Observed results from the interactive Claude smoke run.

Parameters:
  • agent_name (str)

  • transport (str)

  • output_file (Path)

  • file_created (bool)

  • session_id (str | None)

  • explicit_completion_seen (bool)

  • raw_line_count (int)

  • parsed_event_count (int)

  • tool_activity_seen (bool)

  • artifact_submitted (bool)

  • meaningful_output_lines (list[str])

  • errors (list[str])

ralph.cli.commands.smoke.build_smoke_prompt(output_relpath, *, submit_artifact_tool_name, transport=None)

Return the prompt used for the parity smoke test.

Parameters:
  • output_relpath (str)

  • submit_artifact_tool_name (str)

  • transport (AgentTransport | None)

Return type:

str

ralph.cli.commands.smoke.render_smoke_report(results, *, agent_name='claude')

Render a human-readable parity report.

Parameters:
Return type:

str

ralph.cli.commands.smoke.smoke_interactive_agy_command(agent_name='agy/Gemini 3.5 Flash (Medium)', *, display_context=None, pro_hooks=None, model_identity=None)[source]

Run the manual AGY end-to-end smoke harness via the PTY contract.

This drives the live agy binary (or the RALPH_AGY_BINARY override when set). The default alias is agy/Gemini 3.5 Flash (Medium) because that model ships with a generous per-account quota in the agy models list and reliably produces output in the harness environment. The 7 live regression tests in tests/test_agy_live_regression.py all use the same default alias so the public CLI command and the live regression suite share one repo-consistent, directly verified smoke path. Use --agent to pin a different agy/<model> alias from agy models.

Parameters:
Return type:

int

ralph.cli.commands.smoke.smoke_interactive_claude_command(*, display_context=None, pro_hooks=None, model_identity=None)[source]

Run a token-consuming manual parity smoke test for interactive Claude.

Parameters:
Return type:

int

ralph.cli.commands.smoke.smoke_interactive_cursor_command(agent_name='cursor/auto', *, display_context=None, pro_hooks=None, model_identity=None)[source]

Run the manual end-to-end smoke harness via the Cursor headless contract.

This drives the live agent binary (or the RALPH_CURSOR_BINARY override when set). The default alias is cursor/auto because that exercises the dynamic-alias path and the documented Auto default-routing model. The command is OUTSIDE make verify per the cursor non-goal of no live-token-consuming smoke tests in verify (the harness only runs when an operator explicitly invokes it).

Parameters:
Return type:

int

ralph.cli.commands.smoke.smoke_interactive_nanocoder_command(agent_name='nanocoder', *, display_context=None, pro_hooks=None, model_identity=None)[source]

Run the manual PTY smoke test for a Nanocoder interactive alias.

Parameters:
Return type:

int

ralph.cli.commands.star

Star command — open Codeberg and print star CTA for Ralph Workflow.

ralph.cli.commands.star.star(no_browser=False)[source]

Open the Codeberg repo in your browser — star it if Ralph Workflow helps you.

Stars are the most visible signal that Ralph Workflow is useful. If you use it, let us know with a quick star.

Parameters:

no_browser (Annotated[bool, <typer.models.OptionInfo object at 0x10f4fdd10>])

Return type:

None

ralph.cli.commands.check_policy

check_policy command — validate the active policy and report results.

ralph.cli.commands.check_policy.check_policy_command(policy_dir=None, counter_overrides=None)[source]

Validate the active policy and print a pass/fail summary to stdout.

Resolves the policy directory the same way as –explain-policy, loads and validates the policy, then prints a summary of what was found or the validation error. When counter_overrides are supplied, validates that every key is declared in pipeline.budget_counters.

Parameters:
  • policy_dir (Path | None) – Directory containing policy TOML files. Defaults to the workspace-local .agent directory (if it contains TOML files), then the bundled defaults.

  • counter_overrides (dict[str, int] | None) – Budget counter overrides from –counter flags. Any key not declared in pipeline.budget_counters raises a PolicyValidationError.

Returns:

0 on success, 1 on general error, 2 on policy validation error.

Return type:

Exit code

ralph.cli.commands.explain

explain command — render the active policy as a human-readable explanation.

ralph.cli.commands.explain.explain_command(policy_dir=None)[source]

Print a human-readable explanation of the active policy to stdout.

The output starts with the policy source directory, then a WORKFLOW DIAGRAM section showing a deterministic pure-ASCII diagram of the pipeline, followed by a RALPH WORKFLOW section with the structured policy breakdown.

Parameters:

policy_dir (Path | None) – Directory containing policy TOML files. Defaults to the workspace-local .agent directory (if it contains TOML files), then the bundled defaults.

Returns:

0 on success, 1 on general error, 2 on policy validation error.

Return type:

Exit code

ralph.cli.commands.prompt_helper

Interactive prompt helper — PM-style agent for refining PROMPT.md.

ralph.cli.commands.prompt_helper.run_prompt_helper(config, workspace_root)[source]

Run the prompt helper.

This is a host-owned state machine in which the agent never converses with the user; the only conversation is between the user and this orchestrator.

  1. Seed the first turn from an existing PROMPT.md, or ask the user once for an idea when none exists.

  2. Invoke the agent non-interactively for one turn to produce an artifact.

  3. If no artifact is produced, report the failure and leave PROMPT.md alone.

  4. Otherwise drive the refine/accept loop, writing PROMPT.md only on Accept.

Parameters:
Return type:

None

ralph.cli.commands.prompt_helper_prompt

Prompt helper system prompt builder.

ralph.cli.commands.prompt_helper_prompt.build_prompt_helper_prompt(*, submit_artifact_tool_name, existing_prompt_context=None, has_draft=False, current_draft=None, user_idea=None)[source]

Build the system prompt for the non-interactive prompt-helper agent.

The returned prompt instructs the agent to turn the supplied idea (and/or an existing PROMPT.md or current draft) into a structured product specification and submit it immediately, in one shot, without conversing with the user. All conversation with the user is owned by the host orchestrator, not the agent.

Parameters

submit_artifact_tool_namestr

The MCP tool name to use when submitting the product_spec artifact, e.g. “mcp__ralph__ralph_submit_artifact”.

existing_prompt_contextstr | None

Existing PROMPT.md content injected by the host when refining an existing prompt before the first helper turn.

has_draftbool

When True, include the current draft specification in the prompt so the agent can refine from it.

current_draftdict[str, object] | None

The current product_spec artifact content to include when has_draft is True.

user_ideastr | None

The free-text idea the host collected from the user, embedded as a request block on the first turn when no PROMPT.md exists.

Parameters:
  • submit_artifact_tool_name (str)

  • existing_prompt_context (str | None)

  • has_draft (bool)

  • current_draft (dict[str, object] | None)

  • user_idea (str | None)

Return type:

str

Config

This group holds the Pydantic models, loaders, and bootstrap helpers that back Ralph Workflow’s layered config (CLI flag → project-local → user-global → bundled defaults). The merged UnifiedConfig is what the runtime sees on every run. See Configuration Reference for the operator-facing reference (which now folds in the policy-driven overhaul migration background).

ralph.config

Configuration models and enums for Ralph.

Use this package when you need to inspect or construct validated configuration objects, or when you need the public enums used by CLI/config plumbing.

Typical entry points:

  • ralph.config.loader.load_config to build the merged runtime config

  • AgentConfig and UnifiedConfig for validated configuration objects

  • Verbosity and related enums for CLI/config values

  • ensure_global_config and friends to bootstrap user configs on first run

ralph.config.bootstrap

Bootstrap helpers for creating user-global and project-local config files.

Auto-creates the user-global Ralph config set on first run, including ~/.config/ralph-workflow.toml, ~/.config/ralph-workflow-mcp.toml, ~/.config/ralph-workflow-pipeline.toml, and ~/.config/ralph-workflow-artifacts.toml from bundled templates. Also supports regenerating configs with .bak backups via –regenerate-config.

Bootstrap creates the standard first-run config set:
  • User-global: ~/.config/ralph-workflow.toml, ~/.config/ralph-workflow-mcp.toml,

    ~/.config/ralph-workflow-pipeline.toml, ~/.config/ralph-workflow-artifacts.toml

  • Project-local: .agent/ralph-workflow.toml, .agent/mcp.toml,

    .agent/pipeline.toml, .agent/artifacts.toml

  • Advanced optional: .agent/agents.toml (only regenerated when already present)

  • Batteries-included .gitignore: Ralph-local, Python, Node, Rust, Go, Ruby, PHP, Java/Kotlin, .NET, Dart/Flutter, Elixir, Scala, Terraform, IDE, and OS metadata patterns (see _DEFAULT_GITIGNORE_PATTERNS)

class ralph.config.bootstrap.BootstrapResult(path, action, backup=None)[source]

Bases: object

Result of a bootstrap operation.

Parameters:
  • path (Path)

  • action (Literal['created', 'skipped', 'regenerated'])

  • backup (Path | None)

path

Target file path that was acted on.

Type:

pathlib.Path

action

What happened: created, skipped, or regenerated.

Type:

Literal[‘created’, ‘skipped’, ‘regenerated’]

backup

Path to the .bak file if the original was backed up, else None.

Type:

pathlib.Path | None

ralph.config.bootstrap.auto_seed_default_git_exclude(repo_root)[source]

Auto-seed .git/info/exclude on a normal ralph run.

Mirrors auto_seed_default_gitignore but for the per-user .git/info/exclude file. Reads the existing file (if any), computes the patterns from _DEFAULT_GIT_EXCLUDE_PATTERNS that are not already present, appends the missing patterns to the resolved exclude file, and returns the list of patterns that were actually appended.

The git directory is resolved via Repo(repo_root).git_dir so the helper works for normal repositories, git worktrees, and separate-git-dir layouts. In a worktree the top-level .git is a gitfile pointing at the real gitdir; blindly using repo_root / '.git' / 'info' / 'exclude' would call mkdir on a file and fail with NotADirectoryError. Falls back to the repo-root layout only when repo_root is not a git repository.

Idempotent: a second call with the same repo_root returns [] when every default pattern is already present. Does NOT clobber user-added entries.

Tolerates a missing git dir: when repo_root is not a git repository (e.g. first-run bootstrap before git init), the helper writes .git/info/exclude directly into the filesystem, creating the parent dirs as needed.

Parameters:

repo_root (Path) – Path to the repository (or project) root.

Returns:

List of patterns that were appended on this call. Empty when the existing file already covered every default pattern.

Return type:

list[str]

ralph.config.bootstrap.auto_seed_default_gitignore(repo_root)[source]

Auto-seed the batteries-included .gitignore on a normal ralph run.

Reads the existing .gitignore (if any), computes the patterns from _DEFAULT_GITIGNORE_PATTERNS that are not already present, appends them via append_to_gitignore (which filters duplicates by line), and returns the list of patterns that were actually appended.

Idempotent: a second call with the same repo_root returns [] when every default pattern is already present (the underlying append_to_gitignore short-circuits when nothing is missing). Does NOT clobber user-added lines — user-customized patterns are preserved.

Handles the no-git case: the helper just touches .gitignore in repo_root; it does not require a .git directory to exist.

Parameters:

repo_root (Path) – Path to the repository (or project) root.

Returns:

List of patterns that were appended on this call. Empty when the existing .gitignore already covered every default pattern.

Return type:

list[str]

ralph.config.bootstrap.ensure_global_config(global_dir=None, *, force=False)[source]

Ensure ~/.config/ralph-workflow.toml exists, creating it from the bundled template.

Parameters:
  • global_dir (Path | None) – Override the global config directory. Defaults to resolve_global_config_dir().

  • force (bool) – When True, overwrite an existing file (backs it up to <name>.bak first).

Returns:

BootstrapResult describing the action taken.

Return type:

BootstrapResult

ralph.config.bootstrap.ensure_global_mcp_config(global_dir=None, *, force=False)[source]

Ensure ~/.config/ralph-workflow-mcp.toml exists, creating it from the bundled template.

Parameters:
  • global_dir (Path | None) – Override the global config directory. Defaults to resolve_global_config_dir().

  • force (bool) – When True, overwrite an existing file (backs it up to <name>.bak first).

Returns:

BootstrapResult describing the action taken.

Return type:

BootstrapResult

ralph.config.bootstrap.ensure_global_policy_configs(global_dir=None, *, force=False)[source]

Ensure the user-global policy defaults exist.

Parameters:
  • global_dir (Path | None) – Override the global config directory. Defaults to resolve_global_config_dir().

  • force (bool) – When True, overwrite existing files (backs them up first).

Returns:

List of BootstrapResult, one per global policy file.

Return type:

list[BootstrapResult]

ralph.config.bootstrap.ensure_local_configs(agent_dir, *, force=False)[source]

Ensure the full project-local config set exists.

Parameters:
  • agent_dir (Path) – The .agent directory to write configs into.

  • force (bool) – When True, overwrite existing files (backs them up first).

Returns:

List of BootstrapResult, one per config file.

Return type:

list[BootstrapResult]

ralph.config.bootstrap.ensure_local_main_config(agent_dir, *, force=False)[source]

Ensure the project-local main override exists.

Parameters:
  • agent_dir (Path) – The .agent directory to write configs into.

  • force (bool) – When True, overwrite an existing file (backing it up first).

Returns:

BootstrapResult describing the action taken for .agent/ralph-workflow.toml.

Return type:

BootstrapResult

ralph.config.bootstrap.ensure_local_support_configs(agent_dir, *, force=False)[source]

Ensure the standard project-local policy and MCP files exist.

This scaffolds the .agent/ files Ralph needs for project-local runtime behavior without creating the optional project-local main override.

Parameters:
  • agent_dir (Path) – The .agent directory to write configs into.

  • force (bool) – When True, overwrite existing files (backs them up first).

Returns:

List of BootstrapResult, one per support file.

Return type:

list[BootstrapResult]

ralph.config.bootstrap.regenerate_all(*, global_dir=None, agent_dir=None)[source]

Regenerate all configs from bundled defaults, backing up existing files.

Parameters:
  • global_dir (Path | None) – Override the global config directory. Defaults to resolve_global_config_dir().

  • agent_dir (Path | None) – The .agent directory to regenerate local configs in. Skipped when None.

Returns:

Flat list of BootstrapResult for every file touched.

Return type:

list[BootstrapResult]

ralph.config.bootstrap.resolve_global_config_dir(env=None)[source]

Resolve the user-global config directory.

Honors XDG_CONFIG_HOME when set; falls back to ~/.config.

Parameters:

env (Mapping[str, str] | None) – Environment mapping to read from. Uses os.environ when None.

Returns:

Path to the config directory.

Return type:

Path

ralph.config.enums

Backward-compatible enum re-exports for ralph configuration.

NOTE: PipelinePhase is now a type alias to str (not a StrEnum). Phase names are loaded from pipeline.toml at startup. Well-known phases are exposed as module-level constants for use in built-in phase handlers.

class ralph.config.enums.AgentTransport(*values)[source]

Bases: StrEnum

Invocation/MCP transport type for an agent runtime.

CLAUDE

Claude Code compatible invocation/MCP transport.

CLAUDE_INTERACTIVE

Unattended interactive Claude Code transport.

CODEX

Codex CLI compatible invocation/MCP transport.

OPENCODE

OpenCode compatible invocation/MCP transport.

NANOCODER

Nanocoder CLI compatible invocation/MCP transport.

GENERIC

No special transport support.

AGY

Google Anti Gravity compatible invocation/MCP transport.

PI

Pi coding agent (pi.dev) compatible invocation/MCP transport. The headless BuiltinAgentSpec uses pi –mode json <prompt> per https://pi.dev/docs/latest/usage. Ralph wires MCP through a generated Pi extension and treats clean exits without required completion evidence as resumable against the captured Pi session.

CURSOR

Cursor Agent CLI compatible invocation/MCP transport. The headless BuiltinAgentSpec uses agent --print --output-format stream-json and Ralph wires MCP through .cursor/mcp.json / ~/.cursor/mcp.json (the documented Cursor config surface).

class ralph.config.enums.JsonParserType(*values)[source]

Bases: StrEnum

JSON parser type for agent output parsing.

CLAUDE

Parser for Claude’s NDJSON streaming format

CODEX

Parser for Codex’s NDJSON streaming format

GEMINI

Parser for Gemini’s NDJSON streaming format

OPENCODE

Parser for OpenCode’s NDJSON streaming format

GENERIC

Generic NDJSON parser for other agents

PI

Parser for Pi AgentSessionEvent NDJSON streaming format (https://pi.dev/docs/latest/json).

class ralph.config.enums.PauseOnExit(*values)[source]

Bases: StrEnum

Pause behavior before process exit.

AUTO

Pause only on standalone failure

ALWAYS

Always pause before exit

NEVER

Never pause before exit

ralph.config.enums.PipelinePhase

Type alias for a pipeline phase identifier.

A pipeline phase name is a plain str loaded at runtime from the pipeline.toml configuration file. The runtime no longer hard-codes the set of legal phase names; phase order, dependencies, and gate-conditions are all declared in configuration and validated by ralph.config.pipeline.load_pipeline_definition(). Built-in phase handlers expose their canonical names as module-level constants (e.g. DEFAULT_*_PHASE in ralph.phases) so call sites that need a known-good value can reference the constant directly instead of repeating the string literal.

Use this alias for type annotations on helpers, gate conditions, and phase-handler signatures that take or return a phase name. Treat the value as opaque; do not pattern-match on string contents because the set of legal phases is configurable and can change between workspaces.

Examples

>>> def handle(phase: PipelinePhase) -> None: ...

See also

ralph.phases ships the canonical phase-name constants. ralph.config.pipeline.load_pipeline_definition() reads pipeline.toml and returns the validated phase set for the active workspace.

class ralph.config.enums.RecoveryStrategy(*values)[source]

Bases: StrEnum

Recovery strategy when pipeline encounters failures.

FAIL

Fail immediately on errors

AUTO

Attempt automatic recovery

FORCE

Force through errors

class ralph.config.enums.Verbosity(*values)[source]

Bases: StrEnum

Verbosity level for Ralph output.

QUIET

Minimal output (errors only)

NORMAL

Default verbosity level

VERBOSE

More detailed output

FULL

Full output with all details

DEBUG

Debug-level output for troubleshooting

ralph.config.loader

Layered TOML configuration loader.

Merge order (lowest to highest priority):
  1. Embedded defaults (Pydantic field defaults)

  2. ~/.config/ralph-workflow.toml (or $XDG_CONFIG_HOME/ralph-workflow.toml)

  3. .agent/ralph-workflow.toml (project-local)

  4. CLI flag overrides

This module handles the four-layer configuration merge: - Embedded defaults provide the baseline for every field. - Global config supplies user-wide preferences. - Project-local config supplies repo-specific overrides. - CLI overrides apply last via dict patch before Pydantic validation.

ralph.config.loader.deep_merge(base, override)[source]

Recursively merge override into base; override wins on conflict.

Parameters:
  • base (dict[str, object]) – The base dictionary to merge into.

  • override (dict[str, object]) – The override dictionary to merge.

Returns:

A new dictionary with the merged result.

Return type:

dict[str, object]

ralph.config.loader.load_config(config_path=None, cli_overrides=None, workspace_scope=None)[source]

Build merged UnifiedConfig from all layers.

Merge order (lowest to highest priority):
  1. Embedded defaults (Pydantic field defaults)

  2. ~/.config/ralph-workflow.toml

  3. .agent/ralph-workflow.toml (project-local)

  4. CLI flag overrides

Parameters:
  • config_path (Path | None) – Optional path to local config file. Defaults to .agent/ralph-workflow.toml.

  • cli_overrides (dict[str, object] | None) – Optional dictionary of CLI flag overrides.

  • workspace_scope (WorkspaceScope | None)

Returns:

Validated UnifiedConfig instance.

Raises:

SystemExit – If configuration validation fails.

Return type:

UnifiedConfig

ralph.config.loader.load_local_only(config_path)[source]

Load configuration from a specific path without merging global config.

Parameters:

config_path (Path) – Path to the configuration file.

Returns:

Validated UnifiedConfig instance.

Return type:

UnifiedConfig

ralph.config.loader.load_toml(path)[source]

Read a TOML file; return empty dict if missing.

Parameters:

path (Path) – Path to the TOML file.

Returns:

Parsed TOML content as a dictionary, or empty dict if file doesn’t exist.

Return type:

dict[str, object]

ralph.config.mcp_loader

Three-layer mcp.toml loader.

Merge order (lowest → highest priority):
  1. Bundled default - ralph/policy/defaults/mcp.toml (ships in wheel)

  2. User-global - $XDG_CONFIG_HOME/ralph-workflow-mcp.toml

    (default: ~/.config/ralph-workflow-mcp.toml)

  3. Project-local - .agent/mcp.toml (resolved via WorkspaceScope)

Unlike the main config loader, TOML parse errors here are fail-fast: any malformed file triggers a typed exit rather than a silent empty-dict fallback.

exception ralph.config.mcp_loader.McpConfigError(message, *, code=1)[source]

Bases: SystemExit

Typed fail-fast exit for invalid MCP configuration.

Parameters:
  • message (str)

  • code (int)

Return type:

None

ralph.config.mcp_loader.bundled_default_mcp_config_path()[source]

Return the path to the bundled default MCP configuration file.

Return type:

Path

ralph.config.mcp_loader.global_mcp_config_path()[source]

Return the user-level global MCP config path, respecting XDG_CONFIG_HOME.

Return type:

Path

ralph.config.mcp_loader.load_mcp_config(workspace_scope=None, config_path=None)[source]

Build merged McpConfig from all layers.

Parameters:
  • workspace_scope (WorkspaceScope | None) – Provides the project-local .agent/ root. Not used when config_path is given.

  • config_path (Path | None) – Explicit override for the project-local layer.

Returns:

Validated McpConfig.

Raises:

McpConfigError – On TOML parse error, schema validation failure, or unknown fallback backend reference.

Return type:

McpConfig

ralph.config.mcp_loader.local_mcp_config_path(workspace_scope)[source]

Return the workspace-local MCP config path for the given workspace scope.

Parameters:

workspace_scope (WorkspaceScope)

Return type:

Path

ralph.config.mcp_models

Pydantic models for mcp.toml.

class ralph.config.mcp_models.McpConfig(*, mcp_servers=<factory>, web_search=<factory>, web_visit=<factory>, media=<factory>)[source]

Bases: BaseModel

Top-level mcp.toml document.

Parameters:
model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.config.mcp_models.McpServerSpec(*, name, transport, url=None, command=None, args=<factory>, env=<factory>, chains=None)[source]

Bases: BaseModel

Schema for a single MCP server entry in mcp.toml.

Parameters:
  • name (str)

  • transport (Literal['http', 'stdio'])

  • url (str | None)

  • command (str | None)

  • args (list[str])

  • env (dict[str, str])

  • chains (list[str] | None)

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.config.mcp_models.MediaConfig(*, enabled=True, max_inline_bytes=5242880)[source]

Bases: BaseModel

Multimodal media support config in mcp.toml.

Broad multimodal support (images, PDFs, audio, video, documents) is enabled by default. Disable with [media] enabled = false in mcp.toml.

Parameters:
  • enabled (bool)

  • max_inline_bytes (int)

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.config.mcp_models.WebSearchBackendSpec(*, backend, url=None, api_key=None, api_key_env=None, timeout_seconds=None)[source]

Bases: BaseModel

Backend configuration for built-in web search providers.

Parameters:
  • backend (Literal['ddgs', 'searxng', 'tavily', 'brave', 'exa'])

  • url (str | None)

  • api_key (str | None)

  • api_key_env (str | None)

  • timeout_seconds (float | None)

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.config.mcp_models.WebSearchConfig(*, enabled=True, backend='ddgs', fallback=<factory>, backends=<factory>, web_search_default_timeout_seconds=10.0)[source]

Bases: BaseModel

Top-level web_search config in mcp.toml.

Parameters:
  • enabled (bool)

  • backend (str)

  • fallback (list[str])

  • backends (dict[str, WebSearchBackendSpec])

  • web_search_default_timeout_seconds (float)

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.config.mcp_models.WebVisitConfig(*, enabled=True, timeout_ms=15000, max_bytes=2097152, user_agent='RalphWorkflow/1.0 (+https://ralph-workflow.dev)', allow_private_networks=False, extract_links=False)[source]

Bases: BaseModel

Top-level web_visit config in mcp.toml.

Parameters:
  • enabled (bool)

  • timeout_ms (int)

  • max_bytes (int)

  • user_agent (str)

  • allow_private_networks (bool)

  • extract_links (bool)

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

ralph.config.models

Pydantic v2 models for Ralph configuration.

class ralph.config.models.AgentConfig(*, cmd, output_flag=None, yolo_flag=None, verbose_flag=None, can_commit=False, json_parser=JsonParserType.GENERIC, model_flag=None, print_flag=None, streaming_flag=None, session_flag=None, display_name=None, transport=None, subagent_capability=None, model=None)[source]

Bases: BaseModel

Configuration for a single AI agent.

Parameters:
  • cmd (str)

  • output_flag (str | None)

  • yolo_flag (str | None)

  • verbose_flag (str | None)

  • can_commit (bool)

  • json_parser (JsonParserType)

  • model_flag (str | None)

  • print_flag (str | None)

  • streaming_flag (str | None)

  • session_flag (str | None)

  • display_name (str | None)

  • transport (AgentTransport | None)

  • subagent_capability (bool | None)

  • model (str | None)

cmd

Base command to run the agent.

Type:

str

output_flag

Optional output format flag for streaming JSON.

Type:

str | None

yolo_flag

Optional autonomous/non-interactive flag string.

Type:

str | None

verbose_flag

Flag for verbose output.

Type:

str | None

can_commit

Whether the agent can run git commit.

Type:

bool

json_parser

Which JSON parser to use for agent output.

Type:

JsonParserType

model_flag

Optional model/provider flag.

Type:

str | None

print_flag

Optional print flag for non-interactive output mode.

Type:

str | None

streaming_flag

Optional streaming flag for partial JSON messages.

Type:

str | None

session_flag

Optional session continuation flag template.

Type:

str | None

display_name

Human-readable display name for UI/UX.

Type:

str | None

transport

Invocation/MCP transport type for the agent runtime.

Type:

AgentTransport | None

subagent_capability

Whether the agent runtime exposes a usable sub-agent / task tooling that can dispatch parallel work. When None (the default), it is inferred from the resolved transport: Claude / Claude-interactive runs default to True; every other transport defaults to None (no inference, the agent decides at runtime). The bundled ralph-workflow.toml ships with [agents.claude] subagent_capability = true so new installs and partial overrides both inherit the sub-agent-enabled default.

Type:

bool | None

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

model_post_init(_context)[source]

Override this method to perform additional initialization after __init__ and model_construct. This is useful if you want to do some validation that requires the entire model to be initialized.

Parameters:

_context (object)

Return type:

None

class ralph.config.models.CcsConfig(*, output_flag='--output-format=stream-json', yolo_flag='--permission-mode auto', verbose_flag='--verbose', print_flag='--print', streaming_flag='--include-partial-messages', json_parser='claude', session_flag='--resume {}', can_commit=True)[source]

Bases: BaseModel

Headless-by-design Claude Code Switch (CCS) defaults.

CCS aliases explicitly run Claude in non-interactive streaming mode (--print --output-format=stream-json). That is the intended explicit headless Claude path for users who configure [ccs_aliases]. The built-in claude agent runs in interactive mode by default.

Parameters:
  • output_flag (str)

  • yolo_flag (str)

  • verbose_flag (str)

  • print_flag (str)

  • streaming_flag (str)

  • json_parser (str)

  • session_flag (str)

  • can_commit (bool)

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.config.models.GeneralConfig(*, verbosity=2, telemetry_enabled=True, workflow=<factory>, developer_iters=5, developer_context=1, prompt_path=None, templates_dir=None, git_user_name=None, git_user_email=None, provider_fallback=<factory>, max_same_agent_retries=10, max_commit_residual_retries=10, max_retries=3, retry_delay_ms=1000, backoff_multiplier=2.0, max_backoff_ms=60000, max_cycles=3, execution_history_limit=1000, agent_idle_timeout_seconds=300.0, agent_idle_drain_window_seconds=0.5, agent_idle_max_waiting_on_child_seconds=1800.0, agent_idle_poll_interval_seconds=0.05, agent_parent_exit_grace_seconds=5.0, agent_descendant_wait_timeout_seconds=30.0, agent_descendant_wait_poll_seconds=0.5, agent_process_exit_wait_seconds=30.0, agent_max_session_seconds=3300.0, agent_session_soft_wrapup_seconds=3000.0, agent_repeated_error_consecutive_threshold=5, agent_repeated_error_window_count=8, agent_repeated_error_window_seconds=600.0, agent_waiting_status_interval_seconds=30.0, agent_suspect_waiting_on_child_seconds=600.0, agent_idle_no_progress_waiting_on_child_seconds=600.0, agent_os_descendant_only_ceiling_seconds=300.0, agent_os_descendant_only_suspect_seconds=60.0, agent_cpu_idle_seconds=60.0, agent_log_growth_seconds=30.0, agent_no_progress_quiet_seconds=240.0, agent_no_progress_quiet_minimum_invocation_seconds=120.0, agent_no_progress_quiet_heartbeat_ceiling_seconds=240.0, agent_child_progress_ttl_seconds=45.0, agent_child_heartbeat_ttl_seconds=15.0, agent_child_stale_label_ttl_seconds=10.0, agent_child_exit_reconcile_seconds=5.0, agent_post_tool_result_progression_seconds=120.0, agent_idle_activity_evidence_ttl_seconds=30.0, agent_process_monitor_enabled=True, agent_subagent_output_capture_enabled=True, agent_subagent_output_poll_interval_seconds=1.0, agent_workspace_change_weights=<factory>)[source]

Bases: BaseModel

[general] section of ralph-workflow.toml.

Parameters:
  • verbosity (int)

  • telemetry_enabled (bool)

  • workflow (GeneralWorkflowFlags)

  • developer_iters (Annotated[int, Ge(ge=1)])

  • developer_context (Annotated[int, Ge(ge=1)])

  • prompt_path (Path | None)

  • templates_dir (Path | None)

  • git_user_name (str | None)

  • git_user_email (str | None)

  • provider_fallback (dict[str, list[str]])

  • max_same_agent_retries (Annotated[int, Ge(ge=0)])

  • max_commit_residual_retries (Annotated[int, Ge(ge=0)])

  • max_retries (Annotated[int, Ge(ge=0)])

  • retry_delay_ms (Annotated[int, Ge(ge=0)])

  • backoff_multiplier (Annotated[float, Ge(ge=1.0)])

  • max_backoff_ms (Annotated[int, Ge(ge=0)])

  • max_cycles (Annotated[int, Ge(ge=1)])

  • execution_history_limit (Annotated[int, Ge(ge=1)])

  • agent_idle_timeout_seconds (Annotated[float, Gt(gt=0)])

  • agent_idle_drain_window_seconds (Annotated[float, Ge(ge=0)])

  • agent_idle_max_waiting_on_child_seconds (Annotated[float, Gt(gt=0)])

  • agent_idle_poll_interval_seconds (Annotated[float, Gt(gt=0)])

  • agent_parent_exit_grace_seconds (Annotated[float, Ge(ge=0)])

  • agent_descendant_wait_timeout_seconds (Annotated[float, Ge(ge=0)])

  • agent_descendant_wait_poll_seconds (Annotated[float, Gt(gt=0)])

  • agent_process_exit_wait_seconds (Annotated[float, Ge(ge=0)])

  • agent_max_session_seconds (Annotated[float | None, Gt(gt=0.0)])

  • agent_session_soft_wrapup_seconds (Annotated[float | None, Gt(gt=0.0)])

  • agent_repeated_error_consecutive_threshold (Annotated[int | None, Gt(gt=0)])

  • agent_repeated_error_window_count (Annotated[int | None, Gt(gt=0)])

  • agent_repeated_error_window_seconds (Annotated[float | None, Gt(gt=0.0)])

  • agent_waiting_status_interval_seconds (Annotated[float, Gt(gt=0)])

  • agent_suspect_waiting_on_child_seconds (Annotated[float | None, Gt(gt=0.0)])

  • agent_idle_no_progress_waiting_on_child_seconds (Annotated[float | None, Gt(gt=0.0)])

  • agent_os_descendant_only_ceiling_seconds (Annotated[float | None, Gt(gt=0.0)])

  • agent_os_descendant_only_suspect_seconds (Annotated[float | None, Gt(gt=0.0)])

  • agent_cpu_idle_seconds (Annotated[float | None, Gt(gt=0.0)])

  • agent_log_growth_seconds (Annotated[float | None, Gt(gt=0.0)])

  • agent_no_progress_quiet_seconds (Annotated[float | None, Gt(gt=0.0)])

  • agent_no_progress_quiet_minimum_invocation_seconds (Annotated[float | None, Gt(gt=0.0)])

  • agent_no_progress_quiet_heartbeat_ceiling_seconds (Annotated[float | None, Gt(gt=0.0)])

  • agent_child_progress_ttl_seconds (Annotated[float, Gt(gt=0)])

  • agent_child_heartbeat_ttl_seconds (Annotated[float, Gt(gt=0)])

  • agent_child_stale_label_ttl_seconds (Annotated[float, Gt(gt=0)])

  • agent_child_exit_reconcile_seconds (Annotated[float, Ge(ge=0)])

  • agent_post_tool_result_progression_seconds (Annotated[float | None, Gt(gt=0.0)])

  • agent_idle_activity_evidence_ttl_seconds (Annotated[float, Ge(ge=0)])

  • agent_process_monitor_enabled (bool)

  • agent_subagent_output_capture_enabled (bool)

  • agent_subagent_output_poll_interval_seconds (Annotated[float, Gt(gt=0)])

  • agent_workspace_change_weights (dict[str, float])

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.config.models.UnifiedConfig(*, general=<factory>, ccs=<factory>, agents=<factory>, ccs_aliases=<factory>, agent_chains=<factory>, agent_drains=<factory>, prompt_helper=<factory>)[source]

Bases: BaseModel

Top-level merged configuration (global + local + CLI overrides).

Parameters:
model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

ralph.config.welcome

First-run welcome banner and agent availability helper.

ralph.config.welcome.emit_first_run_welcome(results, *, agent_registry=None, is_regenerate=False, display_context)[source]

Print a structured first-run welcome panel.

Parameters:
  • results (list[BootstrapResult]) – Bootstrap results from a bootstrap operation.

  • agent_registry (HasListAgents | None) – Optional agent registry for availability checking.

  • is_regenerate (bool) – Whether this is a regenerate (–regenerate-config) operation.

  • display_context (DisplayContext) – Display context for adaptive layout (required). The banner and panel are both emitted on display_context.console.

Return type:

None

ralph.config.agent_config

Agent configuration model definitions.

class ralph.config.agent_config.AgentConfig(*, cmd, output_flag=None, yolo_flag=None, verbose_flag=None, can_commit=False, json_parser=JsonParserType.GENERIC, model_flag=None, print_flag=None, streaming_flag=None, session_flag=None, display_name=None, transport=None, subagent_capability=None, model=None)[source]

Bases: BaseModel

Configuration for a single AI agent.

Parameters:
  • cmd (str)

  • output_flag (str | None)

  • yolo_flag (str | None)

  • verbose_flag (str | None)

  • can_commit (bool)

  • json_parser (JsonParserType)

  • model_flag (str | None)

  • print_flag (str | None)

  • streaming_flag (str | None)

  • session_flag (str | None)

  • display_name (str | None)

  • transport (AgentTransport | None)

  • subagent_capability (bool | None)

  • model (str | None)

cmd

Base command to run the agent.

Type:

str

output_flag

Optional output format flag for streaming JSON.

Type:

str | None

yolo_flag

Optional autonomous/non-interactive flag string.

Type:

str | None

verbose_flag

Flag for verbose output.

Type:

str | None

can_commit

Whether the agent can run git commit.

Type:

bool

json_parser

Which JSON parser to use for agent output.

Type:

JsonParserType

model_flag

Optional model/provider flag.

Type:

str | None

print_flag

Optional print flag for non-interactive output mode.

Type:

str | None

streaming_flag

Optional streaming flag for partial JSON messages.

Type:

str | None

session_flag

Optional session continuation flag template.

Type:

str | None

display_name

Human-readable display name for UI/UX.

Type:

str | None

transport

Invocation/MCP transport type for the agent runtime.

Type:

AgentTransport | None

subagent_capability

Whether the agent runtime exposes a usable sub-agent / task tooling that can dispatch parallel work. When None (the default), it is inferred from the resolved transport: Claude / Claude-interactive runs default to True; every other transport defaults to None (no inference, the agent decides at runtime). The bundled ralph-workflow.toml ships with [agents.claude] subagent_capability = true so new installs and partial overrides both inherit the sub-agent-enabled default.

Type:

bool | None

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

model_post_init(_context)[source]

Override this method to perform additional initialization after __init__ and model_construct. This is useful if you want to do some validation that requires the entire model to be initialized.

Parameters:

_context (object)

Return type:

None

ralph.config.agent_transport

Agent transport enum for runtime invocation/MCP support.

class ralph.config.agent_transport.AgentTransport(*values)[source]

Bases: StrEnum

Invocation/MCP transport type for an agent runtime.

CLAUDE

Claude Code compatible invocation/MCP transport.

CLAUDE_INTERACTIVE

Unattended interactive Claude Code transport.

CODEX

Codex CLI compatible invocation/MCP transport.

OPENCODE

OpenCode compatible invocation/MCP transport.

NANOCODER

Nanocoder CLI compatible invocation/MCP transport.

GENERIC

No special transport support.

AGY

Google Anti Gravity compatible invocation/MCP transport.

PI

Pi coding agent (pi.dev) compatible invocation/MCP transport. The headless BuiltinAgentSpec uses pi –mode json <prompt> per https://pi.dev/docs/latest/usage. Ralph wires MCP through a generated Pi extension and treats clean exits without required completion evidence as resumable against the captured Pi session.

CURSOR

Cursor Agent CLI compatible invocation/MCP transport. The headless BuiltinAgentSpec uses agent --print --output-format stream-json and Ralph wires MCP through .cursor/mcp.json / ~/.cursor/mcp.json (the documented Cursor config surface).

ralph.config.ccs_config

CCS configuration model definitions.

class ralph.config.ccs_config.CcsAliasConfig(*, cmd, output_flag=None, yolo_flag=None, verbose_flag=None, print_flag=None, streaming_flag=None, json_parser=None, can_commit=None, model_flag=None, session_flag=None)[source]

Bases: BaseModel

Per-alias CCS configuration (table form).

Parameters:
  • cmd (str)

  • output_flag (str | None)

  • yolo_flag (str | None)

  • verbose_flag (str | None)

  • print_flag (str | None)

  • streaming_flag (str | None)

  • json_parser (str | None)

  • can_commit (bool | None)

  • model_flag (str | None)

  • session_flag (str | None)

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.config.ccs_config.CcsConfig(*, output_flag='--output-format=stream-json', yolo_flag='--permission-mode auto', verbose_flag='--verbose', print_flag='--print', streaming_flag='--include-partial-messages', json_parser='claude', session_flag='--resume {}', can_commit=True)[source]

Bases: BaseModel

Headless-by-design Claude Code Switch (CCS) defaults.

CCS aliases explicitly run Claude in non-interactive streaming mode (--print --output-format=stream-json). That is the intended explicit headless Claude path for users who configure [ccs_aliases]. The built-in claude agent runs in interactive mode by default.

Parameters:
  • output_flag (str)

  • yolo_flag (str)

  • verbose_flag (str)

  • print_flag (str)

  • streaming_flag (str)

  • json_parser (str)

  • session_flag (str)

  • can_commit (bool)

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

ralph.config.general_config

General Ralph configuration model definitions.

class ralph.config.general_config.GeneralConfig(*, verbosity=2, telemetry_enabled=True, workflow=<factory>, developer_iters=5, developer_context=1, prompt_path=None, templates_dir=None, git_user_name=None, git_user_email=None, provider_fallback=<factory>, max_same_agent_retries=10, max_commit_residual_retries=10, max_retries=3, retry_delay_ms=1000, backoff_multiplier=2.0, max_backoff_ms=60000, max_cycles=3, execution_history_limit=1000, agent_idle_timeout_seconds=300.0, agent_idle_drain_window_seconds=0.5, agent_idle_max_waiting_on_child_seconds=1800.0, agent_idle_poll_interval_seconds=0.05, agent_parent_exit_grace_seconds=5.0, agent_descendant_wait_timeout_seconds=30.0, agent_descendant_wait_poll_seconds=0.5, agent_process_exit_wait_seconds=30.0, agent_max_session_seconds=3300.0, agent_session_soft_wrapup_seconds=3000.0, agent_repeated_error_consecutive_threshold=5, agent_repeated_error_window_count=8, agent_repeated_error_window_seconds=600.0, agent_waiting_status_interval_seconds=30.0, agent_suspect_waiting_on_child_seconds=600.0, agent_idle_no_progress_waiting_on_child_seconds=600.0, agent_os_descendant_only_ceiling_seconds=300.0, agent_os_descendant_only_suspect_seconds=60.0, agent_cpu_idle_seconds=60.0, agent_log_growth_seconds=30.0, agent_no_progress_quiet_seconds=240.0, agent_no_progress_quiet_minimum_invocation_seconds=120.0, agent_no_progress_quiet_heartbeat_ceiling_seconds=240.0, agent_child_progress_ttl_seconds=45.0, agent_child_heartbeat_ttl_seconds=15.0, agent_child_stale_label_ttl_seconds=10.0, agent_child_exit_reconcile_seconds=5.0, agent_post_tool_result_progression_seconds=120.0, agent_idle_activity_evidence_ttl_seconds=30.0, agent_process_monitor_enabled=True, agent_subagent_output_capture_enabled=True, agent_subagent_output_poll_interval_seconds=1.0, agent_workspace_change_weights=<factory>)[source]

Bases: BaseModel

[general] section of ralph-workflow.toml.

Parameters:
  • verbosity (int)

  • telemetry_enabled (bool)

  • workflow (GeneralWorkflowFlags)

  • developer_iters (Annotated[int, Ge(ge=1)])

  • developer_context (Annotated[int, Ge(ge=1)])

  • prompt_path (Path | None)

  • templates_dir (Path | None)

  • git_user_name (str | None)

  • git_user_email (str | None)

  • provider_fallback (dict[str, list[str]])

  • max_same_agent_retries (Annotated[int, Ge(ge=0)])

  • max_commit_residual_retries (Annotated[int, Ge(ge=0)])

  • max_retries (Annotated[int, Ge(ge=0)])

  • retry_delay_ms (Annotated[int, Ge(ge=0)])

  • backoff_multiplier (Annotated[float, Ge(ge=1.0)])

  • max_backoff_ms (Annotated[int, Ge(ge=0)])

  • max_cycles (Annotated[int, Ge(ge=1)])

  • execution_history_limit (Annotated[int, Ge(ge=1)])

  • agent_idle_timeout_seconds (Annotated[float, Gt(gt=0)])

  • agent_idle_drain_window_seconds (Annotated[float, Ge(ge=0)])

  • agent_idle_max_waiting_on_child_seconds (Annotated[float, Gt(gt=0)])

  • agent_idle_poll_interval_seconds (Annotated[float, Gt(gt=0)])

  • agent_parent_exit_grace_seconds (Annotated[float, Ge(ge=0)])

  • agent_descendant_wait_timeout_seconds (Annotated[float, Ge(ge=0)])

  • agent_descendant_wait_poll_seconds (Annotated[float, Gt(gt=0)])

  • agent_process_exit_wait_seconds (Annotated[float, Ge(ge=0)])

  • agent_max_session_seconds (Annotated[float | None, Gt(gt=0.0)])

  • agent_session_soft_wrapup_seconds (Annotated[float | None, Gt(gt=0.0)])

  • agent_repeated_error_consecutive_threshold (Annotated[int | None, Gt(gt=0)])

  • agent_repeated_error_window_count (Annotated[int | None, Gt(gt=0)])

  • agent_repeated_error_window_seconds (Annotated[float | None, Gt(gt=0.0)])

  • agent_waiting_status_interval_seconds (Annotated[float, Gt(gt=0)])

  • agent_suspect_waiting_on_child_seconds (Annotated[float | None, Gt(gt=0.0)])

  • agent_idle_no_progress_waiting_on_child_seconds (Annotated[float | None, Gt(gt=0.0)])

  • agent_os_descendant_only_ceiling_seconds (Annotated[float | None, Gt(gt=0.0)])

  • agent_os_descendant_only_suspect_seconds (Annotated[float | None, Gt(gt=0.0)])

  • agent_cpu_idle_seconds (Annotated[float | None, Gt(gt=0.0)])

  • agent_log_growth_seconds (Annotated[float | None, Gt(gt=0.0)])

  • agent_no_progress_quiet_seconds (Annotated[float | None, Gt(gt=0.0)])

  • agent_no_progress_quiet_minimum_invocation_seconds (Annotated[float | None, Gt(gt=0.0)])

  • agent_no_progress_quiet_heartbeat_ceiling_seconds (Annotated[float | None, Gt(gt=0.0)])

  • agent_child_progress_ttl_seconds (Annotated[float, Gt(gt=0)])

  • agent_child_heartbeat_ttl_seconds (Annotated[float, Gt(gt=0)])

  • agent_child_stale_label_ttl_seconds (Annotated[float, Gt(gt=0)])

  • agent_child_exit_reconcile_seconds (Annotated[float, Ge(ge=0)])

  • agent_post_tool_result_progression_seconds (Annotated[float | None, Gt(gt=0.0)])

  • agent_idle_activity_evidence_ttl_seconds (Annotated[float, Ge(ge=0)])

  • agent_process_monitor_enabled (bool)

  • agent_subagent_output_capture_enabled (bool)

  • agent_subagent_output_poll_interval_seconds (Annotated[float, Gt(gt=0)])

  • agent_workspace_change_weights (dict[str, float])

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.config.general_config.GeneralWorkflowFlags(*, checkpoint_enabled=True, unsafe_mode=False)[source]

Bases: BaseModel

General configuration workflow automation flags.

Parameters:
  • checkpoint_enabled (bool)

  • unsafe_mode (bool)

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

unsafe_mode: bool

When True Ralph merges its MCP server into the agent’s existing MCP configuration instead of replacing it, giving the agent access to Ralph tools alongside whatever MCP servers it already had. When False (the default) Ralph overwrites the agent’s MCP config with a Ralph-only server set, matching the strict-authority contract used in unattended runs.

ralph.config.json_parser_type

JSON parser type enum for agent output parsing.

class ralph.config.json_parser_type.JsonParserType(*values)[source]

Bases: StrEnum

JSON parser type for agent output parsing.

CLAUDE

Parser for Claude’s NDJSON streaming format

CODEX

Parser for Codex’s NDJSON streaming format

GEMINI

Parser for Gemini’s NDJSON streaming format

OPENCODE

Parser for OpenCode’s NDJSON streaming format

GENERIC

Generic NDJSON parser for other agents

PI

Parser for Pi AgentSessionEvent NDJSON streaming format (https://pi.dev/docs/latest/json).

ralph.config.mcp_server_spec

MCP server configuration model for mcp.toml.

class ralph.config.mcp_server_spec.McpServerSpec(*, name, transport, url=None, command=None, args=<factory>, env=<factory>, chains=None)[source]

Bases: BaseModel

Schema for a single MCP server entry in mcp.toml.

Parameters:
  • name (Annotated[str, _PydanticGeneralMetadata(pattern='^[a-z][a-z0-9_-]*$')])

  • transport (Literal['http', 'stdio'])

  • url (str | None)

  • command (str | None)

  • args (list[str])

  • env (dict[str, str])

  • chains (list[str] | None)

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

ralph.config.pause_on_exit

Pause behavior enum for process exit.

class ralph.config.pause_on_exit.PauseOnExit(*values)[source]

Bases: StrEnum

Pause behavior before process exit.

AUTO

Pause only on standalone failure

ALWAYS

Always pause before exit

NEVER

Never pause before exit

ralph.config.prompt_helper_config

Prompt helper configuration.

class ralph.config.prompt_helper_config.PromptHelperConfig(*, agent=None)[source]

Bases: BaseModel

Configuration for the prompt helper feature.

Parameters:

agent (str | None)

model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

ralph.config.recovery_strategy

Recovery strategy enum for pipeline failures.

class ralph.config.recovery_strategy.RecoveryStrategy(*values)[source]

Bases: StrEnum

Recovery strategy when pipeline encounters failures.

FAIL

Fail immediately on errors

AUTO

Attempt automatic recovery

FORCE

Force through errors

ralph.config.verbosity

Verbosity level enum for Ralph output.

class ralph.config.verbosity.Verbosity(*values)[source]

Bases: StrEnum

Verbosity level for Ralph output.

QUIET

Minimal output (errors only)

NORMAL

Default verbosity level

VERBOSE

More detailed output

FULL

Full output with all details

DEBUG

Debug-level output for troubleshooting

ralph.config.web_search_config

Web search configuration models for mcp.toml.

class ralph.config.web_search_config.WebSearchConfig(*, enabled=True, backend='ddgs', fallback=<factory>, backends=<factory>, web_search_default_timeout_seconds=10.0)[source]

Bases: BaseModel

Top-level web_search config in mcp.toml.

Parameters:
  • enabled (bool)

  • backend (str)

  • fallback (list[str])

  • backends (dict[str, WebSearchBackendSpec])

  • web_search_default_timeout_seconds (Annotated[float, Gt(gt=0)])

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

ralph.config.web_service_configs

Web visit and media configuration models for mcp.toml.

class ralph.config.web_service_configs.WebVisitConfig(*, enabled=True, timeout_ms=15000, max_bytes=2097152, user_agent='RalphWorkflow/1.0 (+https://ralph-workflow.dev)', allow_private_networks=False, extract_links=False)[source]

Bases: BaseModel

Top-level web_visit config in mcp.toml.

Parameters:
  • enabled (bool)

  • timeout_ms (Annotated[int, Gt(gt=0)])

  • max_bytes (Annotated[int, Gt(gt=0)])

  • user_agent (str)

  • allow_private_networks (bool)

  • extract_links (bool)

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

Policy

The policy group is the heart of Ralph Workflow’s policy-driven pipeline: it loads, validates, explains, and renders the TOML policy tables (pipeline.toml, agents.toml, artifacts.toml, mcp.toml). The runtime is a generic policy interpreter; behavior changes land in ralph/policy/defaults/ rather than in code. See Concepts for the model and Configuration Reference for the explain surface used by ralph explain.

ralph.policy

Policy module for Ralph orchestration configuration.

This module provides the policy layer that drives Ralph’s configurable orchestration. Policy is expressed in three TOML files:

  • agents.toml: Agent chains and drain-to-chain bindings

  • pipeline.toml: Phase graph and transition routing

  • artifacts.toml: Artifact contracts per drain

The loader validates all three files together, ensuring cross-file consistency (e.g., every drain used in pipeline.toml is bound in agents.toml).

Example usage:

from pathlib import Path
from ralph.policy import load_policy

bundle = load_policy(Path(".agent"))
drain_binding = bundle.agents.agent_drains["planning"]
chain = bundle.agents.agent_chains[drain_binding.chain]

ralph.policy.loader

TOML policy loader with fallback to bundled defaults.

Loads agents.toml, pipeline.toml, and artifacts.toml from the user’s .agent/ config directory, falling back to the packaged defaults when files are absent.

All loading goes through Pydantic validation so any malformed config surfaces as a PolicyValidationError with field-level detail.

User-global policy overrides prefer branded filenames (ralph-workflow-pipeline.toml, ralph-workflow-artifacts.toml) while still accepting the legacy unprefixed names for backward compatibility.

ralph.policy.loader.load_policy(config_dir, config=None)[source]

Load all three policy TOML files and return a validated PolicyBundle.

Files are loaded from config_dir (the .agent/ directory). Any absent file is silently replaced with the bundled default.

Parameters:
Return type:

PolicyBundle

ralph.policy.loader.load_policy_or_die(config_dir, config=None)[source]

Load policy, exiting with a user-friendly message on failure.

Parameters:
  • config_dir (Path) – Path to the .agent/ configuration directory.

  • config (UnifiedConfig | None)

Returns:

Validated PolicyBundle.

Return type:

PolicyBundle

ralph.policy.models

Pydantic models for policy configuration (agents.toml, pipeline.toml, artifacts.toml).

class ralph.policy.models.AgentChainConfig(*, agents, max_retries=3, retry_delay_ms=1000)[source]

Bases: _FrozenPolicyModel

Definition of a named agent fallback chain.

Parameters:
  • agents (Annotated[list[str], MinLen(min_length=1)])

  • max_retries (Annotated[int, Ge(ge=0)])

  • retry_delay_ms (Annotated[int, Ge(ge=0)])

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.policy.models.AgentDrainConfig(*, chain, drain_class=None, capability_class=None)[source]

Bases: _FrozenPolicyModel

Binding from a named drain to an agent chain.

Parameters:
  • chain (str)

  • drain_class (str | None)

  • capability_class (str | None)

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.policy.models.AgentsPolicy(*, agent_chains=<factory>, agent_drains=<factory>, forbid_sibling_drain_inference=False)[source]

Bases: _FrozenPolicyModel

Top-level agents.toml policy document.

Parameters:
model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.policy.models.ArtifactContract(*, drain, artifact_type, decision_vocabulary=<factory>, prompt_template=None, artifact_json_path=None, markdown_summary_path=None)[source]

Bases: _FrozenPolicyModel

Contract for an artifact type submitted by an agent at a given drain.

Parameters:
  • drain (str)

  • artifact_type (str)

  • decision_vocabulary (list[str])

  • prompt_template (str | None)

  • artifact_json_path (str | None)

  • markdown_summary_path (str | None)

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.policy.models.ArtifactHistoryPolicy(*, enabled=False, clear_on_fresh_entry=True)[source]

Bases: _FrozenPolicyModel

Per-phase artifact history policy.

Parameters:
  • enabled (bool)

  • clear_on_fresh_entry (bool)

model_config = {'extra': 'forbid', 'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.policy.models.ArtifactProofPolicy(*, require_plan_proof=True, require_analysis_proof=True)[source]

Bases: _FrozenPolicyModel

Per-phase proof requirements for development artifacts.

Parameters:
  • require_plan_proof (bool)

  • require_analysis_proof (bool)

model_config = {'extra': 'forbid', 'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.policy.models.ArtifactsPolicy(*, artifacts=<factory>)[source]

Bases: _FrozenPolicyModel

Top-level artifacts.toml policy document.

Parameters:

artifacts (dict[str, ArtifactContract])

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.policy.models.BudgetCounterConfig(*, description='', tracks_budget=True, default_max)[source]

Bases: _FrozenPolicyModel

Declaration of a named budget counter in the pipeline.

Parameters:
  • description (str)

  • tracks_budget (bool)

  • default_max (Annotated[int, Ge(ge=0)])

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

ralph.policy.models.DrainName

alias of str

class ralph.policy.models.GroupPolicyBlock(*, kind='group', child_blocks=<factory>, completion_block, before_complete=<factory>, after_complete=<factory>, increments_counter=None, loop_resets=<factory>)[source]

Bases: _FrozenPolicyModel

Composite authoring block that owns lifecycle semantics.

Parameters:
  • kind (Literal['group'])

  • child_blocks (list[str])

  • completion_block (str)

  • before_complete (list[str])

  • after_complete (list[str])

  • increments_counter (str | None)

  • loop_resets (list[str])

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.policy.models.IndividualPolicyBlock(*, kind='individual', phase_name, phase)[source]

Bases: _FrozenPolicyModel

Leaf authoring block that compiles directly to one runtime phase.

Parameters:
model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.policy.models.LifecyclePhasePolicy(*, lifecycle_name, completion_block, increments_counter=None, loop_resets=<factory>, before_complete=<factory>, after_complete=<factory>)[source]

Bases: _FrozenPolicyModel

Lifecycle-owned accounting metadata keyed by compiled completion phase.

Parameters:
  • lifecycle_name (str)

  • completion_block (str)

  • increments_counter (str | None)

  • loop_resets (list[str])

  • before_complete (list[str])

  • after_complete (list[str])

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.policy.models.LoopCounterConfig(*, default_max=3, description='')[source]

Bases: _FrozenPolicyModel

Declaration of a named loop iteration counter in the pipeline.

Parameters:
  • default_max (Annotated[int, Ge(ge=0)])

  • description (str)

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.policy.models.PhaseCommitPolicy(*, requires_artifact=True, skipped_advances_progress=True, increments_counter=None, route_counter=None, loop_resets=<factory>)[source]

Bases: _FrozenPolicyModel

Commit semantics for commit-role phases.

Parameters:
  • requires_artifact (bool)

  • skipped_advances_progress (bool)

  • increments_counter (str | None)

  • route_counter (str | None)

  • loop_resets (list[str])

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.policy.models.PhaseDecisionRoute(*, target, reset_loop=False, increments_counter=None)[source]

Bases: _FrozenPolicyModel

Route produced by an analysis decision.

Parameters:
  • target (str)

  • reset_loop (bool)

  • increments_counter (str | None)

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.policy.models.PhaseDefinition(*, drain, transitions, role=None, skip_invocation=False, retry_policy=None, loop_policy=None, decisions=<factory>, commit_policy=None, verification=None, artifact_required=True, terminal_outcome=None, bypass_routes=<factory>, clean_outcome=None, issues_outcome=None, prompt_template=None, continuation_template=None, loopback_prompt_template=None, parallelization=None, artifact_history=None, artifact_proof_policy=None, workflow_fallback=None, clear_drains_on_fresh_entry=<factory>, display_style=None)[source]

Bases: _FrozenPolicyModel

Definition of a single phase in the pipeline graph.

Parameters:
  • drain (str)

  • transitions (PhaseTransition)

  • role (Literal['execution', 'analysis', 'review', 'commit', 'verification', 'terminal', 'fanout_join', 'commit_cleanup'] | None)

  • skip_invocation (bool)

  • retry_policy (PhaseRetryPolicy | None)

  • loop_policy (PhaseLoopPolicy | None)

  • decisions (dict[str, PhaseDecisionRoute])

  • commit_policy (PhaseCommitPolicy | None)

  • verification (PhaseVerificationPolicy | None)

  • artifact_required (bool)

  • terminal_outcome (Literal['success', 'failure'] | None)

  • bypass_routes (dict[str, str])

  • clean_outcome (str | None)

  • issues_outcome (str | None)

  • prompt_template (str | None)

  • continuation_template (str | None)

  • loopback_prompt_template (str | None)

  • parallelization (PhaseParallelization | None)

  • artifact_history (ArtifactHistoryPolicy | None)

  • artifact_proof_policy (ArtifactProofPolicy | None)

  • workflow_fallback (PhaseWorkflowFallback | None)

  • clear_drains_on_fresh_entry (list[str])

  • display_style (str | None)

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.policy.models.PhaseLoopPolicy(*, iteration_state_field, loopback_review_outcome=None)[source]

Bases: _FrozenPolicyModel

Loop linkage for analysis phases.

Parameters:
  • iteration_state_field (str)

  • loopback_review_outcome (str | None)

model_config = {'extra': 'forbid', 'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.policy.models.PhaseParallelization(*, mode='same_workspace', dispatch_mode='ralph_fan_out', max_parallel_workers=8, max_work_units=50, require_allowed_directories=True, post_fanout_verification=False)[source]

Bases: _FrozenPolicyModel

Transition-scoped parallelization policy for a pipeline phase.

Parameters:
  • mode (Literal['same_workspace'])

  • dispatch_mode (Literal['ralph_fan_out', 'agent_subagents'])

  • max_parallel_workers (Annotated[int, Ge(ge=1)])

  • max_work_units (Annotated[int, Ge(ge=1)])

  • require_allowed_directories (bool)

  • post_fanout_verification (bool)

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.policy.models.PhaseRetryPolicy(*, max_retries=3, retry_delay_ms=1000, retry_in_session=False)[source]

Bases: _FrozenPolicyModel

Per-phase retry policy overriding chain-level defaults.

Parameters:
  • max_retries (Annotated[int, Ge(ge=0)])

  • retry_delay_ms (Annotated[int, Ge(ge=0)])

  • retry_in_session (bool)

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.policy.models.PhaseTransition(*, on_success, on_failure=None, on_loopback=None)[source]

Bases: _FrozenPolicyModel

Transition rules from a phase to other phases.

Parameters:
  • on_success (str)

  • on_failure (str | None)

  • on_loopback (str | None)

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.policy.models.PhaseVerificationPolicy(*, kind, gate_for, on_failure_route=None)[source]

Bases: _FrozenPolicyModel

Verification gating semantics for a phase.

Parameters:
  • kind (Literal['artifact', 'none'])

  • gate_for (Literal['advancement', 'completion', 'release'])

  • on_failure_route (str | None)

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.policy.models.PhaseWorkflowFallback(*, target, note=None)[source]

Bases: _FrozenPolicyModel

Policy-declared workflow-level fallback when a phase’s agent chain is exhausted.

Parameters:
  • target (str)

  • note (str | None)

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.policy.models.PipelinePolicy(*, blocks=<factory>, phases=<factory>, entry_block=None, entry_phase='planning', terminal_phase='complete', loop_counters=<factory>, budget_counters=<factory>, post_commit_routes=<factory>, lifecycle_phases=<factory>, default_phase_retry_policy=<factory>, recovery=<factory>)[source]

Bases: _FrozenPolicyModel

Top-level pipeline.toml policy document.

Parameters:
effective_retry_policy(phase_name)[source]

Resolve the effective retry policy for a phase.

Parameters:

phase_name (str)

Return type:

PhaseRetryPolicy

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

terminal_states()[source]

Return the full set of terminal state names for transition validation.

Return type:

set[str]

class ralph.policy.models.PolicyBundle(*, agents, pipeline, artifacts)[source]

Bases: _FrozenPolicyModel

Aggregate of all three policy documents.

Parameters:
model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.policy.models.PostCommitRoute(*, when, target)[source]

Bases: _FrozenPolicyModel

Budget-guarded route applied after commit success.

Parameters:
model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.policy.models.PostCommitRouteWhen(*, phase, budget_state)[source]

Bases: _FrozenPolicyModel

Condition selector for post-commit budget-guarded routing.

Parameters:
  • phase (str)

  • budget_state (Literal['remaining', 'exhausted', 'no_review'])

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.policy.models.RecoveryPolicy(*, cycle_cap=200, failed_route='failed_terminal', terminal_failure_phase=None, preserve_session_on_categories=('agent',))[source]

Bases: _FrozenPolicyModel

Pipeline-wide recovery policy.

Parameters:
  • cycle_cap (Annotated[int, Ge(ge=1)])

  • failed_route (str)

  • terminal_failure_phase (str | None)

  • preserve_session_on_categories (tuple[str, ...])

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

ralph.policy.validation

Policy validation utilities beyond what Pydantic provides at the model level.

exception ralph.policy.validation.CheckpointPolicyMismatchError(checkpoint_phase, valid_phases)[source]

Bases: Exception

Raised when a checkpoint’s phase is not present in the current policy.

Parameters:
  • checkpoint_phase (str)

  • valid_phases (set[str])

Return type:

None

checkpoint_phase

Phase name stored in the checkpoint.

valid_phases

Set of valid phase names in the current policy.

exception ralph.policy.validation.PolicyValidationError(message, source=None)[source]

Bases: Exception

Raised when a policy validation rule is violated.

Parameters:
  • message (str)

  • source (str | None)

Return type:

None

message

Human-readable error message describing the validation failure.

source

Which policy area failed (optional).

ralph.policy.validation.get_drain_resolution_matrix(bundle)[source]

Generate a normalized drain resolution matrix.

Parameters:

bundle (PolicyBundle)

Return type:

dict[str, dict[str, str]]

ralph.policy.validation.validate_agent_chains_satisfiable(bundle, agent_registry)[source]

Validate that every chain references known, workflow-compatible agents.

Parameters:
Return type:

None

ralph.policy.validation.validate_chain_exists(chain, bundle)[source]

Validate that an agent chain is defined.

Parameters:
Return type:

None

ralph.policy.validation.validate_checkpoint_against_policy(state, bundle)[source]

Validate a checkpoint state against the current policy bundle.

Parameters:
Return type:

None

ralph.policy.validation.validate_checkpoint_compatible(checkpoint_phase, bundle)[source]

Validate that a checkpoint phase is compatible with the current policy bundle.

Parameters:
Return type:

None

ralph.policy.validation.validate_cli_counter_overrides(policy, cli_counter_overrides, errors)[source]

Validate that every CLI counter override names a declared budget counter.

Parameters:
  • policy (PipelinePolicy)

  • cli_counter_overrides (dict[str, int])

  • errors (list[str])

Return type:

None

ralph.policy.validation.validate_drain_bound(drain, bundle)[source]

Validate that a drain name has a binding in the current policy.

Parameters:
Return type:

None

ralph.policy.validation.validate_drain_contracts(bundle)[source]

Validate drain contracts and enforce strict binding rules.

Parameters:

bundle (PolicyBundle)

Return type:

None

ralph.policy.validation.validate_phase_exists_in_policy(phase, policy)[source]

Validate that a phase name is present in the current pipeline policy.

Parameters:
Return type:

None

ralph.policy.validation.validate_policy_completeness(bundle, *, cli_counter_overrides=None)[source]

Validate that the policy bundle is semantically complete for policy-driven orchestration.

Parameters:
  • bundle (PolicyBundle)

  • cli_counter_overrides (dict[str, int] | None)

Return type:

None

ralph.policy.validation.validate_recovery_config(bundle)[source]

Validate recovery-related configuration in the policy bundle.

Parameters:

bundle (PolicyBundle)

Return type:

None

ralph.policy.validation.validate_required_inputs(workspace_scope, inline_prompt=None)[source]

Validate that required input files exist and are readable.

The source-prompt path is resolved through ralph.pro_support.prompt.resolve_effective_prompt_path() so the PROMPT_PATH env var is honoured in Pro mode. When inline_prompt is supplied the check is skipped entirely (the inline prompt supersedes the file on disk).

Parameters:
Return type:

None

ralph.policy.validation.validate_work_units_against_policy(work_units, pipeline_policy, *, phase)[source]

Validate parsed planning work_units against the active phase’s parallelization policy.

Parameters:
Return type:

None

ralph.policy.explain

Policy explanation — converts a PolicyBundle into a structured human-readable description.

This package answers the key questions a user has when looking at an unfamiliar policy: - What happens after this phase succeeds? - What makes a phase terminal? - When is a commit required? - When does the system retry vs fall back vs fail? - When is parallel execution allowed?

ralph.policy.explain.budget_counter_explanation

Explanation of a budget counter.

class ralph.policy.explain.budget_counter_explanation.BudgetCounterExplanation(name, description, tracks_budget, default_max=0)[source]

Bases: object

Explanation of a budget counter.

Parameters:
  • name (str)

  • description (str)

  • tracks_budget (bool)

  • default_max (int)

ralph.policy.explain.commit_policy_explanation

Explanation of a phase’s commit policy.

class ralph.policy.explain.commit_policy_explanation.CommitPolicyExplanation(increments_counter, loop_resets, requires_artifact)[source]

Bases: object

Explanation of a phase’s commit policy.

Parameters:
  • increments_counter (str | None)

  • loop_resets (list[str])

  • requires_artifact (bool)

ralph.policy.explain.loop_counter_explanation

Explanation of a loop counter.

class ralph.policy.explain.loop_counter_explanation.LoopCounterExplanation(name, default_max, description)[source]

Bases: object

Explanation of a loop counter.

Parameters:
  • name (str)

  • default_max (int)

  • description (str)

ralph.policy.explain.lifecycle_explanation

Lifecycle completion explanation structures.

class ralph.policy.explain.lifecycle_explanation.LifecycleExplanation(lifecycle_name, completion_phase, completion_block, increments_counter=None, before_complete=<factory>, after_complete=<factory>)[source]

Bases: object

Human-readable lifecycle completion metadata.

Parameters:
  • lifecycle_name (str)

  • completion_phase (str)

  • completion_block (str)

  • increments_counter (str | None)

  • before_complete (list[str])

  • after_complete (list[str])

ralph.policy.explain.loop_policy_explanation

Explanation of a phase’s loop policy.

class ralph.policy.explain.loop_policy_explanation.LoopPolicyExplanation(max_iterations, iteration_state_field, loopback_review_outcome)[source]

Bases: object

Explanation of a phase’s loop policy.

Parameters:
  • max_iterations (int)

  • iteration_state_field (str)

  • loopback_review_outcome (str | None)

ralph.policy.explain.parallel_explanation

Explanation of the parallel execution policy.

class ralph.policy.explain.parallel_explanation.ParallelExplanation(phase, max_parallel_workers, max_work_units, require_allowed_directories, post_fanout_verification=False)[source]

Bases: object

Explanation of the parallel execution policy.

Parameters:
  • phase (str)

  • max_parallel_workers (int)

  • max_work_units (int)

  • require_allowed_directories (bool)

  • post_fanout_verification (bool)

ralph.policy.explain.phase_explanation

Explanation of a single phase.

class ralph.policy.explain.phase_explanation.PhaseExplanation(name, role, drain, chain, agents, max_retries, skip_invocation, on_success, on_failure, on_loopback, bypass_routes, decisions, loop_policy, commit_policy, terminal_outcome, clean_outcome=None, issues_outcome=None, is_entry=False, is_terminal=False, verification=None, has_parallelization=False, post_commit_routes_info=<factory>, workflow_fallback=None)[source]

Bases: object

Explanation of a single phase.

Parameters:
  • name (str)

  • role (str | None)

  • drain (str)

  • chain (str | None)

  • agents (list[str])

  • max_retries (int)

  • skip_invocation (bool)

  • on_success (str | None)

  • on_failure (str | None)

  • on_loopback (str | None)

  • bypass_routes (dict[str, str])

  • decisions (dict[str, str])

  • loop_policy (LoopPolicyExplanation | None)

  • commit_policy (CommitPolicyExplanation | None)

  • terminal_outcome (str | None)

  • clean_outcome (str | None)

  • issues_outcome (str | None)

  • is_entry (bool)

  • is_terminal (bool)

  • verification (VerificationExplanation | None)

  • has_parallelization (bool)

  • post_commit_routes_info (list[tuple[str, str]])

  • workflow_fallback (tuple[str, str | None] | None)

ralph.policy.explain.policy_explanation

Complete structured explanation of a PolicyBundle.

class ralph.policy.explain.policy_explanation.PolicyExplanation(entry_phase, terminal_phase, entry_block=None, authored_blocks=<factory>, lifecycle_explanations=<factory>, phases=<factory>, loop_counters=<factory>, budget_counters=<factory>, terminal_outcomes=<factory>, parallel_execution=None, parallel_executions=<factory>, post_commit_routes=<factory>, recovery=None)[source]

Bases: object

Complete structured explanation of a PolicyBundle.

Parameters:

ralph.policy.explain.post_commit_route_explanation

Explanation of a single post-commit route entry.

class ralph.policy.explain.post_commit_route_explanation.PostCommitRouteExplanation(phase, budget_state, target)[source]

Bases: object

Explanation of a single post-commit route entry.

Parameters:
  • phase (str)

  • budget_state (str)

  • target (str)

ralph.policy.explain.recovery_explanation

Explanation of the recovery policy.

class ralph.policy.explain.recovery_explanation.RecoveryExplanation(cycle_cap, terminal_recovery_route, preserve_session_on_categories)[source]

Bases: object

Explanation of the recovery policy.

Parameters:
  • cycle_cap (int)

  • terminal_recovery_route (str)

  • preserve_session_on_categories (list[str])

ralph.policy.explain.terminal_outcome_explanation

Explanation of a terminal phase outcome.

class ralph.policy.explain.terminal_outcome_explanation.TerminalOutcomeExplanation(phase, outcome)[source]

Bases: object

Explanation of a terminal phase outcome.

Parameters:
  • phase (str)

  • outcome (str)

ralph.policy.explain.verification_explanation

Explanation of a phase’s verification policy.

class ralph.policy.explain.verification_explanation.VerificationExplanation(kind, gate_for, on_failure_route)[source]

Bases: object

Explanation of a phase’s verification policy.

Parameters:
  • kind (str)

  • gate_for (str)

  • on_failure_route (str | None)

ralph.policy.render

Render a PolicyExplanation as human-readable text or ASCII workflow diagram.

This module converts the structured PolicyExplanation dataclass (from explain.py) into text or rich console output that answers: - What happens after this phase succeeds? - What makes a phase terminal? - When is a commit required? - When does the system retry vs fall back vs fail? - When is parallel execution allowed?

ralph.policy.render.render_explanation_ascii(exp)[source]

Render a PolicyExplanation as a deterministic pure-ASCII workflow diagram.

Visual contract (per PLAN step 4):

  1. BOX STRUCTURE: Each phase renders as a 4-line box: Line 1: “+” + “-” * (width-2) + “+” Line 2: “|” + <phase_name> centered + “|” Line 3: “|” + “role=<role>” centered + “|” Line 4: “+” + “-” * (width-2) + “+” Width = max(len(name), len(“role=” + role), 6) + 4

  2. ENTRY MARKER: =ENTRY=> appears on the line above the entry phase box.

  3. HAPPY-PATH: A center-aligned “|” then “v” arrowhead connects consecutive phases on the success spine.

  4. DECISION BRANCHES: For each decision whose target differs from on_success, render “ +–[decision_name]–> target_phase” (4-space indent).

  5. LOOPBACK: When on_loopback differs from on_success, render “ <<==[loopback]== returns to ‘target_phase’” When loop_policy is set, also render: “ [LOOPBACK: counter=NAME, max=N]” And always append the re-entry banner: “ >> RE-ENTRY at target_phase”

  6. TERMINAL MARKERS: ==SUCCESS==> for terminal_outcome=”success”; ==FAILURE==> for terminal_outcome=”failure”. Only policy-declared terminal phases get markers.

  7. FANOUT ANNOTATION: >>> FAN_OUT (max_workers=N, max_units=M, post_fanout_verify=yes/no) appears above the phase box for the parallel-eligible phase.

  8. LOOP ANNOTATION: [loop: counter=NAME, max=N] appears above the phase box for phases with loop_policy.

  9. GLYPHS: Pure ASCII only — allowed chars: + - | < > v ^ = [ ] _ . , plus alphanumerics. No Unicode box-drawing characters.

  10. ORDERING: Success spine (follows on_success from entry_phase); unvisited phases appended alphabetically. Ties broken alphabetically.

Parameters:

exp (PolicyExplanation) – The policy explanation to render.

Returns:

A multi-line ASCII string representing the workflow diagram.

Return type:

str

ralph.policy.render.render_explanation_sentences(phase)[source]

Generate explanation sentences for a phase per Required Product Outcome D.

Parameters:

phase (PhaseExplanation)

Return type:

list[str]

ralph.policy.render.render_explanation_text(exp)[source]

Render a PolicyExplanation as a multi-section human-readable text string.

Parameters:

exp (object)

Return type:

str

Pipeline

The pipeline group owns the Ralph-loop runtime: state model, reducer, effect router, phase agent handlers, and the run loop. Effects are declared in TOML and routed through ralph/pipeline/effect_router.py to handlers under ralph/pipeline/parallel/, ralph/pipeline/effects/, and ralph/pipeline/recovery/ (covered by their own groups). See Concepts for the loop model and Concepts for how phases route between planning, development, commit, and analysis.

ralph.pipeline

Public pipeline state and reducer exports.

This package is the core of Ralph’s orchestration loop. It exposes the public state/event types most callers need, plus the pure reduce function used to advance the state machine.

ralph.pipeline.checkpoint

Atomic checkpoint persistence for pipeline resume.

This module handles saving and loading pipeline state checkpoints. Checkpoints enable the pipeline to resume from interruption without losing progress.

All writes are atomic (write to .tmp then rename) to prevent partial checkpoint corruption.

class ralph.pipeline.checkpoint.Checkpoint(path=PosixPath('.agent/checkpoint.json'))[source]

Bases: object

Handle for atomic checkpoint read/write operations at a fixed path.

Parameters:

path (Path)

ralph.pipeline.checkpoint.exists(path=PosixPath('.agent/checkpoint.json'))[source]

Check if a checkpoint exists.

Parameters:

path (Path) – Path to the checkpoint file.

Returns:

True if checkpoint exists.

Return type:

bool

ralph.pipeline.checkpoint.inspect(path=PosixPath('.agent/checkpoint.json'))[source]

Return formatted checkpoint summary.

Parameters:

path (Path) – Path to the checkpoint file.

Returns:

Formatted string representation of the checkpoint.

Return type:

str

ralph.pipeline.checkpoint.load(path=PosixPath('.agent/checkpoint.json'))[source]

Load checkpoint from disk.

Parameters:

path (Path) – Path to the checkpoint file.

Returns:

PipelineState if checkpoint exists and is valid, None otherwise. If the loaded state contains a forbidden last_error sentinel (e.g. “Unknown failure”), it is dropped and replaced with None.

Return type:

PipelineState | None

async ralph.pipeline.checkpoint.load_async(path=PosixPath('.agent/checkpoint.json'))[source]

Load checkpoint from disk without blocking the event loop.

Delegates to load() via asyncio.to_thread so callers can await this from an async context without stalling the event loop.

Parameters:

path (Path) – Path to the checkpoint file.

Returns:

PipelineState if checkpoint exists and is valid, None otherwise.

Return type:

PipelineState | None

ralph.pipeline.checkpoint.remove(path=PosixPath('.agent/checkpoint.json'))[source]

Remove a checkpoint file.

Parameters:

path (Path) – Path to the checkpoint file.

Return type:

None

ralph.pipeline.checkpoint.save(state, path=PosixPath('.agent/checkpoint.json'))[source]

Atomically write state to disk.

Writes to a temporary file first, then renames to the target path. This ensures no partial checkpoint data on disk if the write is interrupted.

Parameters:
  • state (PipelineState) – The pipeline state to save.

  • path (Path) – Path to save the checkpoint. Defaults to .agent/checkpoint.json.

Return type:

None

async ralph.pipeline.checkpoint.save_async(state, path=PosixPath('.agent/checkpoint.json'))[source]

Atomically write state to disk without blocking the event loop.

Delegates to save() via asyncio.to_thread so callers can await this from an async context without stalling the event loop.

Parameters:
  • state (PipelineState) – The pipeline state to save.

  • path (Path) – Path to save the checkpoint. Defaults to .agent/checkpoint.json.

Return type:

None

ralph.pipeline.checkpoint.worker_checkpoint_path(worker_namespace)[source]

Return the worker-local checkpoint path.

Parameters:

worker_namespace (Path)

Return type:

Path

ralph.pipeline.cycle_baseline

Development-cycle diff baseline management.

The baseline SHA is written once when a dev cycle begins (after planning succeeds, before any development phase invocation) and is never updated by mid-cycle commits. It is cleared at cycle boundaries so that the next cycle starts fresh.

ralph.pipeline.cycle_baseline.clear_cycle_baseline(workspace_root)[source]

Remove the baseline file so the next cycle starts fresh.

Parameters:

workspace_root (Path)

Return type:

None

ralph.pipeline.cycle_baseline.read_cycle_baseline(workspace_root)[source]

Return the recorded baseline SHA, or None if no baseline is set.

Parameters:

workspace_root (Path)

Return type:

str | None

ralph.pipeline.cycle_baseline.write_cycle_baseline(workspace_root, sha, *, force=False)[source]

Record sha as the diff baseline for the current dev cycle.

When force is False (the default), an existing baseline is preserved and this call is a no-op. Callers that open a fresh cycle must pass force=True to overwrite any stale baseline.

Parameters:
  • workspace_root (Path)

  • sha (str)

  • force (bool)

Return type:

None

ralph.pipeline.effects

Effect types: what the pipeline wants to do next.

Effects are emitted by the orchestrator and describe the next action to be taken. They carry all necessary data for the effect handler to execute the action.

No I/O is performed in this module - effects are pure data descriptions.

ralph.pipeline.effects.commit_effect

Commit pipeline effect.

class ralph.pipeline.effects.commit_effect.CommitEffect(message_file)[source]

Bases: object

Effect to create a git commit.

Parameters:

message_file (str)

message_file

Path to the commit message file.

Type:

str

ralph.pipeline.effects.early_skip_commit_effect

Early-skip-commit pipeline effect.

class ralph.pipeline.effects.early_skip_commit_effect.EarlySkipCommitEffect[source]

Bases: object

Effect to skip a commit phase before prompt materialization or agent invocation.

Emitted by the orchestrator when the worktree has no pending work so the commit phase can advance via COMMIT_SKIPPED without creating a commit prompt or invoking a commit agent.

ralph.pipeline.effects.exhausted_analysis_phase_advance_effect

Exhausted-analysis-phase-advance pipeline effect.

class ralph.pipeline.effects.exhausted_analysis_phase_advance_effect.ExhaustedAnalysisPhaseAdvanceEffect(phase)[source]

Bases: object

Effect to bypass an already exhausted analysis phase through PHASE_ADVANCE.

Used when the runner is already sitting in an exhausted analysis phase. The runner must not invoke the analysis agent again; instead it emits PHASE_ADVANCE so reducer-owned success routing, loop resets, and phase advancement remain the single source of truth.

Parameters:

phase (str)

ralph.pipeline.effects.exit_failure_effect

Exit-failure pipeline effect.

class ralph.pipeline.effects.exit_failure_effect.ExitFailureEffect(reason)[source]

Bases: object

Effect to exit with failure.

Parameters:

reason (str)

reason

Reason for the failure. Must be non-empty, non-whitespace, and must not contain any known non-empty sentinel that indicates a bug (e.g. “Unknown failure”, “None”, “null”). Empty and whitespace-only reasons are rejected separately. Sentinel checks are performed as substring matches to catch cases like “development: Unknown failure”.

Type:

str

ralph.pipeline.effects.exit_success_effect

Exit-success pipeline effect.

class ralph.pipeline.effects.exit_success_effect.ExitSuccessEffect[source]

Bases: object

Effect to exit with success.

ralph.pipeline.effects.fan_out_effect

Fan-out pipeline effect.

class ralph.pipeline.effects.fan_out_effect.FanOutEffect(work_units, max_workers, run_post_fanout_verification=False, phase='')[source]

Bases: object

Effect to fan out parallel work for any phase whose [parallelization] policy is declared.

Workers run in the shared checkout. Each worker is restricted to its declared allowed_directories and writes its outputs to a per-worker namespace under .agent/workers/<unit_id>/.

Parameters:
  • work_units (tuple[WorkUnit, ...])

  • max_workers (int)

  • run_post_fanout_verification (bool)

  • phase (str)

work_units

Work units to execute in parallel.

Type:

tuple[ralph.pipeline.work_unit.WorkUnit, …]

max_workers

Maximum number of concurrent workers.

Type:

int

run_post_fanout_verification

When True, the runner will execute a serialized workspace-wide verification step after all workers finish. Defaults to False so unit tests do not invoke make verify.

Type:

bool

phase

The pipeline phase for which fan-out is occurring. Defaults to empty string for backward compatibility; the runner always populates this.

Type:

str

ralph.pipeline.effects.invoke_agent_effect

Invoke-agent pipeline effect.

class ralph.pipeline.effects.invoke_agent_effect.InvokeAgentEffect(agent_name, phase, prompt_file, drain=None, chain_name='')[source]

Bases: object

Effect to invoke an AI agent.

Parameters:
  • agent_name (str)

  • phase (str)

  • prompt_file (str)

  • drain (str | None)

  • chain_name (str)

agent_name

Name of the agent to invoke.

Type:

str

phase

Current pipeline phase.

Type:

str

prompt_file

Path to the prompt file for the agent.

Type:

str

chain_name

Name of the agent chain being used.

Type:

str

ralph.pipeline.effects.prepare_prompt_effect

Prepare-prompt pipeline effect.

class ralph.pipeline.effects.prepare_prompt_effect.PreparePromptEffect(phase, iteration=None, drain=None, previous_phase=None, skip_materialization=False)[source]

Bases: object

Effect to prepare a prompt for an agent.

Parameters:
  • phase (str)

  • iteration (int | None)

  • drain (str | None)

  • previous_phase (str | None)

  • skip_materialization (bool)

phase

Current pipeline phase.

Type:

str

iteration

Deprecated — kept for prompt-template rendering only; None when not applicable.

Type:

int | None

skip_materialization

When True, advance phase without materializing the prompt. Used for routing-only transitions (e.g. skip_invocation phases).

Type:

bool

ralph.pipeline.effects.push_effect

Push pipeline effect.

class ralph.pipeline.effects.push_effect.PushEffect(remote='origin', branch=None)[source]

Bases: object

Effect to push changes to remote.

Parameters:
  • remote (str)

  • branch (str | None)

remote

Remote name (default: origin).

Type:

str

branch

Branch name (optional, uses current branch if not specified).

Type:

str | None

ralph.pipeline.effects.save_checkpoint_effect

Save-checkpoint pipeline effect.

class ralph.pipeline.effects.save_checkpoint_effect.SaveCheckpointEffect[source]

Bases: object

Effect to save a checkpoint.

ralph.pipeline.events

Pipeline events representing all state transitions.

ralph.pipeline.events.analysis_decision_event

Analysis decision event for the pipeline.

class ralph.pipeline.events.analysis_decision_event.AnalysisDecisionEvent(phase, decision)[source]

Bases: object

Event emitted when an analysis phase resolves a decision from the agent artifact.

The reducer routes via policy.phases[phase].decisions[decision].target directly, making this a first-class routing input rather than a collapsed signal.

Parameters:
  • phase (str)

  • decision (str)

phase

Name of the phase that emitted this decision.

Type:

str

decision

Raw decision string from the agent artifact (validated against the phase’s decision_vocabulary in the artifacts policy).

Type:

str

ralph.pipeline.events.phase_failure_event

Phase failure event for the pipeline.

class ralph.pipeline.events.phase_failure_event.PhaseFailureEvent(phase, reason, recoverable, retry_in_session=False, failure_category=None, skip_same_agent_retries=False)[source]

Bases: object

Event emitted when a phase handler encounters a failure condition.

This event carries a recoverable flag that determines how the reducer processes the failure: - recoverable=True: routes through _handle_agent_failure retry/fallback logic - recoverable=False: routes directly to the terminal failure phase

Parameters:
  • phase (str)

  • reason (str)

  • recoverable (bool)

  • retry_in_session (bool)

  • failure_category (FailureCategory | None)

  • skip_same_agent_retries (bool)

phase

Name of the phase that generated this event.

Type:

str

reason

Human-readable description of what caused the failure.

Type:

str

recoverable

Whether this failure should trigger retry/fallback (True) or act as a terminal decision (False).

Type:

bool

retry_in_session

When True and the agent’s transport supports session resume, the recovery path should preserve the active session ID so the next retry continues in the same agent session rather than starting from scratch. Only meaningful when recoverable=True.

Type:

bool

failure_category

Optional pre-classified failure category for known phase-level failures such as artifact/proof validation errors. When present, recovery must honor this category directly instead of re-classifying the string reason heuristically.

Type:

FailureCategory | None

skip_same_agent_retries

When True, recoverable routing should advance to the next configured agent immediately instead of spending retry attempts on the current agent.

Type:

bool

ralph.pipeline.events.pipeline_event

Pipeline event type enumeration.

class ralph.pipeline.events.pipeline_event.PipelineEvent(*values)[source]

Bases: StrEnum

Enumeration of all pipeline state-machine transition events.

ralph.pipeline.events.post_fanout_verification_event

Post-fanout verification event for the pipeline.

class ralph.pipeline.events.post_fanout_verification_event.PostFanoutVerificationEvent(success, exit_code=None, error=None)[source]

Bases: object

Event emitted after serialized workspace-wide verification runs post fan-out.

Parameters:
  • success (bool)

  • exit_code (int | None)

  • error (str | None)

success

Whether verification passed (exit code 0).

Type:

bool

exit_code

The verification subprocess exit code, or None if not run.

Type:

int | None

error

Human-readable error description when success=False, else None.

Type:

str | None

ralph.pipeline.events.worker_completed_event

Worker completed event for the pipeline.

class ralph.pipeline.events.worker_completed_event.WorkerCompletedEvent(unit_id, exit_code)[source]

Bases: object

Emitted when a parallel worker finishes successfully.

Parameters:
  • unit_id (str)

  • exit_code (int)

ralph.pipeline.events.worker_failed_event

Worker failed event for the pipeline.

class ralph.pipeline.events.worker_failed_event.WorkerFailedEvent(unit_id, exit_code, error)[source]

Bases: object

Emitted when a parallel worker terminates with a failure.

Parameters:
  • unit_id (str)

  • exit_code (int)

  • error (str)

ralph.pipeline.events.worker_started_event

Worker started event for the pipeline.

class ralph.pipeline.events.worker_started_event.WorkerStartedEvent(unit_id)[source]

Bases: object

Emitted when a parallel worker begins execution.

Parameters:

unit_id (str)

ralph.pipeline.handoffs

Pure helpers for phase handoff and drain resolution.

This module centralizes the policy-driven routing and phase-to-drain lookup used across the reducer and runtime. Keeping these helpers pure makes the handoff contract easy to unit test and keeps runtime injection limited to the policy data loaded at the composition root.

class ralph.pipeline.handoffs.ExhaustedAnalysisBypassResult(state, target_phase, skipped=())[source]

Bases: object

Resolved exhausted-analysis bypass outcome for a phase handoff.

Parameters:
class ralph.pipeline.handoffs.ExhaustedAnalysisSkip(phase, target_phase, iteration_field, iteration_value, max_iterations)[source]

Bases: object

Details for a single exhausted analysis phase that was bypassed.

Parameters:
ralph.pipeline.handoffs.Handoff

alias of ExhaustedAnalysisBypassResult

ralph.pipeline.handoffs.resolve_exhausted_analysis_bypass(state, candidate_phase, pipeline_policy)[source]

Resolve exhausted-analysis re-entry from one canonical policy-driven helper.

The helper accepts an analysis phase candidate and follows that phase’s declared success transition whenever its loop budget is already exhausted. Each skipped analysis phase has its loop counter reset to 0 and its skip metadata recorded so reducer and runner paths can reuse the exact same bypass contract.

Parameters:
Return type:

ExhaustedAnalysisBypassResult

ralph.pipeline.handoffs.resolve_next_phase(current_phase, signal, pipeline_policy)[source]

Resolve the next phase based on a signal and the pipeline policy.

Parameters:
Return type:

PipelinePhase

ralph.pipeline.handoffs.resolve_phase_drain(phase, pipeline_policy)[source]

Return the configured drain for a phase.

Parameters:
Return type:

str | None

ralph.pipeline.handoffs.resolve_post_commit_phase(state, pipeline_policy)[source]

Resolve next phase for a successful commit with optional budget guards.

Routing is driven by post_commit_routes in policy, matched by phase name and budget_state. This works for any commit-role phase, not just the canonical development_commit/review_commit names.

Parameters:
Return type:

PipelinePhase

ralph.pipeline.loopback

Shared loopback handlers for policy-driven phases.

ralph.pipeline.loopback.handle_capped_phase_loopback_policy_driven(state, policy, phase_def, *, review_outcome, advance_to_failed, resolve_or_terminal, advance_phase)[source]

Handle capped loopback using policy-declared loop_policy.

Parameters:
  • state (PipelineState)

  • policy (PipelinePolicy)

  • phase_def (PhaseDefinition)

  • review_outcome (str | None)

  • advance_to_failed (Callable[[PipelineState, str, PipelinePolicy | None], _EffectResult])

  • resolve_or_terminal (Callable[[PipelineState, str, PipelinePolicy, str], _EffectResult])

  • advance_phase (Callable[[PipelineState, str, PipelinePolicy | None], _EffectResult])

Return type:

_EffectResult

ralph.pipeline.phase_entry_cleaner

Phase-entry drain clearing for fresh phase entries.

This module handles the clearing of drain artifact files (JSON + Markdown handoff) when a pipeline phase is genuinely entered fresh — on program start, cross-phase transition, or last-commit re-entry — as opposed to same-phase retry or analysis loopback where the existing context should be preserved.

ralph.pipeline.phase_entry_cleaner.clear_phase_entry_drains(workspace, phase_name, previous_phase, pipeline_policy, artifacts_policy)[source]

Clear declared drain artifacts on genuine fresh phase entry.

Clears the primary JSON and Markdown handoff for each drain listed in the phase’s clear_drains_on_fresh_entry field, but only when is_fresh_phase_entry returns True.

Parameters:
  • workspace (Workspace) – The workspace to operate on.

  • phase_name (str) – The phase being entered.

  • previous_phase (str | None) – The previous phase from PreparePromptEffect.

  • pipeline_policy (PipelinePolicy) – The active pipeline policy.

  • artifacts_policy (ArtifactsPolicy) – The active artifacts policy.

Return type:

None

ralph.pipeline.phase_entry_cleaner.is_fresh_phase_entry(entering_phase, previous_phase, pipeline_policy)[source]

Return True when entering a phase via genuine fresh entry.

Fresh entry means program start, cross-phase transition, or last-commit re-entry. Clearing is suppressed for same-phase retry, analysis loopback, and checkpoint restore (resume). The resume case is handled by the caller via state-field check, not by this function.

Parameters:
  • entering_phase (str) – The phase being entered.

  • previous_phase (str | None) – The phase that preceded this entry (from PreparePromptEffect).

  • pipeline_policy (PipelinePolicy) – The active pipeline policy.

Returns:

True for genuine fresh entry; False for same-phase or analysis loopback.

Return type:

bool

ralph.pipeline.orchestrator

Pure orchestrator: determine next effect from state and policy.

This module implements the core routing logic that was previously hardcoded in the reducer’s match arms. Given the current PipelineState and the loaded PipelinePolicy + AgentsPolicy, determine_next_effect() returns the next Effect to execute.

The orchestrator is purely deterministic and has no side effects of its own. To support the Pro contract (see ralph-workflow-pro/docs/product-spec/CONTRACT_RALPH_INTEGRATION.md §3 — PROMPT_PATH env var) the optional workspace_scope argument is read by ralph.pro_support.prompt.resolve_effective_prompt_path() when an InvokeAgentEffect is emitted. The legacy default behaviour (prompt_file="PROMPT.md") is preserved when workspace_scope is None so existing callers and tests are unaffected.

exception ralph.pipeline.orchestrator.PhaseHandlerNotFoundError(phase)[source]

Bases: Exception

Raised when no handler is registered for a phase.

Parameters:

phase (str)

Return type:

None

phase

Phase name that has no handler.

ralph.pipeline.orchestrator.determine_next_effect(state, pipeline_policy, agents_policy, workspace_scope=None)[source]

Pure function: derive next effect from current state and policy.

This is the single routing function that replaces all hardcoded phase routing in the reducer. It consults the pipeline policy to determine which effect to emit based on: - The current phase definition (drain, transitions) - State flags (prompt prepared, agent invoked, analysis complete) - Budget counters (budget_caps / outer_progress, derived remaining)

Parameters:
  • state (PipelineState) – Current pipeline state.

  • pipeline_policy (PipelinePolicy) – Loaded pipeline policy (phase graph from pipeline.toml).

  • agents_policy (AgentsPolicy) – Loaded agents policy (chains and drain bindings).

  • workspace_scope (WorkspaceScope | None) – Optional workspace scope used to resolve the operator-visible source prompt path (PROMPT_PATH or <workspace>/PROMPT.md). When None the legacy literal "PROMPT.md" is preserved on emitted InvokeAgentEffect instances, matching the pre-Pro contract.

Returns:

The next Effect to execute.

Return type:

Effect

ralph.pipeline.orchestrator.get_phase_drain(phase, pipeline_policy)[source]

Get the drain name for a given phase.

Parameters:
Returns:

Drain name or None if phase is not found.

Return type:

str | None

ralph.pipeline.orchestrator.resolve_next_phase(current_phase, signal, pipeline_policy)[source]

Backward-compatible wrapper for centralized handoff resolution.

Parameters:
Return type:

PipelinePhase

ralph.pipeline.orchestrator.resolve_post_commit_phase(state, pipeline_policy)[source]

Backward-compatible wrapper for centralized post-commit routing.

Parameters:
Return type:

PipelinePhase

ralph.pipeline.parallel

Same-workspace parallel pipeline coordination.

This package provides the core components for running Ralph Workflow development phases in parallel across multiple worker processes within the same repository checkout (same-workspace fan-out, v1).

Supported public surface:

  • ParallelExecutionMode: Enumeration of supported parallel execution modes. Only SAME_WORKSPACE is supported in v1.

  • SameWorkspaceContext: Configuration for a same-workspace fan-out run, including repo root, per-worker namespace root, MCP factory, and optional executor command.

  • validate_for_same_workspace: Pre-flight validator that rejects overlapping, missing, or reserved edit areas before any worker is launched.

These are the only supported parallel primitives for v1. Per-worker branches and post-development branch reconciliation are explicitly out of scope for this iteration.

ralph.pipeline.parallel.coordinator

Structured concurrency coordinator for parallel development fan-out.

class ralph.pipeline.parallel.coordinator.ParallelCoordinator(*, activity_router=None)[source]

Bases: object

Orchestrates parallel work-unit execution with DAG dependency ordering.

Parameters:

activity_router (ActivityRouter | None)

async run_fan_out(effect, executor, display, ctx=None)[source]

Execute parallel work units while respecting DAG dependencies and worker caps.

Parameters:
Return type:

list[Event]

class ralph.pipeline.parallel.coordinator.WorkerContext(log=None, same_workspace=None, activity_router=None)[source]

Bases: object

Optional runtime context injected into each parallel worker.

Parameters:
exception ralph.pipeline.parallel.coordinator.WorkerFailureError(unit_id, exit_code, error)[source]

Bases: Exception

Raised internally when a parallel worker fails.

Parameters:
  • unit_id (str)

  • exit_code (int)

  • error (str)

Return type:

None

class ralph.pipeline.parallel.coordinator.WorkerLog(log_dir, run_id)[source]

Bases: object

Paths and identifiers for per-worker log files.

Parameters:
  • log_dir (Path)

  • run_id (str)

async ralph.pipeline.parallel.coordinator.run_fan_out(effect, executor, display, ctx=None, activity_router=None)[source]

Execute a fan-out effect using a fresh ParallelCoordinator instance.

Parameters:
Return type:

list[Event]

ralph.pipeline.parallel.mode

Parallel execution mode definitions for same-workspace parallel workers v1.

class ralph.pipeline.parallel.mode.ParallelExecutionMode(*values)[source]

Bases: StrEnum

Supported parallel execution modes.

In v1 only SAME_WORKSPACE is supported. Workers share the single checked-out repository root and are isolated only by edit-area path restrictions and per-worker artifact namespaces — not by filesystem isolation or separate git checkouts.

class ralph.pipeline.parallel.mode.SameWorkspaceContext(repo_root, mcp_factory, executor_command=None, worker_commands=<factory>, signal_bridge=None, worker_namespace_root=None, worker_manifest_paths=<factory>, session_drain='', session_capabilities=frozenset({}), session_model_identity=None, session_capability_profile=None)[source]

Bases: object

Runtime context for same-workspace parallel execution.

Workers run against repo_root directly. Per-worker mutable state lives under worker_namespace_root / <unit_id> / {artifacts,tmp,logs,handoffs}. Workers share one checkout; post-development coordination is state aggregation only.

The session contract fields (session_drain, session_capabilities, session_model_identity, session_capability_profile) carry the parent phase’s resolved MCP session plan verbatim so that parallel workers expose the same multimodal capability surface as the serial execution path.

Parameters:
  • repo_root (Path)

  • mcp_factory (McpServerFactory)

  • executor_command (tuple[str, ...] | None)

  • worker_commands (dict[str, tuple[str, ...]])

  • signal_bridge (SignalBridge | None)

  • worker_namespace_root (Path | None)

  • worker_manifest_paths (dict[str, Path])

  • session_drain (str)

  • session_capabilities (frozenset[str])

  • session_model_identity (MultimodalModelIdentity | None)

  • session_capability_profile (ResolvedCapabilityProfile | None)

ralph.pipeline.parallel.worker_manifest

Typed manifest model for a single parallel worker run.

class ralph.pipeline.parallel.worker_manifest.ParallelWorkerManifest(*, unit_id, description, allowed_directories, phase, drain, config_path=None, cli_overrides=<factory>, worker_namespace, worker_artifact_dir, prompt_file, workspace_root)[source]

Bases: BaseModel

Serializable manifest describing one isolated worker invocation.

Parameters:
  • unit_id (str)

  • description (str)

  • allowed_directories (list[str])

  • phase (str)

  • drain (str)

  • config_path (str | None)

  • cli_overrides (dict[str, object])

  • worker_namespace (str)

  • worker_artifact_dir (str)

  • prompt_file (str)

  • workspace_root (str)

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

to_work_unit()[source]

Return the manifest payload as a WorkUnit for worker execution.

Return type:

WorkUnit

ralph.pipeline.parallel.worker_runtime

Early worker-runtime seam for dedicated parallel worker execution.

class ralph.pipeline.parallel.worker_runtime.WorkerRuntimePaths(checkpoint_path, current_prompt_path, prompt_dump_path, system_prompt_path, multimodal_sidecar_path)[source]

Bases: object

Worker-local filesystem paths for prompt and checkpoint runtime state.

Parameters:
  • checkpoint_path (Path)

  • current_prompt_path (Path)

  • prompt_dump_path (Path)

  • system_prompt_path (Path)

  • multimodal_sidecar_path (Path)

ralph.pipeline.parallel.worker_runtime.build_worker_runtime_paths(*, workspace_root, worker_namespace, phase)[source]

Return the namespaced runtime paths a parallel worker should own.

Parameters:
  • workspace_root (Path)

  • worker_namespace (Path)

  • phase (str)

Return type:

WorkerRuntimePaths

ralph.pipeline.parallel.worker_runtime.run_parallel_worker_from_manifest(*, manifest_path, display_context, pipeline_deps=None, model_identity=None, pro_hooks=None)[source]

Execute one manifest-backed worker flow without entering the shared run loop.

model_identity and pro_hooks are forwarded into the shared dependency composition path so a parallel worker uses the same injected collaborators as the direct run path. They are ignored when an explicit pipeline_deps bundle is supplied.

Parameters:
Return type:

int

ralph.pipeline.parallel.scheduler

Wave scheduler for parallel work-unit execution.

Provides schedule_next_wave, which selects the next batch of ready work units given the set of already-completed unit IDs, the full plan, currently running unit IDs, and the maximum worker concurrency. Units are ready when all their declared dependencies are in completed.

ralph.pipeline.parallel.scheduler.schedule_next_wave(completed, all_units, currently_running, max_workers)[source]

Return ready work units that can be launched in the next wave.

Parameters:
  • completed (set[str])

  • all_units (tuple[WorkUnit, ...])

  • currently_running (set[str])

  • max_workers (int)

Return type:

list[WorkUnit]

ralph.pipeline.parallel.worker_session

Factory helpers for building per-worker MCP session bundles.

Provides build_worker_session, which constructs an AgentSession, starts an MCP server for it via McpServerFactory, and returns a WorkerSessionBundle containing the session, its server handle, and the workspace scope that the worker should operate in.

class ralph.pipeline.parallel.worker_session.WorkerSessionBundle(session, mcp_handle, workspace_scope)[source]

Bases: object

Assembled session, MCP server handle, and workspace scope for a parallel worker.

Parameters:
class ralph.pipeline.parallel.worker_session.WorkerSessionConfig(worker_artifact_dir=None, worker_namespace=None, session_drain='', session_capabilities=frozenset({}), session_model_identity=None, session_capability_profile=None)[source]

Bases: object

Optional session contract parameters for a parallel worker session.

Parameters:
  • worker_artifact_dir (Path | None)

  • worker_namespace (Path | None)

  • session_drain (str)

  • session_capabilities (frozenset[str])

  • session_model_identity (MultimodalModelIdentity | None)

  • session_capability_profile (ResolvedCapabilityProfile | None)

ralph.pipeline.parallel.worker_session.build_worker_session(unit, mcp_factory, workspace_scope, config=None)[source]

Create an AgentSession, start an MCP server, and return the worker bundle.

Pass a WorkerSessionConfig to propagate session contract parameters (drain, capabilities, model identity, capability profile) from the parent phase’s SessionMcpPlan so the worker exposes the same multimodal capability surface as serial execution.

Parameters:
Return type:

WorkerSessionBundle

ralph.pipeline.parallel.parallel_execution_mode

Supported parallel execution modes.

class ralph.pipeline.parallel.parallel_execution_mode.ParallelExecutionMode(*values)[source]

Bases: StrEnum

Supported parallel execution modes.

In v1 only SAME_WORKSPACE is supported. Workers share the single checked-out repository root and are isolated only by edit-area path restrictions and per-worker artifact namespaces — not by filesystem isolation or separate git checkouts.

ralph.pipeline.progress

Canonical workflow progress accounting.

This module owns all mutations for workflow progress fields, including completed outer progress counters, inner analysis-loop counters, routing budgets, review-issue flags tied to progress boundaries, and checkpoint-facing progress mirrors derived from canonical state and active policy.

Contract:

  • Outer progress counters are named by policy (via budget_counters); the runtime never assumes a specific counter name.

  • Analysis loopbacks mutate only the inner loop counter for the current cycle or pass.

  • Capped analysis loopback preserves outer progress and carries the inner loop counter to the cap until analysis or commit outcome resets it.

  • Skipped commits honour the commit_policy.skipped_advances_progress flag; when true (the default), a skip still advances outer progress and ends the current inner loop, so routing can distinguish a consumed-but-skipped iteration from one that never ran.

  • Checkpoint mirrors derive from canonical PipelineState and policy-declared budget counters: the first budget-tracked counter (in commit-phase BFS order) maps to actual_developer_runs; the second maps to actual_reviewer_runs.

class ralph.pipeline.progress.AnalysisLoopCounter(completed, cap)[source]

Bases: object

Canonical analysis-loop counter semantics derived from completed loopbacks.

Parameters:
  • completed (int)

  • cap (int)

property display_iteration: int

Return the saturated 1-based label shown to users.

property is_final: bool

Return True when the current run is the final labeled analysis run.

property next_completed: int

Return the clamped completed count after one more loopback.

property should_skip_reentry: bool

Return True when the next attempt to enter analysis must be skipped.

ralph.pipeline.progress.advance_phase(state, target_phase, *, policy)[source]

Advance phases while applying only canonical routing-budget bookkeeping.

Resets recovery_epoch to 0 on every normal forward advance so the missing-plan recovery loop counter is scoped to the CURRENT consecutive recovery loop, not the lifetime total of unrelated recoveries. Callers that need to override recovery_epoch (e.g. recover_missing_plan_handoff in ralph/pipeline/_runner_state_helpers.py and _advance_to_failed in ralph/pipeline/reducer.py) explicitly set it after this function returns, so the reset is invisible to the recovery bookkeeping contract and visible to every other forward-progress path. This keeps pipeline_policy.recovery.cycle_cap scoped to the current missing-plan recovery loop rather than inflating across successful forward transitions.

Parameters:
Return type:

PipelineState

ralph.pipeline.progress.apply_analysis_loopback(state, advanced_state, iteration_field, *, max_iterations, review_outcome=None)[source]

Apply canonical loopback bookkeeping for an analysis phase.

Parameters:
  • state (PipelineState)

  • advanced_state (PipelineState)

  • iteration_field (str)

  • max_iterations (int)

  • review_outcome (str | None)

Return type:

PipelineState

ralph.pipeline.progress.apply_analysis_success(state, advanced_state, *, policy=None)[source]

Reset inner-loop progress when analysis exits successfully to commit/approval.

Parameters:
  • state (PipelineState)

  • advanced_state (PipelineState)

  • policy (PipelinePolicy | None)

Return type:

PipelineState

ralph.pipeline.progress.apply_budget_counter_increment(state, advanced_state, counter)[source]

Increment a policy-declared budget counter when a lifecycle route completes.

Parameters:
  • state (PipelineState)

  • advanced_state (PipelineState)

  • counter (str | None)

Return type:

PipelineState

ralph.pipeline.progress.apply_commit_outcome(state, advanced_state, *, skipped, policy=None)[source]

Apply canonical outer-progress semantics for commit success vs skip.

Policy is required. Lifecycle-owned accounting takes precedence when the current phase is declared in pipeline.lifecycle_phases. Otherwise commit_policy drives the legacy fallback behavior.

Parameters:
  • state (PipelineState)

  • advanced_state (PipelineState)

  • skipped (bool)

  • policy (PipelinePolicy | None)

Return type:

PipelineState

ralph.pipeline.progress.derive_run_context_progress(state, run_context, policy=None)[source]

Derive checkpoint-facing progress mirrors from canonical pipeline state.

Resolves counter names by BFS through commit phases in the active policy; the first budget-tracked counter maps to actual_developer_runs, the second to actual_reviewer_runs. When policy is None, both fields are set to 0.

Parameters:
Return type:

RunContext

ralph.pipeline.progress.is_final_analysis_iteration(current_iteration, max_iterations)[source]

Return True when the current analysis state should be treated as final.

This intentionally matches the user-facing label semantics.

Parameters:
  • current_iteration (int)

  • max_iterations (int)

Return type:

bool

ralph.pipeline.progress.resolve_analysis_cap(iteration_field, policy)[source]

Resolve the effective analysis cap from live policy.

Parameters:
Return type:

int

ralph.pipeline.progress.review_issues_found(state, policy=None)[source]

Return True when the current review outcome indicates issues were found.

When policy is provided, checks the active review phase’s clean_outcome to determine whether the stored review_outcome represents an issues-found state. When policy is None or no review phase with clean_outcome is declared, falls back to checking whether review_outcome is non-None.

Parameters:
Return type:

bool

ralph.pipeline.progress.should_skip_analysis_reentry(current_iteration, max_iterations)[source]

Return True when the next attempt to enter analysis must be skipped.

current_iteration stores completed loopbacks, while the visible FINAL label is rendered for the current run. That means re-entry should skip only after the final labeled run has already happened.

Parameters:
  • current_iteration (int)

  • max_iterations (int)

Return type:

bool

ralph.pipeline.reducer

Pure reducer: (state, event, policy) -> (new_state, effects).

No I/O, no side effects, fully deterministic.

This module implements the core state machine for the Ralph pipeline. Given the current state, an event, and the loaded policy, it computes the new state and any effects to execute.

The reducer is a PURE FUNCTION — it contains no I/O operations, no logging, and no mutable state. This makes it fully deterministic and easy to test.

Routing is driven by the policy: phase transitions come from pipeline.toml, not hardcoded match arms. All workflow semantics are expressed in policy.

ralph.pipeline.reducer.reduce(state, event, pipeline_policy=None, recovery=None)[source]

Pure state transition function.

This is the core of the Ralph pipeline state machine. Given the current state, an event, and the pipeline policy, it computes the new state and any effects to execute.

Parameters:
  • state (PipelineState) – Current pipeline state.

  • event (Event) – Event to process.

  • pipeline_policy (PipelinePolicy | None) – Pipeline policy for resolving transitions. Required for all routing decisions. Passing None causes routing handlers to route to the policy-declared failure route rather than silently falling back to hardcoded behavior.

  • recovery (RecoveryController | None) – Optional RecoveryController. When supplied, PhaseFailureEvents and worker failure events are delegated to it for classification-aware recovery (intelligent attribution, budget management). When None, the legacy retry/fallback logic is used.

Returns:

Tuple of (new_state, effects). Effects are instructions for the effect handler to execute.

Return type:

tuple[PipelineState, list[Effect]]

ralph.pipeline.runner

Pipeline runner: orchestration glue that wires extracted submodules together.

This module coordinates effect dispatch, step execution, and policy resolution. Heavy lifting is delegated to focused submodules; runner.py owns only the plumbing that connects them.

class ralph.pipeline.runner.AgentRegistry(*, ccs_defaults=None, catalog=None)[source]

Bases: object

Registry of available AI agents.

The registry maintains a mapping of agent names to their configurations. It supports loading agents from UnifiedConfig and resolving agent names at runtime.

Parameters:
  • ccs_defaults (CcsConfig | None)

  • catalog (AgentCatalog | None)

agents

Dictionary mapping agent names to their configurations.

build_subagent_pid_registry(transport)[source]

Construct a per-invocation SubagentPidRegistry + SubagentPidSource.

R1 (Trustworthy Idle Watchdog spec): a single shared SubagentPidRegistry is created per invocation and threaded into both the execution strategy (via subagent_pid_source=) and the parser (via subagent_pid_registry=) so any PID registered by either layer becomes visible to ProcessMonitor.spawned_subagent_count().

The per-transport factory helpers in ralph.process.monitor._subagent_pid_source_providers wrap the shared registry to expose a SubagentPidSource that filters by transport source label. OpenCode’s ChildLivenessSubagentPidSource continues to use its own ChildLivenessRegistry (the registry is shared but the source adapter is transport-specific).

Returns:

A (registry, source) tuple. The registry is the single source of truth (FIFO-bounded at 1024 entries); the source is the per-transport adapter the watchdog consumes.

Parameters:

transport (AgentTransport | str)

Return type:

tuple[SubagentPidRegistry, SubagentPidSource]

property catalog: AgentCatalog

Return the AgentCatalog bound to this registry.

When no catalog is injected at construction time, the registry falls back to ralph.agents.catalog.default_catalog(). register_agent_support uses this property to write into the caller-owned catalog only, so a fresh AgentRegistry(catalog=AgentCatalog()) does not leak registrations into the global default catalog.

classmethod from_config(config)[source]

Create registry from UnifiedConfig.

Parameters:

config (UnifiedConfig) – Unified configuration containing agent definitions.

Returns:

Populated AgentRegistry instance.

Return type:

AgentRegistry

get(name)[source]

Get agent configuration by name.

Parameters:

name (str) – Agent name.

Returns:

AgentConfig if found, None otherwise.

Return type:

AgentConfig | None

get_command(name)[source]

Get the command for an agent.

Parameters:

name (str) – Agent name.

Returns:

Command string if agent found, None otherwise.

Return type:

str | None

list_agents()[source]

List all registered agent names.

Returns:

List of agent names.

Return type:

list[str]

register(name, config)[source]

Register an agent with the registry.

Parameters:
  • name (str) – Agent name.

  • config (AgentConfig) – Agent configuration.

Return type:

None

unregister(name)[source]

Unregister an agent from the registry and the bound catalog.

Parameters:

name (str) – Agent name.

Return type:

None

validate()[source]

Validate all registered agents.

Returns:

List of validation error messages (empty if all valid).

Return type:

list[str]

class ralph.pipeline.runner.DynamicBindingMcpServerFactory(workspace, *, reserve_port=None, start_server=<function start_mcp_server>, lifecycle_deps=None)[source]

Bases: McpServerFactory

Build MCP server handles with dynamically allocated localhost endpoints.

Parameters:
class ralph.pipeline.runner.McpSupervisor(bridge, *, check_interval=datetime.timedelta(seconds=2), on_restart=None, on_error=None)[source]

Bases: object

Background-thread supervisor for an active MCP server bridge.

Usage:

with McpSupervisor(bridge, on_restart=subscriber.record_mcp_restart):
    output = invoke_agent(...)
    stream_output(output)

The supervisor polls check_mcp_bridge_health(bridge) every check_interval seconds. Restarts are recorded via the optional on_restart callback. If the restart budget is exhausted, the stored McpServerError is re-raised when the context manager exits — taking priority over any agent-level error.

Parameters:
ralph.pipeline.runner.PendingPhaseTransitionMetadata

alias of _PendingPhaseTransitionMetadata

class ralph.pipeline.runner.SubprocessAgentExecutor(command, *, signal_bridge=None, cwd=None, extra_env=None, activity_router=None, raw_overflow_root=None, subagent_sink=None, _pm=None)[source]

Bases: object

AgentExecutor that spawns a subprocess in its own process group.

Uses ProcessManager.spawn_async with start_new_session=True so the child gets its own process group, enabling escalating tree-kill on cancellation. Success or failure is determined by the coordinator from empirical evidence (artifact submission, git changes) — never from this executor’s exit code.

Parameters:
  • command (Sequence[str])

  • signal_bridge (SignalBridge | None)

  • cwd (Path | None)

  • extra_env (Mapping[str, str] | None)

  • activity_router (ActivityRouter | None)

  • raw_overflow_root (Path | None)

  • subagent_sink (Callable[[str], None] | None)

  • _pm (ProcessManager | None)

drop_unit(unit_id)[source]

Release per-unit state so long parallel sessions don’t accumulate state across waves.

Removes the unit’s raw overflow log entry from self._raw_logs so the memory the log holds (up to DEFAULT_MAX_OVERFLOW_FILE_BYTES per unit) is released when the unit is no longer needed. Calls close() on the log first so any buffered tail bytes reach disk deterministically. Safe to call for a unit that was never added; it just no-ops.

Parameters:

unit_id (str)

Return type:

None

ralph.pipeline.runner.available_width(prefix_len)[source]

Return usable terminal width minus prefix and padding.

Parameters:

prefix_len (int)

Return type:

int

ralph.pipeline.runner.build_session_mcp_plan(*, transport, drain, workspace_path, agents_policy=None, model_opts=None, model_flag=None)[source]

Build the runtime MCP plan for a new agent session.

The result captures both session capability grants and any upstream MCP environment that must be present in the Ralph MCP subprocess so its runtime tool registry matches what the agent is expected to see.

Identity resolution precedence: 1. model_identity (explicit, if provided) 2. model_flag resolved via resolve_model_identity(transport, model_flag) 3. UNKNOWN_IDENTITY fallback

Parameters:
Return type:

SessionMcpPlan

ralph.pipeline.runner.check_mcp_bridge_health(bridge)[source]

Perform a health check on the MCP bridge, restarting if it crashed.

Only has an effect when bridge is a RestartAwareMcpBridge. Raises McpServerError when the restart budget is exhausted.

Parameters:

bridge (SessionBridgeLike)

Return type:

None

ralph.pipeline.runner.clear_cycle_baseline(workspace_root)[source]

Remove the baseline file so the next cycle starts fresh.

Parameters:

workspace_root (Path)

Return type:

None

ralph.pipeline.runner.commit_effect(workspace_root)[source]

Build a CommitEffect pointing at the standard commit message artifact path.

Parameters:

workspace_root (Path)

Return type:

CommitEffect

ralph.pipeline.runner.create_initial_state(config, *, agents_policy=None, pipeline_policy, counter_overrides=None)[source]

Create initial pipeline state from configuration.

Parameters:
Return type:

PipelineState

ralph.pipeline.runner.default_mcp_capabilities_for_phase(phase, *, agents_policy=None)[source]

Return the default MCP capability set for a given phase.

Parameters:
Return type:

set[str]

ralph.pipeline.runner.emit_final_summary(state, workspace_root, *, subscriber=None, display=None, display_context)[source]

Emit an end-of-run completion summary panel.

Parameters:
Return type:

None

ralph.pipeline.runner.emit_phase_transition_if_changed(display, previous_phase, state, *, verbosity, pipeline_policy)[source]

Emit phase-transition surfaces via the consolidated display surface.

Parameters:
Return type:

str

ralph.pipeline.runner.execute_agent_effect(effect, config, pipeline_deps, workspace_scope, *, bridge=None, raw_output_sink=None, rendered_output_sink=None, run_id=None, required_artifact=None, session_id=None, extra_env=None, raise_resumable_exit=False, agent_invocation_error_sink=None, **opts)[source]

Execute an agent-invocation effect end-to-end, including MCP server lifecycle.

Parameters:
Return type:

PipelineEvent

ralph.pipeline.runner.execute_commit_effect(effect, create_commit_fn, stage_all_fn, repo_root, display=None, **opts)[source]

Execute a commit effect while preserving runner-level dependency injection hooks.

Parameters:
  • effect (CommitEffect)

  • create_commit_fn (Callable[[Path | str, str], str])

  • stage_all_fn (Callable[[Path | str], None])

  • repo_root (Path)

  • display (ParallelDisplay | None)

  • opts (object)

Return type:

PipelineEvent

ralph.pipeline.runner.handle_phase(effect, ctx)[source]

Dispatch to the appropriate phase handler.

Parameters:
Returns:

List of events to emit to the reducer.

Raises:

PhaseHandlerNotFoundError – If no handler is registered for the phase.

Return type:

list[PipelineEvent | PhaseFailureEvent | WorkerStartedEvent | WorkerCompletedEvent | WorkerFailedEvent | PostFanoutVerificationEvent | AnalysisDecisionEvent]

ralph.pipeline.runner.heartbeat_policy_from_env(env=None)[source]

Return the configured MCP supervision check interval.

Parameters:

env (Mapping[str, str] | None)

Return type:

HeartbeatPolicy

ralph.pipeline.runner.install_signal_handlers(loop, root_task, bridge, controller=None)[source]

Register SIGINT handlers that cancel root_task and forward to child PIDs.

The fourth argument is type-broadened to accept an InterruptController (legacy) OR an InterruptDispatcher (new). Discrimination is by isinstance inside the body. When a controller is passed, the implementation synthesizes a dispatcher that forwards the controller’s kill_process_group and hard_exit so the controller’s injected exit callable is the one invoked on _second_sigint (PA-019).

The returned callable is an idempotent teardown that removes the second-SIGINT handler installed by the first handler. Calling it twice is safe (a short-circuit flag is stored in the closure).

Parameters:
Return type:

Callable[[], None] | None

ralph.pipeline.runner.install_width_refresher(ctx_holder, on_refresh=None)[source]

Install a width refresher using the best available strategy.

On POSIX main thread: uses SIGWINCH signal handler (install_sigwinch_refresher). On Windows or non-main thread: falls back to poll-based refresher (install_poll_refresher).

Parameters:
  • ctx_holder (list[DisplayContext]) – A single-element list whose 0th element is the DisplayContext to refresh on resize.

  • on_refresh (Callable[[DisplayContext], None] | None) – Optional callback invoked with the refreshed context after ctx_holder[0] is replaced.

Returns:

A stop() callable (for poll-based refresher; SIGWINCH handler has no cleanup).

Return type:

Callable[[], None]

ralph.pipeline.runner.make_display_context(*, env=None, console=None, force_width=None, force_glyphs=None)[source]

Create a DisplayContext with resolved terminal metrics and adaptive limits.

Parameters:
  • env (Mapping[str, str] | None) – Environment mapping (defaults to os.environ).

  • console (Console | None) – Console to use (defaults to make_console() with env-aware color policy).

  • force_width (int | None) – Override terminal width detection.

  • force_glyphs (bool | None) – Override glyph detection (True=Unicode, False=ASCII, None=auto-detect).

Returns:

Fully initialised DisplayContext.

Return type:

DisplayContext

ralph.pipeline.runner.materialize_prompt_for_phase(context=None, options=None, **kwargs)[source]

Render and persist the prompt for a pipeline phase, returning its dump path.

Parameters:
Return type:

str

ralph.pipeline.runner.materialize_system_prompt(*, workspace_root, name, default_current_prompt=None, worker_namespace=None)[source]

Write a system prompt file for the named agent and return its path.

Parameters:
  • workspace_root (Path)

  • name (str)

  • default_current_prompt (str | None)

  • worker_namespace (Path | None)

Return type:

str

ralph.pipeline.runner.phase_output_artifact_paths(phase, *, drain=None, policy_bundle=None)[source]

Return paths of all output artifacts produced by a phase.

Parameters:
  • phase (str)

  • drain (str | None)

  • policy_bundle (PolicyBundle | None)

Return type:

tuple[str, …]

ralph.pipeline.runner.prompt_session_drain_for_phase(drain, *, phase=None, pipeline_policy=None, agents_policy=None)

Return the prompt capability profile for a policy drain.

Parameters:
Return type:

SessionDrain

ralph.pipeline.runner.reducer_reduce(state, event, pipeline_policy=None, recovery=None)

Pure state transition function.

This is the core of the Ralph pipeline state machine. Given the current state, an event, and the pipeline policy, it computes the new state and any effects to execute.

Parameters:
  • state (PipelineState) – Current pipeline state.

  • event (Event) – Event to process.

  • pipeline_policy (PipelinePolicy | None) – Pipeline policy for resolving transitions. Required for all routing decisions. Passing None causes routing handlers to route to the policy-declared failure route rather than silently falling back to hardcoded behavior.

  • recovery (RecoveryController | None) – Optional RecoveryController. When supplied, PhaseFailureEvents and worker failure events are delegated to it for classification-aware recovery (intelligent attribution, budget management). When None, the legacy retry/fallback logic is used.

Returns:

Tuple of (new_state, effects). Effects are instructions for the effect handler to execute.

Return type:

tuple[PipelineState, list[Effect]]

ralph.pipeline.runner.register_role_handlers(policy)[source]

Register generic handlers for policy-declared role-based phases.

Called at policy-load time to ensure every phase with a recognized role has a handler registered, even if the phase name is not one of the canonical built-in names.

  • Execution-role phases are mapped to the generic handle_execution_phase.

  • Commit-role phases are mapped to the generic handle_commit_phase.

  • Analysis-role phases are mapped to the generic handle_generic_analysis_phase.

  • Review-role phases are mapped to the generic handle_review.

  • Verification-role phases are mapped to the generic handle_verification_phase.

Phases already registered via @register_handler are not overwritten.

Parameters:

policy (PipelinePolicy) – Loaded pipeline policy.

Return type:

None

ralph.pipeline.runner.resolve_display(display, display_context=None, *, is_quiet=False)[source]

Return the given display or construct one from the context.

Single source of truth that replaces the legacy resolve_display helper from ralph.pipeline.legacy_console_display. Pass-through for non-None inputs; constructs a ParallelDisplay from the supplied context when display is None. When is_quiet=True, the constructed display short-circuits all banner and log-line emissions (see ParallelDisplay quiet-mode contract).

Parameters:
Return type:

ParallelDisplay

ralph.pipeline.runner.resolve_workspace_scope(start=None)[source]

Resolve the active workspace scope.

The workspace root remains the active checkout, but linked worktrees inherit default .agent config from the main checkout unless the linked worktree has an explicit local override file.

Parameters:

start (Path | str | None)

Return type:

WorkspaceScope

async ralph.pipeline.runner.run_process_async(command, args=(), *, cwd=None, env=None, timeout=None, label=None, _pm=None)[source]

Run a process asynchronously and capture its output.

Parameters:
  • command (str)

  • args (Sequence[str])

  • cwd (str | Path | None)

  • env (Mapping[str, str] | None)

  • timeout (float | None)

  • label (str | None)

  • _pm (ProcessManager | None)

Return type:

ProcessResult

ralph.pipeline.runner.shutdown_mcp_server(bridge)[source]

Shutdown MCP server process.

Parameters:

bridge (SessionBridgeLike)

Return type:

None

ralph.pipeline.runner.start_mcp_server(session, workspace, *, upstream_registry=None, deps=None, extras=None)[source]

Start a standalone Ralph MCP HTTP subprocess and verify tool reachability.

Returns a RestartAwareMcpBridge that can auto-restart the server on crash up to the extras.restart_policy budget (default: 20 restarts, defined by McpRestartPolicy).

Parameters:
Return type:

RestartAwareMcpBridge

ralph.pipeline.activity_stream

Activity stream rendering and artifact handoff for the pipeline runner.

ralph.pipeline.activity_stream.render_phase_artifact_handoff(phase, event, workspace_root, display, ctx=None)[source]

Render the artifact handoff panel after a phase completes.

Parameters:
  • phase (str)

  • event (Event)

  • workspace_root (Path)

  • display (ParallelDisplay | None)

  • ctx (ArtifactHandoffContext | None)

Return type:

None

ralph.pipeline.activity_stream.stream_parsed_agent_activity(lines, parser_type, agent_name, display=None, *, agent_config=None, **kwargs)[source]

Stream and render parsed agent output lines.

Accepts and forwards the per-invocation subagent_pid_registry= and subagent_source_label= kwargs into the resolved parser so the parser’s structured-event hook registers any embedded PID into the shared registry (R1 / R5 of the Trustworthy Idle Watchdog spec). Both kwargs are optional; legacy callers continue to work without them.

Parameters:
  • lines (Iterable[object])

  • parser_type (str)

  • agent_name (str)

  • display (ParallelDisplay | None)

  • agent_config (AgentConfig | None)

  • kwargs (object)

Return type:

None

ralph.pipeline.effect_executor

Agent and commit effect execution for the pipeline runner.

ralph.pipeline.effect_executor.pop_last_captured_retry_intent()[source]

Return and clear the most recent canonical next-attempt retry intent.

Return type:

AgentRetryIntent

ralph.pipeline.effect_executor.stage_files(repo_root, files)[source]

Stage only the provided repository-relative paths.

Uses git add --all -- <paths> so modified, untracked, and deleted files are all handled consistently for the selected scope.

Parameters:
  • repo_root (Path | str)

  • files (list[str])

Return type:

None

ralph.pipeline.effect_router

Effect routing: determine which effect to apply given the current pipeline state.

ralph.pipeline.effect_router.determine_effect_from_policy(state, policy_bundle, workspace_scope=None, *, config=None)[source]

Select the next pipeline effect based on current state and policy.

Parameters:
Return type:

Effect

ralph.pipeline.fan_out

Fan-out parallel execution for the pipeline runner.

Dormant since the parallelization rework; not invoked by the effect router when dispatch_mode='agent_subagents' (the bundled default). Retained for future use. Re-arm by setting [phases.<phase>.parallelization] dispatch_mode = 'ralph_fan_out' on the relevant phase in pipeline.toml.

ralph.pipeline.fan_out.execute_fan_out_sync(*, effect, state, display, pipeline_deps=None, **opts)[source]

Execute fan-out development synchronously by wrapping asyncio.run().

Parameters:
Return type:

PipelineState

ralph.pipeline.fan_out.write_parallel_development_summary(workspace_scope, effect, state, verification=None)[source]

Write .agent/artifacts/parallel_development_summary.json after fan-out completes.

Parameters:
Return type:

None

ralph.pipeline.phase_agent_handler

Phase artifact rendering and post-agent-run event handling.

ralph.pipeline.phase_rendering

Verbosity ranking and normalization helpers for the pipeline runner.

ralph.pipeline.phase_rendering.normalize_verbosity(value)[source]

Coerce a Verbosity enum, integer rank, or None into a Verbosity value.

The legacy GeneralConfig.verbosity field is an integer (0-4); the new CLI surface is the Verbosity StrEnum. This helper accepts either and falls back to Verbosity.VERBOSE for unknown / unset inputs.

Parameters:

value (Verbosity | int | None)

Return type:

Verbosity

ralph.pipeline.phase_rendering.verbosity_rank(verbosity)[source]

Return a numeric rank for a Verbosity enum value (QUIET=0 .. DEBUG=4).

Parameters:

verbosity (Verbosity)

Return type:

int

ralph.pipeline.phase_transition

Phase transition display logic for the pipeline runner.

ralph.pipeline.phase_transition.PendingPhaseTransitionMetadata

alias of _PendingPhaseTransitionMetadata

ralph.pipeline.phase_transition.build_phase_entry_model_from_state(phase, state, pipeline_policy, *, agent_name=None)

Build the canonical phase-entry model from pipeline state.

Parameters:
  • phase (str)

  • state (PipelineState)

  • pipeline_policy (PipelinePolicy)

  • agent_name (str | None)

Return type:

PhaseEntryModel

ralph.pipeline.phase_transition.clear_phase_materialization_outputs(workspace, phase)

Remove stale prompt-materialization outputs for a phase when it is skipped.

Parameters:
  • workspace (FsWorkspace)

  • phase (str)

Return type:

None

ralph.pipeline.phase_transition.emit_final_summary(state, workspace_root, *, subscriber=None, display=None, display_context)[source]

Emit an end-of-run completion summary panel.

Parameters:
Return type:

None

ralph.pipeline.phase_transition.emit_phase_transition_if_changed(display, previous_phase, state, *, verbosity, pipeline_policy)

Emit the canonical close+transition display when the phase changes.

Returns the new previous_phase value (always state.phase). Quiet mode is a no-op except for state tracking.

Parameters:
Return type:

str

ralph.pipeline.phase_transition.find_commit_counter_from_phase(phase_name, policy)

Trace on_success transitions to the nearest lifecycle or commit counter owner.

Returns the lifecycle-owned counter name when the phase graph declares one, otherwise falls back to the nearest commit phase increments_counter.

Parameters:
Return type:

str | None

ralph.pipeline.phase_transition.show_phase_start_with_context(phase, agent_name, display_context, state, *, pipeline_policy)

Display the canonical model-based phase-start banner for the live runner.

Parameters:
Return type:

None

ralph.pipeline.prompt_prep

Prompt materialization helpers for the pipeline runner.

ralph.pipeline.prompt_prep.prompt_session_drain_for_phase(drain, *, phase=None, pipeline_policy=None, agents_policy=None)

Return the prompt capability profile for a policy drain.

Parameters:
Return type:

SessionDrain

ralph.pipeline.prompt_prep.session_capabilities_for_agent_phase(drain, *, phase=None, pipeline_policy=None, agent=None, agents_policy=None)[source]

Return prompt session capabilities with the effective transport tool prefix.

Parameters:
Return type:

SessionCapabilities

ralph.pipeline.factory

Pipeline dependency bundle and factory.

This module is the single composition point for the five PROMPT-mandated collaborators (display, model identity, system/phase prompt materializers, artifact resolver), the bridge factory, and all seven ProPipelineHooks overrides. Both the main pipeline (via run_loop.run) and plumbing commands compose from PipelineCore so they share the same underlying collaborators.

class ralph.pipeline.factory.ArtifactRequirementsResolverFn(*args, **kwargs)[source]

Bases: Protocol

Resolve the required artifact contract for a phase/drain.

class ralph.pipeline.factory.CheckMcpBridgeHealthFn(*args, **kwargs)[source]

Bases: Protocol

Check that an MCP bridge is healthy, raising on failure.

class ralph.pipeline.factory.DefaultPipelineFactory[source]

Bases: object

Default composition root for the main pipeline and plumbing commands.

This thin, stateless factory implements PipelineFactory by delegating to build_default_pipeline_deps(). Because the extended call sites (main pipeline, parallel worker runtime) need to inject model_identity and policy_bundle, the build() method accepts those optional kwargs in addition to the Protocol surface.

build(config, display_context, *, model_identity=None, policy_bundle=None, recovery_sleep=None, pro_hooks=None)[source]

Build a PipelineDeps wired to production defaults.

Parameters:
Return type:

PipelineDeps

class ralph.pipeline.factory.HeartbeatPolicyFromEnvFn(*args, **kwargs)[source]

Bases: Protocol

Return the heartbeat policy read from the environment.

class ralph.pipeline.factory.MaterializeSystemPromptFn(*args, **kwargs)[source]

Bases: Protocol

Materialize a system prompt file and return its path.

Matches the wrapper in ralph._session_runtime_deps and the public ralph.prompts.system_prompt.materialize_system_prompt surface.

class ralph.pipeline.factory.McpSupervisorFactoryFn(*args, **kwargs)[source]

Bases: Protocol

Construct a context manager that supervises an MCP bridge during invocation.

class ralph.pipeline.factory.PhasePromptMaterializerFn(*args, **kwargs)[source]

Bases: Protocol

Materialize a phase prompt and return its dump path.

class ralph.pipeline.factory.PipelineCore(display_context, model_identity=None, system_prompt_materializer=<function _materialize_system_prompt>, phase_prompt_materializer=<function _materialize_prompt_for_phase>, artifact_requirements_resolver=<function _resolve_phase_required_artifact>)[source]

Bases: object

The five PROMPT-mandated pipeline collaborators.

This is the lean, modular surface shared by the main pipeline and plumbing commands. It contains exactly the collaborators that can be injected by Pro via ralph.pro_support.hooks.ProPipelineHooks:

  • display_context — display/rendering context.

  • model_identity — resolved multimodal model identity.

  • system_prompt_materializer — system-prompt materializer.

  • phase_prompt_materializer — phase-prompt materializer.

  • artifact_requirements_resolver — phase/drain artifact resolver.

The bridge factory is intentionally NOT part of PipelineCore; it is a plumbing-only concern and is supplied separately to plumbing call sites.

Parameters:
class ralph.pipeline.factory.PipelineDeps(*, core=None, display_context=<object object>, model_identity=<object object>, system_prompt_materializer=<object object>, phase_prompt_materializer=<object object>, artifact_requirements_resolver=<object object>, registry_factory=None, bridge_factory=<function build_session_bridge>, mcp_supervisor_factory=<function _mcp_supervisor_factory>, heartbeat_policy_from_env_fn=<function _heartbeat_policy_from_env>, check_mcp_bridge_health_fn=<function _check_mcp_bridge_health>, policy_bundle=None, policy_bundle_factory=None, state_factory=None, recovery_controller_factory=None, marker_watcher_factory=None, snapshot_registry=None, recovery_sleep=None, connectivity_state_provider=None, is_waiting_state_provider=None, process_teardown=None)[source]

Bases: object

Injectable dependency bundle for the pipeline and plumbing commands.

Fields cover the five PROMPT-mandated collaborators (bundled in core), the bridge factory, MCP lifecycle machinery, the seven ProPipelineHooks overrides, and the recovery sleep seam.

For backward compatibility, the four collaborators may still be passed directly to __init__; they are composed into the embedded PipelineCore. Callers that already have a PipelineCore should pass core=... instead.

Parameters:
property artifact_requirements_resolver: ArtifactRequirementsResolverFn

Backward-compatible accessor for core.artifact_requirements_resolver.

property display_context: DisplayContext

Backward-compatible accessor for core.display_context.

property model_identity: MultimodalModelIdentity | None

Backward-compatible accessor for core.model_identity.

property phase_prompt_materializer: PhasePromptMaterializerFn

Backward-compatible accessor for core.phase_prompt_materializer.

property system_prompt_materializer: MaterializeSystemPromptFn

Backward-compatible accessor for core.system_prompt_materializer.

class ralph.pipeline.factory.PipelineFactory(*args, **kwargs)[source]

Bases: Protocol

Factory that builds a PipelineDeps for a given config.

ralph.pipeline.factory.apply_pro_hooks_to_deps(deps, pro_hooks, config)[source]

Return a new PipelineDeps with ProPipelineHooks overrides applied.

Parameters:
Return type:

PipelineDeps

ralph.pipeline.factory.build_default_pipeline_deps(config, display_context, *, model_identity=None, policy_bundle=None, recovery_sleep=None, pro_hooks=None)[source]

Build a PipelineDeps wired to production defaults.

model_identity defaults to None so callers that do not have a resolved identity reproduce the pre-refactor UNKNOWN_IDENTITY behavior; callers that already know the effective identity (e.g. plumbing commands with a single selected agent) can inject it here.

policy_bundle lets the main pipeline load the policy once and inject it into the shared bundle instead of passing it as a separate runner argument.

recovery_sleep lets callers replace the wall-clock sleep used during recovery backoff; pro_hooks.recovery_sleep takes precedence over this argument when both are provided.

Parameters:
Return type:

PipelineDeps

ralph.pipeline.factory.build_minimal_pipeline_core(config, display_context, *, model_identity=None)[source]

Build the shared 4-collaborator PipelineCore.

This is the lean composition root used by both plumbing commands and build_default_pipeline_deps. It does NOT accept pro_hooks, policy_bundle, recovery_sleep, or any other extended field; callers that need the extended bundle should use build_default_pipeline_deps().

Parameters:
Return type:

PipelineCore

ralph.pipeline.run_loop

Pipeline event loop: the run() entry point and connectivity helpers.

ralph.pipeline.run_loop.run(config, initial_state=None, display=None, pipeline_subscriber=None, *, dashboard_subscriber=None, verbosity=None, connectivity_monitor=None, display_context=None, counter_overrides=None, config_path=None, cli_overrides=None, _recovery_sleep=None, pro_hooks=None, policy_bundle_factory=None, registry_factory=None, state_factory=None, recovery_controller_factory=None, marker_watcher_factory=None, snapshot_registry=None, pipeline_deps=None)[source]

Execute the pipeline event loop.

Parameters:
  • config (UnifiedConfig) – Unified configuration for the pipeline.

  • initial_state (PipelineState | None) – Optional initial state (for resume from checkpoint).

  • display (ParallelDisplay | None) – Optional pre-built display. When omitted, a ParallelDisplay is constructed by default unless verbosity is QUIET.

  • pipeline_subscriber (_PipelineSubscriberProtocol | None) – Optional subscriber that will receive notify(state) calls after each reduce.

  • verbosity (Verbosity | None) – Optional explicit verbosity. Defaults to the configured value in config.general.verbosity (mapped from int rank).

  • pro_hooks (ProPipelineHooks | None) – Optional ProPipelineHooks carrying Pro overrides. Ignored when pipeline_deps is provided; prefer passing pro_hooks to build_default_pipeline_deps() instead.

  • pipeline_deps (PipelineDeps | None) – Optional PipelineDeps carrying injected collaborators. This is the single authoritative injection surface for the run loop. When provided, its values take precedence over pro_hooks and production defaults.

  • policy_bundle_factory (Callable[[WorkspaceScope, UnifiedConfig], PolicyBundle] | None) – (DEPRECATED) Use pipeline_deps.

  • registry_factory (Callable[[UnifiedConfig], _RegistryLike] | None) – (DEPRECATED) Use pipeline_deps.

  • state_factory (Callable[[UnifiedConfig, AgentsPolicy, PipelinePolicy, dict[str, int] | None], PipelineState] | None) – (DEPRECATED) Use pipeline_deps.

  • recovery_controller_factory (Callable[[PipelineState, PolicyBundle, UnifiedConfig], tuple[RecoveryController, int]] | None) – (DEPRECATED) Use pipeline_deps.

  • marker_watcher_factory (Callable[[Path], ProMarkerWatcher] | None) – (DEPRECATED) Use pipeline_deps.

  • snapshot_registry (SnapshotRegistry | None) – (DEPRECATED) Use pipeline_deps.

  • _recovery_sleep (Callable[[float], None] | None) – (DEPRECATED) Use pipeline_deps.recovery_sleep (or pass recovery_sleep to build_default_pipeline_deps()).

  • dashboard_subscriber (_PipelineSubscriberProtocol | None)

  • connectivity_monitor (_ConnectivityMonitorLike | None)

  • display_context (DisplayContext | None)

  • counter_overrides (dict[str, int] | None)

  • config_path (Path | None)

  • cli_overrides (dict[str, object] | None)

Return type:

int

Migration Notes:

run() previously accepted individual factory kwargs such as policy_bundle_factory, registry_factory, etc. These are now deprecated. Construct a PipelineDeps bundle via build_default_pipeline_deps() (optionally passing pro_hooks or recovery_sleep to it) and pass only pipeline_deps. Passing any deprecated factory kwarg alongside pipeline_deps raises ValueError. Callers using only the old factory kwargs (without pipeline_deps) continue to work for backward compatibility.

Returns:

Exit code (0 for success, non-zero for failure).

Parameters:
Return type:

int

ralph.pipeline.state_init

Initial pipeline state creation.

ralph.pipeline.state_init.create_initial_state(config, *, agents_policy=None, pipeline_policy, counter_overrides=None)[source]

Create initial pipeline state from configuration.

Parameters:
Return type:

PipelineState

ralph.pipeline.waiting_dispatch

Dispatch waiting status events to pipeline subscribers.

ralph.pipeline.waiting_dispatch.dispatch_waiting_event(event, *, subscriber, unit_id, agent_name)[source]

Dispatch a WaitingStatusEvent to the subscriber.

Exposed as a free function so tests can exercise it without a full pipeline.

Parameters:
Return type:

None

ralph.pipeline.agent_retry_decision

Shared retry-decision core for failed agent invocations.

There is exactly ONE place where both the pipeline executor (_invoke_agent_with_recovery) and the direct-MCP recovery loop (run_with_direct_mcp_recovery) decide whether a failed attempt is retryable and what the canonical next-attempt intent is. Routing both callers through resolve_retry_intent makes the retry semantics impossible to drift apart.

drift-audit: This module owns the recovery-decision pipeline seam — the ONLY FailureClassifier( site in ralph/pipeline/. The 8-file allowlist is INVARIANT (8 files; 5 actual sites). When extending the recovery decision surface, do NOT add a 6th FailureClassifier( site here — the seam is already a single owner. New callers MUST route through should_reset_tool_registry(…) (ralph/recovery/failure_classifier.py) for classification, and through resolve_retry_intent(…) (this module) for the canonical next-attempt intent. PA-003 procedure: pin counts are invariant — do not raise them.

ralph.pipeline.agent_retry_decision.resolve_retry_intent(exc, *, phase, agent, session_id, inactivity_error_type)[source]

Return the canonical retry intent for a failed attempt, or None.

None means the failure is not retryable. Otherwise the returned intent is the single source of truth for the next attempt’s session action and tool-registry reset. session_id is the caller-resolved observed session id (the intent clears it when the failure semantics demand a fresh session).

Parameters:
  • exc (Exception)

  • phase (str)

  • agent (str | None)

  • session_id (str | None)

  • inactivity_error_type (type[Exception])

Return type:

AgentRetryIntent | None

ralph.pipeline.agent_retry_intent

Canonical retry/session intent for the next agent attempt.

class ralph.pipeline.agent_retry_intent.AgentRetryIntent(*, action=None, session_id=None, reset_tool_registry=False, failure_reason='', skip_same_agent_retries=False)[source]

Bases: BaseModel

Single source of truth for the next-attempt session action.

Parameters:
  • action (Literal['fresh', 'resume', 'new_session_with_id'] | None)

  • session_id (str | None)

  • reset_tool_registry (bool)

  • failure_reason (str)

  • skip_same_agent_retries (bool)

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

ralph.pipeline.agent_retry_intent.agent_retry_intent_for_failure(*, failure_reason, session_id, reset_tool_registry)[source]

Build the canonical next-attempt action from failure semantics.

Parameters:
  • failure_reason (str)

  • session_id (str | None)

  • reset_tool_registry (bool)

Return type:

AgentRetryIntent

ralph.pipeline.agent_retry_intent.cleared_agent_retry_intent()[source]

Return the empty intent used to clear next-attempt session state.

Return type:

AgentRetryIntent

ralph.pipeline.agent_retry_intent.resume_agent_retry_intent(session_id, *, failure_reason='', reset_tool_registry=False)[source]

Build a resume intent that reuses an existing agent session id.

Parameters:
  • session_id (str)

  • failure_reason (str)

  • reset_tool_registry (bool)

Return type:

AgentRetryIntent

ralph.pipeline.retryable_failure

Shared retryable agent-failure classification helpers.

ralph.pipeline.retryable_failure.retryable_agent_failure_reason(exc, inactivity_error_type)[source]

Return the canonical retry reason for a retryable agent failure.

Parameters:
  • exc (Exception)

  • inactivity_error_type (type[Exception])

Return type:

str | None

ralph.pipeline.state

Immutable pipeline state model.

This module defines PipelineState - the single source of truth for pipeline execution progress. It serves dual purposes: 1. Runtime State: Tracks current phase, iteration counters, agent chain state 2. Checkpoint Payload: Serializes to JSON for resume functionality

PipelineState is IMMUTABLE from the reducer’s perspective. State transitions occur exclusively through the reduce function.

POLICY-DRIVEN STATE TRACKING

Loop counters (loop_iterations) and phase chains (phase_chains) are keyed by policy-declared names, not hardcoded field names. This enables custom workflows with arbitrary phase and counter names to work without modifying source code.

Budget counters (budget_caps / outer_progress) track the cap and completed cycles for each policy-declared budget counter. Remaining budget is always derived: remaining = max(0, cap - progress).

Legacy checkpoint fields (budget fields only) are migrated to the generic dicts at deserialise time via the _migrate_legacy_state_fields model_validator.

class ralph.pipeline.state.AgentChainState(*, agents=<factory>, current_index=0, retries=0)[source]

Bases: BaseModel

State for agent fallback chain management.

Parameters:
  • agents (list[str])

  • current_index (int)

  • retries (int)

agents

List of agent names in the fallback chain.

Type:

list[str]

current_index

Current agent index being used.

Type:

int

retries

Number of retries for current agent.

Type:

int

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

with_advance()[source]

Return a copy advanced to the next agent with retries reset to 0.

Return type:

AgentChainState

with_retry_increment()[source]

Return a copy with retries incremented by 1.

Return type:

AgentChainState

class ralph.pipeline.state.CommitState(*, message_prepared=False, diff_prepared=False, agent_invoked=False)[source]

Bases: BaseModel

State for commit operations.

Parameters:
  • message_prepared (bool)

  • diff_prepared (bool)

  • agent_invoked (bool)

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.pipeline.state.FalloverRecord(*, phase, from_agent, to_agent, timestamp_iso)[source]

Bases: BaseModel

A record of a single agent fallover event persisted in pipeline state.

Parameters:
  • phase (str)

  • from_agent (str)

  • to_agent (str)

  • timestamp_iso (str)

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.pipeline.state.PipelineState(*, phase='__unset__', previous_phase=None, review_outcome=None, phase_chains=<factory>, loop_iterations=<factory>, budget_caps=<factory>, outer_progress=<factory>, rebase=<factory>, commit=<factory>, metrics=<factory>, checkpoint_saved_count=0, recovery_epoch=0, interrupted_by_user=False, git_auth_configured=False, pr_created=False, pr_url=None, push_count=0, last_error=None, last_reviewed_sha=None, policy_entry_phase='__unset__', policy_format_version=None, current_drain=None, work_units=<factory>, worker_states=<factory>, recovery_cycle_count=0, fallover_history=<factory>, last_failure_category=None, last_connectivity_state='unknown', recovery_cycle_cap=200, last_retry_delay_ms=0, last_agent_session_id=None, agent_retry_intent=<factory>, last_unavailability_reason=None, is_waiting_state=False)[source]

Bases: _FrozenPipelineStateModel

Immutable snapshot of pipeline execution state.

This is the checkpoint payload - the single source of truth for pipeline progress. Serialize it to JSON to save state; deserialize to resume interrupted runs.

GENERIC TRACKING FIELDS (policy-keyed):

phase_chains: Per-phase agent chain state keyed by canonical phase name. loop_iterations: Loop iteration counters keyed by iteration_state_field name. budget_caps: Max budget keyed by budget counter name (seeded from policy). outer_progress: Completed cycle counts keyed by budget counter name. Remaining budget is derived on-demand: max(0, cap - progress).

Parameters:
  • phase (str)

  • previous_phase (str | None)

  • review_outcome (str | None)

  • phase_chains (dict[str, AgentChainState])

  • loop_iterations (dict[str, int])

  • budget_caps (dict[str, int])

  • outer_progress (dict[str, int])

  • rebase (RebaseState)

  • commit (CommitState)

  • metrics (RunMetrics)

  • checkpoint_saved_count (int)

  • recovery_epoch (int)

  • interrupted_by_user (bool)

  • git_auth_configured (bool)

  • pr_created (bool)

  • pr_url (str | None)

  • push_count (int)

  • last_error (str | None)

  • last_reviewed_sha (str | None)

  • policy_entry_phase (str)

  • policy_format_version (int | None)

  • current_drain (str | None)

  • work_units (tuple[WorkUnit, ...])

  • worker_states (dict[str, WorkerState])

  • recovery_cycle_count (int)

  • fallover_history (tuple[FalloverRecord, ...])

  • last_failure_category (str | None)

  • last_connectivity_state (str)

  • recovery_cycle_cap (Annotated[int, Ge(ge=1)])

  • last_retry_delay_ms (int)

  • last_agent_session_id (str | None)

  • agent_retry_intent (AgentRetryIntent)

  • last_unavailability_reason (str | None)

  • is_waiting_state (bool)

advance_agent()[source]

Advance to the next agent in the fallback chain.

Return type:

PipelineState

chain_for_phase(phase)[source]

Get the tracked agent chain state for a phase, if any.

Parameters:

phase (str)

Return type:

AgentChainState | None

copy_with(**updates)[source]

Return a copy with updates applied in a typed-safe manner.

Parameters:

updates (object)

Return type:

PipelineState

current_agent()[source]

Get the current agent for the active phase.

Return type:

str | None

classmethod from_policy(policy, **overrides)[source]

Construct initial pipeline state from a loaded PipelinePolicy.

The entry phase is derived from policy.entry_phase so no workflow entry semantics are embedded in this class.

Parameters:
Return type:

PipelineState

get_budget_cap(counter_name)[source]

Get the budget cap for a policy-declared budget counter.

Parameters:

counter_name (str) – The budget counter name.

Returns:

Budget cap (maximum allowed), or 0 if not set.

Return type:

int

get_budget_remaining(counter_name)[source]

Get the remaining budget for a policy-declared budget counter.

Parameters:

counter_name (str) – The budget counter name from PhaseCommitPolicy.increments_counter.

Returns:

Remaining budget count, derived as max(0, cap - completed).

Return type:

int

get_loop_iteration(field_name)[source]

Get the loop iteration counter for a policy-declared iteration field.

Parameters:

field_name (str) – The iteration_state_field value from PhaseLoopPolicy.

Returns:

Current iteration count (0 when not yet set).

Return type:

int

get_outer_progress(counter_name)[source]

Get the completed cycle count for a policy-declared budget counter.

Parameters:

counter_name (str)

Return type:

int

is_complete(policy)[source]

Check if pipeline has reached a terminal success state.

Parameters:

policy (PipelinePolicy) – PipelinePolicy. Compares current phase against policy.terminal_phase to determine completion.

Raises:

RuntimeError – When policy is None (routing requires loaded policy).

Return type:

bool

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

remaining_retries()[source]

Calculate remaining retries for current agent.

Return type:

int

with_budget_cap(counter_name, value)[source]

Return a copy with the specified budget cap set to value.

Parameters:
  • counter_name (str)

  • value (int)

Return type:

PipelineState

with_drain(drain)[source]

Return a copy with the current_drain set.

Parameters:

drain (DrainName | None)

Return type:

PipelineState

with_fallover_record(record)[source]

Return a copy with one additional fallover record, trimmed to cycle cap.

Parameters:

record (FalloverRecord)

Return type:

PipelineState

with_loop_iteration(field_name, value)[source]

Return a copy with the specified loop iteration field set to value.

Parameters:
  • field_name (str) – The iteration_state_field value from PhaseLoopPolicy.

  • value (int) – New iteration count.

Returns:

New PipelineState with the iteration counter updated.

Return type:

PipelineState

with_outer_progress(counter_name, value)[source]

Return a copy with the specified outer progress counter set to value.

Parameters:
  • counter_name (str)

  • value (int)

Return type:

PipelineState

with_parallel_execution_cleared()[source]

Return a copy with completed fan-out tracking removed.

This is the single sanctioned seam for ending a parallel wave’s lifecycle. copy_with deliberately refuses to mutate non-empty work_units so mid-wave state cannot be lost by accident; once a wave has fully succeeded the tracking state must be dropped here or it would poison routing of the next (non-parallelized) phase.

Return type:

PipelineState

with_phase_chain(phase, chain)[source]

Return a copy with the chain state for the given phase updated.

Parameters:
Return type:

PipelineState

class ralph.pipeline.state.RebaseState(*, pending=False, in_progress=False, completed=False)[source]

Bases: BaseModel

State for git rebase operations.

Parameters:
  • pending (bool)

  • in_progress (bool)

  • completed (bool)

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.pipeline.state.RunMetrics(*, total_agent_calls=0, total_continuations=0, total_fallbacks=0, total_retries=0)[source]

Bases: BaseModel

Run-level execution metrics.

Parameters:
  • total_agent_calls (int)

  • total_continuations (int)

  • total_fallbacks (int)

  • total_retries (int)

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

with_continuation_increment()[source]

Return a copy with total_continuations incremented by 1.

Return type:

RunMetrics

with_fallback_increment()[source]

Return a copy with total_fallbacks incremented by 1.

Return type:

RunMetrics

with_retry_increment()[source]

Return a copy with total_retries incremented by 1.

Return type:

RunMetrics

ralph.pipeline.work_units

Planning work_units parsing and validation.

This module provides a typed parser for work_units[] declared in planning artifacts. It intentionally focuses on schema and graph validation; execution fanout remains orchestrator-owned.

class ralph.pipeline.work_units.WorkUnit(*, unit_id, description, allowed_directories=<factory>, dependencies=<factory>)[source]

Bases: BaseModel

Single planning work unit declaration.

Parameters:
  • unit_id (Annotated[str, MinLen(min_length=1)])

  • description (Annotated[str, MinLen(min_length=1), MaxLen(max_length=4096)])

  • allowed_directories (list[str])

  • dependencies (list[str])

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.pipeline.work_units.WorkUnitsPlan(*, work_units=<factory>)[source]

Bases: BaseModel

Typed representation of work_units[] in planning artifacts.

Parameters:

work_units (list[WorkUnit])

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

exception ralph.pipeline.work_units.WorkUnitsValidationError[source]

Bases: ValueError

Raised when a planning artifact contains invalid work_units.

ralph.pipeline.work_units.parse_work_units_from_artifact(artifact)[source]

Parse and validate work_units[] from a planning artifact payload.

Returns None when the artifact does not declare work_units.

Parameters:

artifact (Mapping[str, object])

Return type:

WorkUnitsPlan | None

ralph.pipeline.work_units.validate_for_same_workspace(plan)[source]

Validate that a plan is safe for same-workspace parallel execution.

Enforces rules that apply specifically when workers share the same checkout: - Every unit must declare at least one allowed_directory. - No unit may declare a reserved path (.agent, .git, .worktrees, ., “”). - No two units may have overlapping edit areas (prefix-overlap by path segments).

Raises:

WorkUnitsValidationError – with a human-readable message naming the problematic units/paths and suggesting a fix.

Parameters:

plan (WorkUnitsPlan)

Return type:

None

ralph.pipeline.worker_state

Worker execution state model for parallel pipeline workers.

class ralph.pipeline.worker_state.WorkerState(*, unit_id, status=WorkerStatus.PENDING, started_at=None, finished_at=None, exit_code=None, error_message=None, worker_namespace=None, log_file=None)[source]

Bases: BaseModel

Immutable snapshot of a single parallel worker’s execution state.

Parameters:
  • unit_id (Annotated[str, MinLen(min_length=1)])

  • status (WorkerStatus)

  • started_at (datetime | None)

  • finished_at (datetime | None)

  • exit_code (int | None)

  • error_message (str | None)

  • worker_namespace (str | None)

  • log_file (str | None)

unit_id

Identifier of the work unit this worker is executing.

Type:

str

status

Current execution status.

Type:

ralph.pipeline.worker_status.WorkerStatus

started_at

When the worker started execution.

Type:

datetime.datetime | None

finished_at

When the worker finished execution.

Type:

datetime.datetime | None

exit_code

Process exit code, if finished.

Type:

int | None

error_message

Human-readable error description, if failed.

Type:

str | None

worker_namespace

Filesystem path to the worker’s per-worker namespace under .agent/workers/<unit_id>/ in the shared checkout.

Type:

str | None

log_file

Path to the worker’s log file.

Type:

str | None

copy_with(**updates)[source]

Return a copy with the given fields replaced.

Parameters:

updates (object)

Return type:

WorkerState

model_config = {'extra': 'ignore', 'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.pipeline.worker_state.WorkerStatus(*values)[source]

Bases: StrEnum

Execution status of a single parallel worker.

ralph.pipeline.exhausted_analysis_bypass_result

Exhausted-analysis bypass result model.

class ralph.pipeline.exhausted_analysis_bypass_result.ExhaustedAnalysisBypassResult(state, target_phase, skipped=())[source]

Bases: object

Resolved exhausted-analysis bypass outcome for a phase handoff.

Parameters:

ralph.pipeline.exhausted_analysis_skip

Exhausted-analysis skip details.

class ralph.pipeline.exhausted_analysis_skip.ExhaustedAnalysisSkip(phase, target_phase, iteration_field, iteration_value, max_iterations)[source]

Bases: object

Details for a single exhausted analysis phase that was bypassed.

Parameters:

ralph.pipeline.frozen_work_unit_model

Shared frozen base model for work unit models.

class ralph.pipeline.frozen_work_unit_model.FrozenWorkUnitModel[source]

Bases: BaseModel

Shared base for frozen work unit models.

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

ralph.pipeline.state_models

Frozen sub-models used by the immutable pipeline state model.

class ralph.pipeline.state_models.AgentChainState(*, agents=<factory>, current_index=0, retries=0)[source]

Bases: BaseModel

State for agent fallback chain management.

Parameters:
  • agents (list[str])

  • current_index (int)

  • retries (int)

agents

List of agent names in the fallback chain.

Type:

list[str]

current_index

Current agent index being used.

Type:

int

retries

Number of retries for current agent.

Type:

int

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

with_advance()[source]

Return a copy advanced to the next agent with retries reset to 0.

Return type:

AgentChainState

with_retry_increment()[source]

Return a copy with retries incremented by 1.

Return type:

AgentChainState

class ralph.pipeline.state_models.AgentRetryIntent(*, action=None, session_id=None, reset_tool_registry=False, failure_reason='', skip_same_agent_retries=False)[source]

Bases: BaseModel

Single source of truth for the next-attempt session action.

Parameters:
  • action (Literal['fresh', 'resume', 'new_session_with_id'] | None)

  • session_id (str | None)

  • reset_tool_registry (bool)

  • failure_reason (str)

  • skip_same_agent_retries (bool)

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.pipeline.state_models.CommitState(*, message_prepared=False, diff_prepared=False, agent_invoked=False)[source]

Bases: BaseModel

State for commit operations.

Parameters:
  • message_prepared (bool)

  • diff_prepared (bool)

  • agent_invoked (bool)

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.pipeline.state_models.FalloverRecord(*, phase, from_agent, to_agent, timestamp_iso)[source]

Bases: BaseModel

A record of a single agent fallover event persisted in pipeline state.

Parameters:
  • phase (str)

  • from_agent (str)

  • to_agent (str)

  • timestamp_iso (str)

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.pipeline.state_models.RebaseState(*, pending=False, in_progress=False, completed=False)[source]

Bases: BaseModel

State for git rebase operations.

Parameters:
  • pending (bool)

  • in_progress (bool)

  • completed (bool)

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.pipeline.state_models.RunMetrics(*, total_agent_calls=0, total_continuations=0, total_fallbacks=0, total_retries=0)[source]

Bases: BaseModel

Run-level execution metrics.

Parameters:
  • total_agent_calls (int)

  • total_continuations (int)

  • total_fallbacks (int)

  • total_retries (int)

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

with_continuation_increment()[source]

Return a copy with total_continuations incremented by 1.

Return type:

RunMetrics

with_fallback_increment()[source]

Return a copy with total_fallbacks incremented by 1.

Return type:

RunMetrics

with_retry_increment()[source]

Return a copy with total_retries incremented by 1.

Return type:

RunMetrics

ralph.pipeline.work_unit

Single planning work unit declaration.

class ralph.pipeline.work_unit.WorkUnit(*, unit_id, description, allowed_directories=<factory>, dependencies=<factory>)[source]

Bases: BaseModel

Single planning work unit declaration.

Parameters:
  • unit_id (Annotated[str, MinLen(min_length=1)])

  • description (Annotated[str, MinLen(min_length=1), MaxLen(max_length=4096)])

  • allowed_directories (list[str])

  • dependencies (list[str])

model_config = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

ralph.pipeline.work_units_validation_error

Validation error for work units planning artifacts.

exception ralph.pipeline.work_units_validation_error.WorkUnitsValidationError[source]

Bases: ValueError

Raised when a planning artifact contains invalid work_units.

ralph.pipeline.worker_status

Worker status enum for parallel execution.

class ralph.pipeline.worker_status.WorkerStatus(*values)[source]

Bases: StrEnum

Execution status of a single parallel worker.

Skills

This group owns the shipped skill bundle and the helpers that sync it with each agent’s skill tree. The mirrored baseline skill content lives under ralph/skills/content/ and is listed in BASELINE_SKILL_NAMES in ralph/skills/_content.py. The manager (ralph/skills/manager.py) handles install, adoption, and the deterministic auto-commit contract enforced by ralph/testing/audit_skill_auto_commit.py (verify step 18).

ralph.skills

Baseline capability management for Ralph Workflow.

ralph.skills.manager

SkillManager for baseline capability health tracking and skill installation.

class ralph.skills.manager.SkillManager(state_path=None, policy=RecheckPolicy(healthy_recheck_hours=24.0, failed_recheck_hours=1.0, always_recheck_if_not_installed=True))[source]

Bases: object

Manages baseline capability health state and skill bundle installation.

Parameters:
  • state_path (Path | None)

  • policy (RecheckPolicy)

check_baseline_health()[source]

Mark web_search/visit_url INSTALLED_OUTDATED if ralph version changed.

Return type:

dict[str, bool]

check_skills_for_updates()[source]

Surface outdated baseline skills and return whether an update is available.

The user must run ralph --force-init-skills to apply the update; this method records the signal in state for ralph runs and _print_user_global_update_hint to surface, but does NOT mutate the user-global canonical or any sibling symlink. (Do NOT auto-repair.)

Return type:

bool

ensure_baseline_capabilities(*, workspace_root)[source]

Install skills, probe docs_mcp, stamp web_search/visit_url with Ralph version.

Returns (updated_state, failures) where failures is the list of failure codes returned by install_baseline_skills (empty list on success). The failures list is threaded up to the caller (init.py) so a NEEDS_REPAIR status from the skill installer is visible in the user-facing init summary.

Parameters:

workspace_root (Path)

Return type:

tuple[CapabilityState, list[str]]

get_docs_mcp_available(*, workspace_root)[source]

Return True if docs_mcp is reachable and healthy (uses TTL-based cache).

Parameters:

workspace_root (Path)

Return type:

bool

reinstall_baseline_skills(workspace_root)[source]

Re-run user-global + project-scope baseline skill installation.

Used by ralph --force-init-skills to force a full re-resolve even when the install predicates report a healthy state. The two install calls are merged: the WORST CapabilityStatus wins (NEEDS_REPAIR > INSTALLED_OUTDATED > others, per CapabilityStatus enum order) and the failure-code lists are concatenated. Saved to state and returned.

Non-fatal: a raising exception is caught and reported via the ‘reinstall-exception’ failure code so the CLI surface stays usable.

Parameters:

workspace_root (Path)

Return type:

tuple[CapabilityState, list[str]]

Git

The Git group bundles the GitPython-backed repository operations used by the runtime: status, diff, log, show, rebase, hooks, subprocess runner, and the commit-cleanup helpers that ship under ralph/git/commit_cleanup.py. Every blocking call in this group uses a bounded timeout via ralph/git/subprocess_runner.run_git; see the bounded-subprocess contract in docs/agents/verification.md.

ralph.git.commit_cleanup

Git cleanup operations for commit hardening.

This module provides deterministic git operations for the commit cleanup phase, handling file deletion, gitignore updates, and git exclude patterns.

ralph.git.commit_cleanup.add_to_git_exclude(repo_root, patterns)[source]

Append patterns to .git/info/exclude for machine-local excludes.

Parameters:
  • repo_root (Path | str) – Path to the repository root.

  • patterns (list[str]) – List of patterns to add to exclude.

Return type:

None

ralph.git.commit_cleanup.delete_file_from_repo(repo_root, relative_path)[source]

Remove a file from the repository, unstaging if necessary.

Parameters:
  • repo_root (Path | str) – Path to the repository root.

  • relative_path (str) – Path relative to repo_root of the file to delete.

Return type:

None

ralph.git.commit_cleanup.ensure_git_initialized(repo_root)[source]

Ensure the directory is a git repository, initializing if necessary.

Parameters:

repo_root (Path | str) – Path to the repository root.

Return type:

None

ralph.git.commit_cleanup.untrack_engine_internal_files(repo_root, is_internal_path)[source]

Pre-emptively git rm --cached every tracked engine-internal file.

This is the rock-solid safety net that the commit_cleanup phase calls BEFORE the agent runs: any tracked file that matches is_internal_path (the canonical is_agent_internal_path predicate) is removed from the index so it cannot enter the diff the agent sees. Without this step, a delete_file action for a tracked engine-internal file is rejected by _is_safe_to_delete (the prior failure mode – the safety check would otherwise surface a hard fail for tracked engine-owned paths even though the path is engine-owned).

Contract:

  • git rm --cached is used (NOT git rm) – the working-tree files remain on disk so the agent can decide whether to follow up with a separate delete_file action.

  • Symlinks are rejected BEFORE git rm --cached by checking Path(repo_root / path).is_symlink(). git rm follows symlinks, so a symlink under .agent/ could stage the symlink target (which may live outside the repo).

  • The FIVE canonical project-scope skill-root prefixes (.opencode/skills/, .agents/skills/, .claude/skills/, .codex/skills/, .gemini/antigravity-cli/skills/) are early-skipped BEFORE the symlink-WARNING block. Skills are tracked by design (see commit e4b47d2fb), so a symlink under any of those roots is intentional and the WARNING is noise. This does NOT widen the safety surface: the canonical is_agent_internal_path predicate is unchanged and is_internal_path still gates the git rm --cached call.

  • Repo is opened in a try/finally and closed on every exit path, mirroring delete_file_from_repo.

  • Per-path failures are wrapped in try/except Exception so a single bad entry does NOT abort the batch – the helper is best-effort by design.

  • The function returns the list of paths actually untracked so the caller (handle_commit_cleanup_phase) can log the result.

The placement of this helper’s call in handle_commit_cleanup_phase is pinned by the _check_pre_emptive_untrack_placement AST helper in ralph/testing/audit_agent_internal_paths.py – any future refactor that moves the call behind the artifact load, or that widens the deletion surface, fails that audit.

Parameters:
  • repo_root (Path | str) – Path to the repository root. Accepts both Path and str for parity with the rest of this module’s API.

  • is_internal_path (Callable[[str], bool]) – Predicate that returns True when a tracked path is a Ralph runtime artifact (the canonical is_agent_internal_path from ralph.phases._agent_internal_paths). Passed as a positional argument to keep the helper decoupled from the leaf module and avoid circular-import risk.

Returns:

List of repository-relative paths that were removed from the index. Empty when no paths matched the predicate, when the repository has no tracked files, when repo_root is not a git repository, or when every match was a symlink and got rejected before git rm --cached.

Return type:

list[str]

Phases

This group holds the per-phase logic: analysis (ralph/phases/analysis.py), artifact-required checks (required_artifacts), commit / commit-cleanup / commit-logging, execution, integrity, review, verification, and timing. Each phase maps to a TOML block under [blocks.*] in the bundled ralph/policy/defaults/pipeline.toml. See Concepts for how phase transitions are wired and Advanced Pipeline Configuration for extending the phase graph.

ralph.phases

Phases module — phase handlers for the Ralph pipeline.

Each phase is implemented as a module that exports a handle_phase function. The handler receives an Effect and a PhaseContext, performs any necessary I/O (prompt preparation, agent invocation, artifact reading), and emits Events.

Phase handlers are registered by name in HANDLERS dict. Unknown phases in the pipeline graph will produce a PhaseHandlerNotFoundError at startup.

Two registration mechanisms are supported: 1. Decorator-based at import time: @register_handler(“phase_name”) 2. Role-based at policy-load time: register_role_handlers(policy)

The role-based mechanism registers the generic handler for every phase whose role matches a known role class (commit or analysis). This allows policy-renamed phases to work without hardcoded handler registration.

ralph.phases.analysis

Shared analysis logic for parsing analysis decisions.

The analysis phase reads a typed artifact submitted by the agent via MCP and extracts the decision field to route the pipeline.

Decision routing is driven entirely by policy: the phase’s decisions table in pipeline.toml maps raw status strings (from the agent artifact) to PhaseDecisionRoute targets. The reducer routes via decisions[status].target directly, so the raw status string is passed through as-is.

Decisions are raw strings from the agent artifact mapped to routes in policy. The BaseModel AnalysisDecision in ralph.mcp.artifacts.typed_artifacts is the artifact schema; routing uses the raw status string, not a StrEnum.

ralph.phases.analysis.handle_generic_analysis_phase(effect, ctx)[source]

Generic handler for analysis-role phases registered via register_role_handlers.

Used for policy-declared analysis phases whose names are not the canonical development_analysis or review_analysis. The handler:

  • Uses effect.phase as the pipeline policy phase name.

  • Uses effect.drain (if set) or effect.phase as the drain name for artifact path and vocabulary lookup.

  • Emits AnalysisDecisionEvent with the raw decision string, letting the reducer route directly through phase_def.decisions[status].target.

Parameters:
  • effect (Effect) – The effect that triggered this phase.

  • ctx (PhaseContext) – Phase context with workspace and policy.

Returns:

List of events to emit.

Return type:

list[Event]

ralph.phases.analysis.parse_analysis_decision_status(ctx, drain_name, *, phase_name=None)[source]

Parse the raw decision status string from the MCP artifact.

Reads the artifact file from the workspace and extracts the status field. The raw status string is returned directly — the reducer looks up the target in phase_def.decisions[status].target.

The drain_name is used to locate the artifact and vocabulary; the phase_name (defaults to drain_name when omitted) is used to look up the phase’s decisions table in pipeline policy.

Parameters:
  • ctx (PhaseContext) – Phase context with workspace and pipeline_policy.

  • drain_name (str) – Name of the drain (used for artifact path and vocabulary lookup).

  • phase_name (str | None) – Name of the phase in pipeline policy (defaults to drain_name).

Returns:

Raw status string, or None if parsing fails (caller should emit PhaseFailureEvent).

Return type:

str | None

ralph.phases.artifacts

Helpers for reading persisted MCP artifacts inside phase handlers.

exception ralph.phases.artifacts.PhaseArtifactError[source]

Bases: ValueError

Raised when a phase artifact is missing or malformed.

ralph.phases.artifacts.artifact_contract_for_drain(artifacts_policy, drain, artifact_type)[source]

Find the artifact contract for a drain/type pair if one exists.

Parameters:
  • artifacts_policy (object)

  • drain (str)

  • artifact_type (str)

Return type:

ArtifactContract | None

ralph.phases.artifacts.artifact_validation_failure_event(phase, reason, *, retry_in_session=True)[source]

Build a typed phase failure event for artifact/proof validation issues.

Parameters:
  • phase (str)

  • reason (str)

  • retry_in_session (bool)

Return type:

PhaseFailureEvent

ralph.phases.artifacts.decision_vocabulary_for_drain(artifacts_policy, drain, artifact_type)[source]

Return the allowed decision strings for a given drain and artifact type.

Parameters:
  • artifacts_policy (object)

  • drain (str)

  • artifact_type (str)

Return type:

list[str]

ralph.phases.artifacts.load_phase_artifact(workspace, path)[source]

Load a persisted MCP artifact wrapper from the workspace.

Parameters:
Return type:

dict[str, object]

ralph.phases.artifacts.unwrap_phase_artifact_content(artifact, *, expected_type=None)[source]

Return the inner content payload from a persisted artifact wrapper.

Parameters:
  • artifact (Mapping[str, object])

  • expected_type (str | None)

Return type:

dict[str, object]

ralph.phases.artifacts.validate_artifact_on_disk(workspace, required_artifact)[source]

Return None if the required artifact is present, parseable, and valid.

Otherwise return a human-readable failure detail. This is the SINGLE on-disk artifact-contract check used by both the pipeline phase gates and the commit command, so “missing / can’t parse / wrong type / wrong format” detection cannot drift between callers.

Parameters:
Return type:

str | None

ralph.phases.commit

Commit phase handler.

Commit-role phases handle git operations after successful development or review phases. They stage and commit changes with an appropriate message.

If the working tree has no uncommitted changes when a commit phase is entered, the handler emits COMMIT_SKIPPED so the reducer can advance routing without incrementing iteration/reviewer_pass counters for a no-op pass.

The generic handle_commit_phase() function works for any phase with role=’commit’. It is registered exclusively via register_role_handlers(policy) at policy-load time for all commit-role phases declared in the active pipeline policy.

ralph.phases.commit.handle_commit_phase(effect, ctx)[source]

Generic commit phase handler for any role=’commit’ phase.

Stages and commits changes after a successful phase. When the working tree has no pending changes, emits COMMIT_SKIPPED so the pipeline advances without billing a progress counter for the no-op pass.

Parameters:
  • effect (Effect)

  • ctx (PhaseContext)

Return type:

list[Event]

ralph.phases.commit_cleanup

Commit cleanup phase handler.

This phase runs before the commit message phase to clean up any files that should not be committed (binaries, build artifacts, temporary files, etc.).

The phase is hardened to be ROCK SOLID: cleanup actions are applied best-effort – a single unsafe delete_file does not abort the whole phase. Safe actions (matching files, gitignore patterns, git exclude patterns) are still applied even when one or more delete actions are skipped. The phase only returns PhaseFailureEvent when EVERY delete action was rejected and no safe work was done; in that case the event carries a structured retry hint naming the rejected paths.

The phase PRE-EMPTIVELY UNTRACKS tracked engine-internal files (via untrack_engine_internal_files from ralph.git.commit_cleanup) BEFORE loading the artifact. This is the safety net for the prior failure mode where tracked .agent/raw/opencode.log, .agent/tmp/mcp-server.log, or root checkpoint.json would trigger a hard reject from the safety classifier when the agent submitted delete_file actions – the rejection came because the file was tracked in HEAD and the safety check ran before the engine-internal fast-path exemption. The pre-emptive untrack removes those paths from the INDEX (not the working tree) so the agent’s diff never includes them and the rejection cannot occur.

The phase also auto-seeds the canonical .gitignore and .git/info/exclude patterns on every entry (via auto_seed_default_gitignore and auto_seed_default_git_exclude from ralph.config.bootstrap) so the engine-internal allowlist stays in effect on non-bootstrap runs. Both seeds are wrapped in try/except so a seeding failure cannot fail the phase.

ralph.phases.commit_cleanup.build_cleanup_retry_hint(skipped_paths, safe_applied_count)[source]

Build a structured retry-hint message naming rejected paths and how to fix them.

The hint is intended to be appended to the PhaseFailureEvent.reason when cleanup returns a failure so the agent can self-correct on retry. Each skipped path appears on its own line; the action is the recommended remediation (typically add_to_git_exclude for machine-local files or delete_file was already attempted and rejected, so the agent should drop the entry).

Parameters:
  • skipped_paths (list[str]) – Paths whose delete_file action was rejected.

  • safe_applied_count (int) – Number of safe actions applied in the same batch (used to tell the agent how much partial work succeeded).

Returns:

A multi-line structured message. Always non-empty – even an empty skipped_paths produces a sentinel that explains the empty case.

Return type:

str

ralph.phases.commit_cleanup.handle_commit_cleanup_phase(effect, ctx)[source]

Handle the commit cleanup phase.

Behavior summary:

  • PreparePromptEffect returns PROMPT_PREPARED.

  • non-agent effects return [].

  • InvokeAgentEffect ensures git exists, auto-seeds canonical .gitignore and .git/info/exclude patterns on every entry, validates the commit-cleanup artifact, applies cleanup actions best-effort, and returns AGENT_SUCCESS when analysis_complete=True or PHASE_LOOPBACK otherwise.

  • Cleanup is best-effort: a single unsafe delete_file does not abort the phase. The phase only fails when EVERY delete action was unsafe AND no safe action was applied.

  • Missing artifacts return PhaseFailureEvent with recoverable=True.

Parameters:
  • effect (Effect)

  • ctx (PhaseContext)

Return type:

list[Event]

ralph.phases.commit_logging

Commit logging session for tracking commit generation attempts.

Ported from ralph-workflow/src/phases/commit_logging/io.rs.

This module provides detailed logging for each commit generation attempt, creating a clear audit trail for debugging parsing failures.

class ralph.phases.commit_logging.CommitAttemptLog(attempt_number, agent, strategy, timestamp=<factory>, prompt_size_bytes=0, diff_size_bytes=0, diff_was_truncated=False, raw_output=None, outcome=None)[source]

Bases: object

Per-attempt log for commit message generation.

Parameters:
  • attempt_number (int)

  • agent (str)

  • strategy (str)

  • timestamp (datetime)

  • prompt_size_bytes (int)

  • diff_size_bytes (int)

  • diff_was_truncated (bool)

  • raw_output (str | None)

  • outcome (str | None)

class ralph.phases.commit_logging.CommitLoggingSession(run_dir, attempt_counter=0, is_noop=False)[source]

Bases: object

Session tracker for commit generation logging.

Parameters:
  • run_dir (Path)

  • attempt_counter (int)

  • is_noop (bool)

classmethod new(base_log_dir, workspace_exists_func, workspace_makedirs_func)[source]

Create a new logging session.

Creates a unique run directory under the base log path.

Parameters:
  • base_log_dir (str) – Base directory for logs.

  • workspace_exists_func (Callable[[Path], bool]) – Function to check if path exists (workspace.exists).

  • workspace_makedirs_func (Callable[[Path], None]) – Function to create directories (workspace.create_dir_all).

Returns:

A new CommitLoggingSession instance.

Return type:

CommitLoggingSession

new_attempt(agent, strategy)[source]

Create a new attempt log.

Parameters:
  • agent (str) – Agent name.

  • strategy (str) – Retry strategy.

Returns:

A new CommitAttemptLog instance.

Return type:

CommitAttemptLog

next_attempt_number()[source]

Get the next attempt number and increment the counter.

Returns:

The next attempt number.

Return type:

int

classmethod noop()[source]

Create a no-op logging session that discards all writes.

Returns:

A no-op CommitLoggingSession instance.

Return type:

CommitLoggingSession

write_attempt_log(attempt_log, workspace_write_func)[source]

Write an attempt log to a file.

Parameters:
  • attempt_log (CommitAttemptLog) – The attempt log to write.

  • workspace_write_func (Callable[[str, str], None]) – Function to write to workspace (workspace.write).

Return type:

None

write_summary(total_attempts, final_outcome, workspace_write_func)[source]

Write summary file at end of session.

For no-op sessions, this silently succeeds without writing anything.

Parameters:
  • total_attempts (int) – Total number of attempts.

  • final_outcome (str) – Final outcome string.

  • workspace_write_func (Callable[[str, str], None]) – Function to write to workspace (workspace.write).

Return type:

None

ralph.phases.execution

Generic execution phase handler.

Handles any phase with role=’execution’. Behavior is determined by the drain’s artifact contract:

  • Drain produces artifact_type=’plan’: plan validation, noop detection, plan draft management (PreparePromptEffect clears stale drafts).

  • Drain produces artifact_type=’development_result’: plan INPUT validation (noop short-circuit + work-unit policy check) before validating the output artifact.

  • All other drains: validate the configured output artifact contract only.

On PreparePromptEffect: clears a stale plan draft when the phase produces a plan. On InvokeAgentEffect: validates the output artifact contract, with type-specific pre-validation for plan and development_result drains.

ralph.phases.execution.handle_execution_phase(effect, ctx)[source]

Generic handler for any phase with role=’execution’.

Parameters:
  • effect (Effect) – The effect that triggered this phase.

  • ctx (PhaseContext) – Phase context with workspace and policy.

Returns:

List of events to emit.

Return type:

list[Event]

ralph.phases.integrity

PROMPT.md integrity helpers for Python phase execution.

class ralph.phases.integrity.IntegrityResult(ok, restored, prompt_path='PROMPT.md', backup_path=None, message='')[source]

Bases: object

Result of a PROMPT.md integrity verification pass.

Parameters:
  • ok (bool)

  • restored (bool)

  • prompt_path (str)

  • backup_path (str | None)

  • message (str)

ralph.phases.integrity.default_prompt_path(workspace_root, env=None)[source]

Return the env-aware effective source-prompt path for a workspace.

Thin convenience wrapper around ralph.pro_support.prompt.resolve_effective_prompt_path().

Parameters:
  • workspace_root (Path)

  • env (Mapping[str, str] | None)

Return type:

Path

ralph.phases.integrity.ensure_prompt_integrity(workspace, *, phase, iteration, prompt_path=None, backup_paths=('.agent/prompt.backup.md', '.agent/PROMPT.md.bak', '.agent/PROMPT.backup.md'))[source]

Ensure PROMPT.md is present, restoring from backup when possible.

When prompt_path is None the effective path is resolved through ralph.pro_support.prompt.resolve_effective_prompt_path().

Parameters:
  • workspace (Workspace)

  • phase (str)

  • iteration (int)

  • prompt_path (str | None)

  • backup_paths (tuple[str, ...])

Return type:

IntegrityResult

ralph.phases.integrity.find_prompt_backup(workspace, *, backup_paths=('.agent/prompt.backup.md', '.agent/PROMPT.md.bak', '.agent/PROMPT.backup.md'))[source]

Return the first available prompt backup path.

Parameters:
  • workspace (Workspace)

  • backup_paths (tuple[str, ...])

Return type:

str | None

ralph.phases.integrity.verify_prompt_integrity(workspace, *, prompt_path=None)[source]

Check that PROMPT.md exists and is non-empty.

When prompt_path is None the effective path is resolved through ralph.pro_support.prompt.resolve_effective_prompt_path() so the PROMPT_PATH env var is honoured in Pro mode. The legacy literal "PROMPT.md" is preserved when the caller passes that string explicitly so the existing tests can keep their explicit defaults.

Parameters:
  • workspace (Workspace)

  • prompt_path (str | None)

Return type:

IntegrityResult

ralph.phases.required_artifacts

Centralized required-artifact metadata for all pipeline phases.

Artifact metadata is split across two policy surfaces. artifacts.toml owns artifact type, JSON path, markdown handoff path, and schema normalizer lookup. pipeline.toml owns whether a phase’s output artifact is required for success. There are no built-in override tables — artifact paths must be declared in artifacts.toml and requiredness must be declared on the phase definition.

class ralph.phases.required_artifacts.RequiredArtifact(phase, artifact_type, json_path, markdown_path, normalizer, artifact_required=True)[source]

Bases: object

Metadata about an artifact contract for a pipeline phase.

When artifact_required is False, an absent artifact does not fail the phase; a present artifact is still validated.

Parameters:
  • phase (str)

  • artifact_type (str)

  • json_path (str)

  • markdown_path (str | None)

  • normalizer (Callable[[dict[str, object]], dict[str, object]] | None)

  • artifact_required (bool)

ralph.phases.required_artifacts.build_missing_input_hint(phase, upstream_phase, artifact_path)[source]

Build a retry hint for a phase that is missing a required upstream input artifact.

Unlike build_retry_hint (which describes a missing output), this function describes a missing input — i.e., a handoff that a prior phase should have produced. The hint is written to the phase’s retry-hint file so the agent sees an explanation on the next attempt, but the message correctly names the upstream producer rather than blaming the current agent.

Parameters:
  • phase (str)

  • upstream_phase (str)

  • artifact_path (str)

Return type:

str

ralph.phases.required_artifacts.build_proof_failure_hint(phase, detail)[source]

Build a retry hint for a phase that submitted proof but failed validation.

Parameters:
  • phase (str)

  • detail (str)

Return type:

str

ralph.phases.required_artifacts.build_required_artifacts(artifacts_policy)[source]

Build a drain-keyed artifact registry from ArtifactsPolicy.

The registry contains artifact identity and path metadata only. Callers that need phase-specific requiredness must use resolve_phase_required_artifact().

Parameters:

artifacts_policy (ArtifactsPolicy)

Return type:

dict[str, RequiredArtifact]

ralph.phases.required_artifacts.build_retry_hint(phase, detail, *, registry=None, prior_output=None, submit_tool_name=None, example_payload=None)[source]

Build the retry hint for an agent that failed to submit a required artifact.

This is the SINGLE source of artifact-missing retry guidance — every caller (pipeline phase gates AND the commit command) routes through it, so the recovery cannot drift. When prior_output/submit_tool_name are given, the hint additionally echoes the agent’s own prior analysis back and tells it to submit via the named tool, so a model that already drafted the artifact submits it instead of restarting.

Parameters:
  • phase (str) – Pipeline phase / drain name.

  • detail (str) – Error detail message.

  • registry (dict[str, RequiredArtifact] | None) – Optional artifact registry; when provided the hint names the specific artifact type and json path.

  • prior_output (list[str] | None) – The agent’s prior output lines, echoed back as context.

  • submit_tool_name (str | None) – The submit-artifact tool the agent must call.

  • example_payload (str | None) – An example submit-tool arguments payload, if available.

Return type:

str

ralph.phases.required_artifacts.resolve_phase_required_artifact(pipeline_policy, artifacts_policy, *, phase, drain=None)[source]

Resolve the artifact contract for a phase, including phase-owned requiredness.

Parameters:
Return type:

RequiredArtifact | None

ralph.phases.required_artifacts.resolve_required_artifact(artifacts_policy, *, drain)[source]

Resolve artifact identity/path metadata for a drain from artifacts.toml.

Parameters:
Return type:

RequiredArtifact | None

ralph.phases.required_artifacts.retry_hint_path(phase)[source]

Return the workspace-relative path for the retry hint file for a phase.

Parameters:

phase (str)

Return type:

str

ralph.phases.review

Generic review-role phase handler.

This handler may be registered for any phase declared with role=’review’. It does not assume the phase is named ‘review’: all emitted events derive the phase name from the incoming effect’s phase attribute.

When no new commits have landed since the last successful review pass, the handler emits REVIEW_CLEAN so the reducer routes straight to review_commit without invoking the reviewer agent. We intentionally treat any commit since the baseline as a trigger to re-review — even documentation churn — because the reviewer, not this handler, is the correct judge of which changes are substantive.

ralph.phases.review.handle_review(effect, ctx)[source]

Handle the review phase.

Parameters:
  • effect (Effect) – The effect that triggered this phase.

  • ctx (PhaseContext) – Phase context with workspace and policy.

Returns:

List of events to emit.

Return type:

list[Event]

ralph.phases.verification

Verification phase handler.

The verification phase enforces a policy-defined gating check before the pipeline can advance. The gate is declarative — it is validated at runtime against the configured verification kind.

Verification kinds: - artifact: the configured artifact path must exist and be non-empty - none: purely declarative gate; always passes

On gate failure, when on_failure_route is set, the handler emits PhaseFailureEvent(recoverable=False) so the reducer routes through _enter_failed_recovery to the policy-declared failure route. When on_failure_route is unset, the pipeline halts at the terminal failure route.

ralph.phases.verification.handle_verification_phase(effect, ctx)[source]

Generic handler for verification-role phases.

Dispatches on phase_def.verification.kind: - ‘artifact’: requires the configured artifact path to exist and be non-empty - ‘none’: purely declarative; emits AGENT_SUCCESS to advance

On gate failure, when on_failure_route is set, emits PhaseFailureEvent(recoverable=False) so the reducer routes to that target. When on_failure_route is unset, recoverable=False halts at the terminal failure.

Parameters:
  • effect (Effect) – The effect that triggered this phase.

  • ctx (PhaseContext) – Phase context with workspace and policy.

Returns:

List of events to emit.

Return type:

list[Event]

ralph.phases.timing

Phase timing utilities.

Provides monotonic-clock helpers and two dataclasses for measuring how long each pipeline phase takes:

  • PhaseTimer - start timing a phase with PhaseTimer.start(phase) and stop it with timer.finish() to get a PhaseTimingRecord.

  • PhaseTimingRecord - frozen record holding the phase name, iteration number, start timestamp, and elapsed timedelta / whole-second count.

All time values use time.monotonic so they are safe across system-clock adjustments. Elapsed seconds are truncated (not rounded) to whole integers.

class ralph.phases.timing.PhaseTimer(phase, iteration, started_at)[source]

Bases: object

Simple helper for measuring phase execution durations.

Parameters:
  • phase (str)

  • iteration (int)

  • started_at (float)

finish()[source]

Return a completed timing record for the phase.

Return type:

PhaseTimingRecord

classmethod start(phase, *, iteration=0)[source]

Start timing a phase.

Parameters:
  • phase (str)

  • iteration (int)

Return type:

PhaseTimer

class ralph.phases.timing.PhaseTimingRecord(phase, iteration, started_at, elapsed, elapsed_seconds)[source]

Bases: object

Structured timing result for a completed phase execution.

Parameters:
  • phase (str)

  • iteration (int)

  • started_at (float)

  • elapsed (timedelta)

  • elapsed_seconds (int)

ralph.phases.timing.capture_time()[source]

Return a monotonic timestamp suitable for elapsed-time calculations.

Return type:

float

ralph.phases.timing.elapsed(start)[source]

Return the duration since start using a monotonic clock.

Parameters:

start (float)

Return type:

timedelta

ralph.phases.timing.elapsed_seconds(start)[source]

Return whole elapsed seconds since start.

Parameters:

start (float)

Return type:

int

Agents

This is the largest group: it owns the built-in agent catalog, the executor protocol and subprocess adapter, the parsers for every supported agent (Claude, Codex, OpenCode, Nanocoder, AGY, Pi), the execution strategies, and the activity-aware idle watchdog with its post-exit sibling. See Agent CLI lifecycle for the selection and trust story, Concepts for the timeout model, and Agent Compatibility Guide for the supported-agent matrix.

ralph.agents

Public agent-management exports.

This package exposes the set of agent abstractions most callers need: registry lookup, chain composition, process invocation, and support registration.

The unified registration flow enables adding, updating, or removing agents. For the 90% case, prefer the opinionated 5-line recipe register_my_agent.

Example:

from ralph.agents import register_my_agent, AgentRegistry
from ralph.agents.parsers.generic import GenericParser
from ralph.config.enums import AgentTransport

register_my_agent(
    name="my-agent",
    transport=AgentTransport.GENERIC,
    parser=GenericParser,
    agent_registry=AgentRegistry(),
)

For advanced scenarios (CCS aliases, dynamic model parsing, custom AgentRegistry.ccs_defaults) use the 14-kwarg register_agent_support helper or AgentCatalog.add directly. Both still delegate to the same single mutation surface.

Imports are resolved lazily so submodule imports like ralph.agents.clock do not pull in the full agent runtime during package initialization.

ralph.agents.activity

Watchdog-relevant activity signals emitted by agent transports.

class ralph.agents.activity.AgentActivityKind(*values)[source]

Bases: StrEnum

Kinds of agent activity that can reset the idle watchdog.

class ralph.agents.activity.AgentActivitySignal(kind, raw='')[source]

Bases: object

Small transport-neutral signal consumed by timeout control flow.

Parameters:

ralph.agents.agent_activity_kind

Watchdog-relevant agent activity kind enumeration.

class ralph.agents.agent_activity_kind.AgentActivityKind(*values)[source]

Bases: StrEnum

Kinds of agent activity that can reset the idle watchdog.

ralph.agents.agent_chain

Agent fallback chain with retry and backoff behavior.

class ralph.agents.agent_chain.AgentChain(agents, max_retries=3, retry_delay_ms=1000, backoff_multiplier=2.0, max_backoff_ms=60000)[source]

Bases: object

Manages agent fallback chain with retry logic.

The chain maintains an ordered list of agents and handles: - Current agent selection - Retry counting and limits - Exponential backoff between retries - Fallback to next agent on exhaustion

Parameters:
  • agents (list[str])

  • max_retries (int)

  • retry_delay_ms (int)

  • backoff_multiplier (float)

  • max_backoff_ms (int)

agents

List of agent names in the chain.

current_index

Index of the currently selected agent.

retries

Number of retries for current agent.

max_retries

Maximum retries before falling back.

retry_delay_ms

Base delay between retries in milliseconds.

backoff_multiplier

Multiplier for exponential backoff.

max_backoff_ms

Maximum backoff delay in milliseconds.

advance()[source]

Advance to the next agent in the chain.

Returns:

True if advanced successfully, False if chain exhausted.

Return type:

bool

calculate_backoff()[source]

Calculate backoff delay in seconds.

Returns:

Backoff delay in seconds.

Return type:

float

can_retry()[source]

Check if current agent can be retried.

Returns:

True if retries remain for current agent.

Return type:

bool

property current_agent: str | None

Get the current agent name.

Returns:

Agent name or None if chain is exhausted.

property is_exhausted: bool

Check if all agents in chain are exhausted.

Returns:

True if no agents remain.

record_retry()[source]

Record a retry attempt for current agent.

Return type:

None

wait_backoff(*, _sleep=<built-in function sleep>)[source]

Wait for the backoff period.

Parameters:

_sleep (Callable[[float], None])

Return type:

None

ralph.agents.agent_entry

Minimal agent config protocol for availability checks.

class ralph.agents.agent_entry.AgentEntry(*args, **kwargs)[source]

Bases: Protocol

Minimal agent config interface for availability checks.

ralph.agents.clock

Clock protocol for the agent timeout subsystem.

class ralph.agents.clock.Clock(*args, **kwargs)[source]

Bases: Protocol

Protocol for wall-clock operations used by the timeout subsystem.

monotonic()[source]

Return current monotonic time in seconds.

Return type:

float

sleep(seconds)[source]

Pause execution for the given number of seconds.

Parameters:

seconds (float)

Return type:

None

wait_for_event(event, seconds)[source]

Wait up to seconds for event to be set.

Returns True if the event was set during the wait, False on timeout. Production: uses event.wait() so line arrivals wake the poll loop immediately. Test: advances logical time by seconds and checks event state (no real wait).

Parameters:
  • event (threading.Event)

  • seconds (float)

Return type:

bool

ralph.agents.drain_not_bound_error

Errors raised when drain-to-chain binding is missing.

exception ralph.agents.drain_not_bound_error.DrainNotBoundError(drain, available_drains)[source]

Bases: Exception

Raised when a drain has no explicit chain binding.

Parameters:
  • drain (str)

  • available_drains (set[str])

Return type:

None

drain

The unbound drain name.

available_drains

Names of all bound drains.

ralph.agents.executor_error

Executor exception types.

exception ralph.agents.executor_error.ExecutorError[source]

Bases: Exception

Raised when an executor encounters an unrecoverable failure.

ralph.agents.system_clock

Production clock for the agent timeout subsystem.

class ralph.agents.system_clock.SystemClock[source]

Bases: Clock

Production Clock: uses real wall-clock time.

monotonic()[source]

Return current monotonic time in seconds.

Return type:

float

sleep(seconds)[source]

Pause execution for the given number of seconds.

Parameters:

seconds (float)

Return type:

None

wait_for_event(event, seconds)[source]

Wait up to seconds for event to be set.

Returns True if the event was set during the wait, False on timeout. Production: uses event.wait() so line arrivals wake the poll loop immediately. Test: advances logical time by seconds and checks event state (no real wait).

Parameters:
  • event (Event)

  • seconds (float)

Return type:

bool

ralph.agents.unknown_agent_error

Errors raised when agent lookup fails.

exception ralph.agents.unknown_agent_error.UnknownAgentError(agent_name)[source]

Bases: Exception

Raised when an agent name is not found in the registry.

Parameters:

agent_name (str)

Return type:

None

agent_name

The unknown agent name.

ralph.agents.builtin_spec

Single declarative source for the 7 built-in agent declarations.

The BuiltinAgentSpec dataclass mirrors the kwargs accepted by ralph.agents.registration.register_agent_support() and the legacy AgentSupport.from_registration_kwargs so the 7 built-in entries in ralph.agents.builtin can be expressed as a single declarative row per agent, instead of repeating the kwargs across seven function calls.

Use BuiltinAgentSpec.to_support() to materialize the dataclass into an AgentSupport instance. The resulting is_builtin flag is always True so the catalog can treat these entries as reserved.

class ralph.agents.builtin_spec.BuiltinAgentSpec(transport, parser_factory, strategy_factory, json_parser=JsonParserType.GENERIC, cmd=None, output_flag=None, yolo_flag=None, verbose_flag=None, can_commit=False, model_flag=None, print_flag=None, streaming_flag=None, session_flag=None, display_name=None, interactive=False, subagent_capability=None, no_default_session_flag=False)[source]

Bases: object

Declarative description of one built-in agent.

Parameters:
  • transport (AgentTransport)

  • parser_factory (Callable[[], AgentParser])

  • strategy_factory (StrategyFactory)

  • json_parser (JsonParserType)

  • cmd (str | None)

  • output_flag (str | None)

  • yolo_flag (str | None)

  • verbose_flag (str | None)

  • can_commit (bool)

  • model_flag (str | None)

  • print_flag (str | None)

  • streaming_flag (str | None)

  • session_flag (str | None)

  • display_name (str | None)

  • interactive (bool)

  • subagent_capability (bool | None)

  • no_default_session_flag (bool)

transport

Transport enum value.

Type:

AgentTransport

parser_factory

Callable returning a parser instance.

Type:

Callable[[], AgentParser]

strategy_factory

Callable returning an execution strategy instance.

Type:

StrategyFactory

json_parser

Parser type token.

Type:

JsonParserType

cmd

Executable command; defaults to name on materialization.

Type:

str | None

output_flag

Optional output format flag.

Type:

str | None

yolo_flag

Optional autonomous flag string.

Type:

str | None

verbose_flag

Optional verbose flag string.

Type:

str | None

can_commit

Whether the agent can run git commit.

Type:

bool

model_flag

Optional model/provider flag.

Type:

str | None

print_flag

Optional print flag.

Type:

str | None

streaming_flag

Optional streaming flag.

Type:

str | None

session_flag

Optional session continuation flag template.

Type:

str | None

display_name

Human-readable display name.

Type:

str | None

interactive

Whether the agent is interactive (PTY).

Type:

bool

subagent_capability

Whether the agent exposes usable sub-agent tooling.

Type:

bool | None

no_default_session_flag

When True, suppress the default --resume {} session template that would otherwise be set by AgentSupport.from_registration_kwargs() for interactive agents. Used for agy.

Type:

bool

to_support(name)[source]

Materialize the dataclass into an AgentSupport.

Forwards every dataclass field as a keyword argument to AgentSupport.from_registration_kwargs() so future BuiltinAgentSpec additions do not require updating two parallel kwarg lists. is_builtin=True is always set.

Parameters:

name (str) – Agent name to assign to the resulting support.

Returns:

The materialized AgentSupport with is_builtin=True.

Return type:

AgentSupport

ralph.agents.builtin

Declarative registry of the eight built-in agent CLIs.

This module is the single source of truth for the agents that Ralph Workflow ships with out of the box. Each entry is a BuiltinAgentSpec declaratively describing one CLI: the transport, the parser/strategy pair, the JSON parsing mode, the executable, the flags used for unattended (“yolo”) invocation, resume/session support, and whether the agent is allowed to author commits.

The eight built-in agents are:

  • claude (Claude Code interactive / PTY transport)

  • claude-headless (Claude Code headless JSON-stream transport)

  • codex (Codex CLI)

  • opencode (OpenCode CLI)

  • nanocoder (Nanocoder CLI)

  • agy (AGY CLI; binary overridable via RALPH_AGY_BINARY)

  • pi (Pi.dev CLI)

  • cursor (Cursor Agent CLI; binary overridable via RALPH_CURSOR_BINARY)

Adding a new built-in agent requires editing this module only; the catalog picks the entries up via builtin_supports(). Custom agents configured via .agent/agents.toml are layered on top by the catalog and do not need to be declared here.

Side effects: none at import time. The agent supports are returned as a fresh tuple on each call to builtin_supports() so callers can iterate without sharing state.

ralph.agents.builtin.builtin_supports()[source]

Return a fresh copy of the built-in agent supports.

Return type:

tuple[AgentSupport, …]

ralph.agents.idle_watchdog_kill

Typed exception for an idle-watchdog kill of the agent process.

When the idle watchdog fires, it terminates the agent with a SIGTERM (exit signal 15) and tags the exception with the watchdog’s fire-reason (idle, stalled, no_output, etc.). The recovery controller classifies the failure from these typed attributes — not from substring-matching the agent’s stderr (the failure class that relabeled a SIGTERM as a connectivity blip because the agent’s stderr happened to contain the word “timeout”).

Use this exception type whenever the watchdog fires, so the classifier sees isinstance(exc, IdleWatchdogKilledError) and consults exc.signal and exc.reason directly.

exception ralph.agents.idle_watchdog_kill.IdleWatchdogKilledError(reason, signal, *, evidence_summary=None, child_alive=None, resumable_session_id=None)[source]

Bases: Exception

The idle watchdog killed the agent.

Parameters:
  • reason (str)

  • signal (int)

  • evidence_summary (str | None)

  • child_alive (bool | None)

  • resumable_session_id (str | None)

Return type:

None

reason

The watchdog’s authoritative fire-reason (e.g. "idle", "stalled", "no_output"). NEVER derived from a text match.

signal

The OS signal the watchdog used to terminate the agent (typically signal.SIGTERM == 15). Typed, not parsed from text.

evidence_summary

Optional human-readable summary of per-channel evidence state at fire time, including tier labels and freshness.

child_alive

Optional bool recording the corroborator’s alive_by signal at the moment of the fire.

  • True – the corroborator confirmed a live child (AliveBy.OS_DESCENDANT_ONLY_STALE_PROGRESS, CPU_IDLE_WHILE_ALIVE, LOG_STALE_WHILE_ALIVE, FRESH_HEARTBEAT_ONLY, or STALE_LABEL_ONLY). Normally dead code: the gate refinement in IdleWatchdog._is_no_progress_quiet defers the NO_PROGRESS_QUIET fire when the corroborator reports any alive_by signal. This path is defense-in-depth.

  • False – the corroborator returned alive_by=None (no live signal — i.e. the child is truly dead or missing). The conservative policy routes this to is_unavailable=True with unavailability_reason=STALE_CHILD_QUIET (Rule 2: exponential backoff to the next agent).

  • None – the construction site did not set the field (legacy default). The conservative policy preserves the original STALE_CHILD_QUIET (Rule 2) behavior for backward-compat with the existing construction sites that do not set the field.

ralph.agents.worker_result

Typed result returned by an agent executor.

class ralph.agents.worker_result.WorkerResult(unit_id, exit_code, final_message, duration_ms)[source]

Bases: object

Immutable result returned by an executor after a work unit finishes.

exit_code mirrors the subprocess exit status; 0 indicates success. final_message is the last status line emitted by the agent. duration_ms is the wall-clock elapsed time for the unit.

Parameters:
  • unit_id (str)

  • exit_code (int)

  • final_message (str)

  • duration_ms (int)

ralph.agents.availability

Agent PATH availability checks for Ralph Workflow.

Shared helper used by both the first-run welcome banner and the ralph –diagnose command to determine whether configured agents are reachable on the system PATH.

class ralph.agents.availability.AgentEntry(*args, **kwargs)[source]

Bases: Protocol

Minimal agent config interface for availability checks.

class ralph.agents.availability.HasListAgents(*args, **kwargs)[source]

Bases: Protocol

Protocol for agent registries used in availability checks.

ralph.agents.availability.check_agent_availability(registry)[source]

Check which agents are available on PATH.

Parameters:

registry (HasListAgents) – Object implementing list_agents() and get(name) for agent resolution.

Returns:

List of (registry_name, status) tuples where status is one of ‘available’, ‘missing_on_path’, or ‘no_cmd’. The key is always the configured registry name so callers can join back to the registry without a secondary display-name lookup.

Return type:

list[tuple[str, Literal[‘available’, ‘missing_on_path’, ‘no_cmd’]]]

ralph.agents.chain

Agent fallback chain management with strict drain-to-chain binding.

This module handles the agent fallback chain — the ordered list of agents to try when an agent fails. It supports retry logic and exponential backoff.

IMPORTANT: This module implements STRICT drain-to-chain binding. Every drain must have an explicit binding in AgentsPolicy or startup validation fails. There is NO permissive fallback resolution — no sibling fallback, no inference, no default chains. If a drain is not bound, DrainNotBoundError is raised.

class ralph.agents.chain.AgentChain(agents, max_retries=3, retry_delay_ms=1000, backoff_multiplier=2.0, max_backoff_ms=60000)[source]

Bases: object

Manages agent fallback chain with retry logic.

The chain maintains an ordered list of agents and handles: - Current agent selection - Retry counting and limits - Exponential backoff between retries - Fallback to next agent on exhaustion

Parameters:
  • agents (list[str])

  • max_retries (int)

  • retry_delay_ms (int)

  • backoff_multiplier (float)

  • max_backoff_ms (int)

agents

List of agent names in the chain.

current_index

Index of the currently selected agent.

retries

Number of retries for current agent.

max_retries

Maximum retries before falling back.

retry_delay_ms

Base delay between retries in milliseconds.

backoff_multiplier

Multiplier for exponential backoff.

max_backoff_ms

Maximum backoff delay in milliseconds.

advance()[source]

Advance to the next agent in the chain.

Returns:

True if advanced successfully, False if chain exhausted.

Return type:

bool

calculate_backoff()[source]

Calculate backoff delay in seconds.

Returns:

Backoff delay in seconds.

Return type:

float

can_retry()[source]

Check if current agent can be retried.

Returns:

True if retries remain for current agent.

Return type:

bool

property current_agent: str | None

Get the current agent name.

Returns:

Agent name or None if chain is exhausted.

property is_exhausted: bool

Check if all agents in chain are exhausted.

Returns:

True if no agents remain.

record_retry()[source]

Record a retry attempt for current agent.

Return type:

None

wait_backoff(*, _sleep=<built-in function sleep>)[source]

Wait for the backoff period.

Parameters:

_sleep (Callable[[float], None])

Return type:

None

class ralph.agents.chain.ChainManager(agents_policy)[source]

Bases: object

Manages agent chains with strict drain-to-chain binding.

ChainManager is constructed with an AgentsPolicy and provides lookup of chains by drain name. Drain resolution is STRICT — there is no fallback or inference. If a drain is not explicitly bound, DrainNotBoundError is raised.

Parameters:

agents_policy (AgentsPolicy)

agents_policy

The agents policy containing chains and drain bindings.

chain_config_for_drain(drain)[source]

Alias for chain_for_drain for clarity.

Parameters:

drain (str)

Return type:

AgentChainConfig

chain_for_drain(drain)[source]

Get the chain configuration for a drain.

This is the STRICT drain resolution — no fallback, no inference. If the drain is not explicitly bound in agents.toml, DrainNotBoundError is raised at startup before any agent is invoked.

Parameters:

drain (str) – Drain name to look up.

Returns:

AgentChainConfig for the bound chain.

Raises:

DrainNotBoundError – If the drain is not explicitly bound.

Return type:

AgentChainConfig

classmethod from_config(config)[source]

Create ChainManager from a legacy UnifiedConfig.

This is a compatibility shim that converts the old UnifiedConfig format to the new AgentsPolicy format.

Parameters:

config (UnifiedConfig) – Legacy unified configuration.

Returns:

ChainManager instance.

Return type:

ChainManager

validate()[source]

Validate the policy for internal consistency.

Returns:

List of validation error messages (empty if valid).

Return type:

list[str]

exception ralph.agents.chain.DrainNotBoundError(drain, available_drains)[source]

Bases: Exception

Raised when a drain has no explicit chain binding.

Parameters:
  • drain (str)

  • available_drains (set[str])

Return type:

None

drain

The unbound drain name.

available_drains

Names of all bound drains.

exception ralph.agents.chain.UnknownAgentError(agent_name)[source]

Bases: Exception

Raised when an agent name is not found in the registry.

Parameters:

agent_name (str)

Return type:

None

agent_name

The unknown agent name.

ralph.agents.chain.create_chain_from_config(config, chain_name)[source]

Create an AgentChain from UnifiedConfig.

Parameters:
  • config (UnifiedConfig) – Unified configuration.

  • chain_name (str) – Name of the chain in agent_chains.

Returns:

AgentChain instance or None if chain not found.

Return type:

AgentChain | None

ralph.agents.completion_signals

Completion signal evaluation for OpenCode agent exits.

evaluate_completion() inspects the workspace artifacts directory and the raw NDJSON output to determine whether an OpenCode agent run produced the required phase artifact or explicitly declared completion via the declare_complete MCP tool. Explicit completion and artifact presence are separate signals; the explicit-complete flag is never auto-set just because a phase has no required artifact entry.

Phases whose pipeline definition marks the output artifact optional (artifact_required=False) are treated as terminal on a clean exit even when no artifact is produced and no explicit declare_complete call is made. The artifact provides context only; its absence does not gate phase success. A present optional artifact is still fully validated.

Phases without any artifact contract return required_artifact_present=False. OpenCode agents running such phases must still call declare_complete explicitly rather than relying on implicit success.

class ralph.agents.completion_signals.CompletionSignals(explicit_complete, required_artifact_present, artifact_types, terminal_ack_seen=False, artifact_optional=False, completion_sentinel_present=False)[source]

Bases: object

Signals that indicate whether an agent run actually completed its work.

Parameters:
  • explicit_complete (bool)

  • required_artifact_present (bool)

  • artifact_types (tuple[str, ...])

  • terminal_ack_seen (bool)

  • artifact_optional (bool)

  • completion_sentinel_present (bool)

explicit_complete

True when the agent called the declare_complete MCP tool successfully (independent of artifact presence).

Type:

bool

required_artifact_present

True when the required phase artifact exists on disk. False when the phase has no registered required artifact or the artifact file does not yet exist.

Type:

bool

artifact_types

Tuple of artifact type names found.

Type:

tuple[str, …]

terminal_ack_seen

True when a child_terminal lifecycle ACK was received from the OpenCode transport.

Type:

bool

artifact_optional

True when the phase marks its output artifact optional (artifact_required=False). A clean exit is terminal even without the artifact or an explicit declare_complete call.

Type:

bool

completion_sentinel_present

True when the run-scoped completion sentinel written by handle_declare_complete exists on disk. This is the authoritative proof that the agent actually invoked the declare_complete MCP tool; the plain-text marker alone can be spoofed by agent output.

Type:

bool

ralph.agents.completion_signals.evaluate_completion(workspace, raw_output=None, *, required_artifact=None, run_id=None, sentinel_secret=None, receipt_secret=None)[source]

Check whether the agent run produced a required artifact or explicit completion.

explicit_complete is set from scanning raw_output for the declare_complete MCP tool marker, independently of artifact presence. required_artifact_present is True only when the artifact file exists on disk, parses as valid JSON, and contains a non-empty dict for phases that have a registered required artifact. Phases without a registered required artifact always return required_artifact_present=False so OpenCode agents cannot implicitly succeed — they must call declare_complete explicitly.

Parameters:
  • workspace (Path) – Workspace root path.

  • raw_output (list[str] | None) – Raw NDJSON lines from agent stdout for explicit-completion detection.

  • required_artifact (RequiredArtifact | None) – Policy-derived artifact metadata.

  • run_id (str | None) – Run id used to key the run-scoped completion sentinel.

  • sentinel_secret (str | None) – RFC-013 P3: broker-owned secret for HMAC verification of the completion sentinel. None falls back to the pre-P3 contract (no HMAC verification; legacy fall-through path). Threading a secret through this parameter is what stops a model with workspace write capabilities from forging a sentinel — the matching write side must also thread the same secret (see handle_declare_complete in ralph.mcp.tools.coordination).

  • receipt_secret (str | None) – RFC-013 P3: broker-owned secret for HMAC verification of the artifact submission receipt. None falls back to the pre-P3 contract (no HMAC verification). Threading a secret through this parameter stops a model with workspace write capabilities from forging a receipt — the matching write side must also thread the same secret (see handle_submit_artifact in ralph.mcp.tools.artifact).

Returns:

CompletionSignals reflecting current artifact state and explicit completion.

Return type:

CompletionSignals

ralph.agents.completion_signals.extract_explicit_completion(raw_output)[source]

Return True if raw NDJSON output contains a successful declare_complete call.

Detects the unique marker produced by handle_declare_complete() in ralph/mcp/tools/coordination.py. The marker string only appears in the output when the agent successfully calls the declare_complete MCP tool.

Parameters:

raw_output (list[str]) – Raw NDJSON lines from the agent subprocess stdout.

Returns:

True if the declare_complete marker is found in any output line.

Return type:

bool

ralph.agents.completion_signals.is_artifact_submitted(workspace, run_id, artifact_type, *, deps=None, receipt_secret=None)[source]

Return True when a canonical receipt exists or can be promoted from fallback.

This is the completion-signal layer’s single entry point for artifact presence. It first checks for a receipt; if none exists it attempts to promote a fallback file written by the agent (.agent/tmp/<type>.json or .agent/artifacts/<type>.json) through the canonical submit path so a receipt is stamped.

Parameters:
  • receipt_secret (str | None) – RFC-013 P3 broker-owned secret for HMAC verification of the receipt. None falls back to the pre-P3 contract (no HMAC verification).

  • workspace (Path)

  • run_id (str)

  • artifact_type (str)

  • deps (ArtifactHandlerDeps | None)

Return type:

bool

ralph.agents.execution_state

Transport-aware execution state model for agent lifecycle management.

Provides AgentExecutionState (active/waiting/resumable/terminal), the execution strategies, and OpenCode registry routing helpers.

ralph.agents.execution_state.agent_execution_state

Agent execution state enumeration.

class ralph.agents.execution_state.agent_execution_state.AgentExecutionState(*values)[source]

Bases: StrEnum

Execution state for an agent run.

ralph.agents.execution_state.claude_execution_strategy

Execution strategy for Claude agents.

class ralph.agents.execution_state.claude_execution_strategy.ClaudeExecutionStrategy(*, label_scope=None, registry=None, subagent_pid_source=None)[source]

Bases: GenericExecutionStrategy

Claude-aware activity classification for watchdog control flow.

Parameters:
  • label_scope (str | None)

  • registry (ChildLivenessRegistry | None)

  • subagent_pid_source (SubagentPidSource | None)

classify_activity_line(line)[source]

Classify a raw output line for idle-watchdog activity.

Generic transports treat any non-blank line as activity while rejecting whitespace-only heartbeats so a process cannot evade the idle deadline without emitting meaningful provider output. JSON error events are classified as ERROR_LINE so the repeated-error circuit breaker can detect a wedged retry loop.

Parameters:

line (str)

Return type:

AgentActivitySignal | None

ralph.agents.execution_state.claude_interactive_execution_strategy

Execution strategy for interactive Claude agents.

class ralph.agents.execution_state.claude_interactive_execution_strategy.ClaudeInteractiveExecutionStrategy(**kwargs)[source]

Bases: CompletionEnforcingStrategy, ClaudeExecutionStrategy

Interactive Claude session strategy.

Uses a VT-aware transcript parser before falling back to the headless Claude classifier so TUI repaint noise does not downgrade meaningful tool/lifecycle lines into generic output.

Parameters:

kwargs (object)

classify_activity_line(line)[source]

Classify a raw output line for idle-watchdog activity.

Generic transports treat any non-blank line as activity while rejecting whitespace-only heartbeats so a process cannot evade the idle deadline without emitting meaningful provider output. JSON error events are classified as ERROR_LINE so the repeated-error circuit breaker can detect a wedged retry loop.

Parameters:

line (str)

Return type:

AgentActivitySignal | None

ralph.agents.execution_state.generic_execution_strategy

Generic execution strategy for agents.

class ralph.agents.execution_state.generic_execution_strategy.GenericExecutionStrategy(*, label_scope=None, registry=None, subagent_pid_source=None)[source]

Bases: BaseExecutionStrategy

Default strategy: single-process lifetime, exit 0 is terminal success.

Replicates the behaviour that existed before the session-aware model was introduced so that Claude/Codex paths are unaffected.

Parameters:
  • label_scope (str | None)

  • registry (ChildLivenessRegistry | None)

  • subagent_pid_source (SubagentPidSource | None)

ralph.agents.execution_state.opencode_execution_strategy

Execution strategy for Opencode agents.

class ralph.agents.execution_state.opencode_execution_strategy.OpenCodeExecutionStrategy(*, label_scope=None, registry=None, subagent_activity_sink=None, **_kwargs)[source]

Bases: BaseExecutionStrategy

OpenCode-aware strategy.

Idle classification checks the injectable LivenessProbe before falling back to the psutil-based has_live_descendants(), so unit tests can inject a FakeLivenessProbe without spawning real processes.

Exit classification uses evidence precedence:
  1. terminal_ack_seen or schema-valid required artifact -> TERMINAL_COMPLETE

  2. fresh progress in registry -> WAITING_ON_CHILD

  3. live OS descendants with no fresh progress -> RESUMABLE_CONTINUE (stale)

  4. else -> RESUMABLE_CONTINUE

label_scope narrows the Ralph-tracked liveness check to processes whose labels start with agent:{label_scope}:. When no scope is available, the empty-prefix registry-wide snapshot is consulted; the strategy never returns ACTIVE based on a never-matching sentinel.

Parameters:
  • label_scope (str | None)

  • registry (ChildLivenessRegistry | None)

  • subagent_activity_sink (Callable[[str], None] | None)

  • _kwargs (object)

classify_activity_line(line)[source]

Classify OpenCode output for idle-watchdog activity.

Parameters:

line (str)

Return type:

AgentActivitySignal | None

observe_line(line)[source]

Route a parsed output line into the child liveness registry.

When subagent_activity_sink is set, the sink is invoked once per CHILD_PROGRESS or CHILD_HEARTBEAT signal so the idle watchdog’s per-channel evidence surface stays fresh. The child_liveness registry continues to own freshness tracking; this is a thin shim from “progress observed” to “activity signal sent”. Sink exceptions are swallowed so a buggy sink cannot corrupt the registry or break the line loop.

Parameters:

line (str)

Return type:

None

ralph.agents.executor

Agent executor protocol.

class ralph.agents.executor.AgentExecutor(*args, **kwargs)[source]

Bases: Protocol

Protocol that every agent executor implementation must satisfy.

Implementors receive a WorkUnit, stream output via on_output, report status transitions via on_status, and return a WorkerResult when the unit completes or fails.

exception ralph.agents.executor.ExecutorError[source]

Bases: Exception

Raised when an executor encounters an unrecoverable failure.

class ralph.agents.executor.WorkerResult(unit_id, exit_code, final_message, duration_ms)[source]

Bases: object

Immutable result returned by an executor after a work unit finishes.

exit_code mirrors the subprocess exit status; 0 indicates success. final_message is the last status line emitted by the agent. duration_ms is the wall-clock elapsed time for the unit.

Parameters:
  • unit_id (str)

  • exit_code (int)

  • final_message (str)

  • duration_ms (int)

ralph.agents.idle_watchdog

Idle watchdog for agent timeout policy enforcement.

IdleWatchdog owns the in-stream idle/deadline logic and exposes a single evaluate() method. All wall-clock decisions go through the injected Clock so the watchdog is fully testable without real sleeps (FakeClock) per CLAUDE.md test performance policy.

This module is the canonical home for the watchdog subsystem. It exposes two canonical owner classes:

  • IdleWatchdog (in-stream) — in .idle_watchdog — the sole owner of in-stream fire decisions.

  • PostExitWatchdog (post-exit) — in ._post_exit_watchdog — the sole owner of post-EOF fire decisions. Re-exported here so callers can from ralph.agents.idle_watchdog import PostExitWatchdog.

Together these two watchdogs cover every wall-clock timeout fire path in the agent invocation system; no ad-hoc clock.monotonic()/clock.sleep() loops are allowed in invoke.py. The drift audit (ralph.testing.audit_watchdog_drift) enforces this single-owner invariant.

IdleWatchdog owns fire reasons: SESSION_CEILING_EXCEEDED, NO_OUTPUT_DEADLINE, and CHILDREN_PERSIST_TOO_LONG. PostExitWatchdog owns: PROCESS_EXIT_HANG and DESCENDANT_HANG. See ._post_exit_watchdog for the post-exit family.

ralph.agents.idle_watchdog.corroboration_snapshot

Corroboration snapshot for idle watchdog.

class ralph.agents.idle_watchdog.corroboration_snapshot.ChannelEvidenceSummary(channel_name, tier, last_at, age_seconds, counter=None, kind_breakdown=None, alive_by=None, can_defer=True)[source]

Bases: object

Per-channel activity evidence snapshot for the watchdog verdict.

Each channel is a separate stream of activity evidence that the watchdog considers for the NO_OUTPUT_DEADLINE verdict. The channel is “fresh” when age_seconds is below the configured activity_evidence_ttl_seconds TTL. A channel with last_at is None has never been observed and is treated as stale.

  • channel_name: Canonical name of the channel (see ChannelName).

  • tier: Whether this channel is first-party or side-channel evidence.

  • last_at: Monotonic clock value of the last observed activity on this channel, or None if the channel has never been observed.

  • age_seconds: Seconds since the last observed activity; None when last_at is None. Always non-negative for observable channels.

  • counter: Number of activity events seen on this channel, or None if the channel has never been observed.

  • kind_breakdown: Per-kind breakdown of the channel counter. Only populated for the workspace channel. None when the channel has no kind breakdown or when no workspace activity has been observed.

  • alive_by: For the subagent_liveness channel, the AliveBy classification at the time of the summary; None otherwise.

  • can_defer: Whether this channel’s fresh evidence is allowed to defer the NO_OUTPUT_DEADLINE verdict. First-party channels and strong side-channel channels defer; weak side-channel channels do not.

Parameters:
  • channel_name (ChannelName)

  • tier (EvidenceTier)

  • last_at (float | None)

  • age_seconds (float | None)

  • counter (int | None)

  • kind_breakdown (dict[str, int] | None)

  • alive_by (AliveBy | None)

  • can_defer (bool)

is_fresh(ttl)[source]

Return True when this channel is fresher than ttl seconds.

A channel with last_at is None is never fresh. A non-positive TTL disables freshness, matching the existing activity_evidence_ttl disable semantics.

Parameters:

ttl (float | None)

Return type:

bool

to_dict()[source]

Render the summary as a dict for diagnostic embedding.

Always returns a fresh dict. Keys with None values are omitted for backward compatibility with consumers that assert on the dict shape.

Return type:

dict[str, object]

class ralph.agents.idle_watchdog.corroboration_snapshot.ChannelName(*values)[source]

Bases: StrEnum

Canonical evidence channel names.

STDOUT: agent stdout output (first-party). MCP_TOOL: MCP tool-call invocations/completions made by the agent (first-party). SUBAGENT_OUTPUT: a subagent’s own output/log stream, read where observable (first-party). SUBAGENT_LIVENESS: bare subagent PID liveness when output is not observable (side-channel). WORKSPACE: workspace file changes (side-channel).

class ralph.agents.idle_watchdog.corroboration_snapshot.CorroborationSnapshot(workspace_event_count=None, oldest_child_seconds=None, scoped_child_active=None, scoped_child_count=None, terminal_child_events_total=None, last_activity_was_meaningful=None, alive_by=None, mcp_tool_call_count=None, subagent_progress_count=None, last_mcp_tool_call_at=None, last_subagent_progress_at=None, last_workspace_event_at=None, current_run_idle_elapsed_seconds=None)[source]

Bases: object

Advisory snapshot of corroborating signals for WAITING_ON_CHILD diagnosis.

All fields are Optional so callers without a given source can leave them None. Corroborators are advisory only; they NEVER affect WatchdogVerdict. The hard stop is determined solely by max_waiting_on_child_seconds and max_session_seconds.

Per-channel activity evidence fields (mcp_tool_call_count, subagent_progress_count, last_mcp_tool_call_at, last_subagent_progress_at, last_workspace_event_at) carry the evidence the watchdog needs to defer a NO_OUTPUT_DEADLINE verdict while work is happening on a non-stdout channel. They default to None so existing construction sites remain valid; a fully-populated snapshot is the canonical “rich” evidence surface that IdleWatchdog.last_evidence_summary() reduces for the verdict hook.

Parameters:
  • workspace_event_count (int | None)

  • oldest_child_seconds (float | None)

  • scoped_child_active (bool | None)

  • scoped_child_count (int | None)

  • terminal_child_events_total (int | None)

  • last_activity_was_meaningful (bool | None)

  • alive_by (AliveBy | None)

  • mcp_tool_call_count (int | None)

  • subagent_progress_count (int | None)

  • last_mcp_tool_call_at (float | None)

  • last_subagent_progress_at (float | None)

  • last_workspace_event_at (float | None)

  • current_run_idle_elapsed_seconds (float | None)

ralph.agents.idle_watchdog.idle_watchdog

Idle watchdog for detecting stalled agents.

Two-State Invariant

The watchdog is one half of the recovery contract; the recovery controller is the other. The pipeline can only enter TWO recovery states; no third state is allowed:

  1. Exponential backoff to the next agent – driven by AgentUnavailabilityTracker.mark_unavailable in ralph/recovery/agent_unavailability_tracker.py. The current agent is marked unavailable for a per-reason backoff; the chain advances to the next agent whose cooldown has expired. The wrap=True re-arming in RecoveryController._next_available_agent_index reconsiders earlier agents whose cooldown has expired.

  2. Retry with the same agent – driven by AgentChain.record_retry. The same agent is retried in-place (chain.retries is incremented; the budget is debited but the chain index does not advance).

The watchdog contributes to state (1) only indirectly: when the watchdog fires and the controller classifies the failure as unavailable, the tracker applies the per-reason backoff. The watchdog contributes to state (2) when it fires and the controller classifies the failure as retryable.

Hard rules

  • The watchdog NEVER calls sys.exit, os._exit, or raise SystemExit. The run loop owns the exit decision.

  • The watchdog NEVER marks an agent as permanently unavailable. Every fire reason is transient; the cooldown math is owned by AgentUnavailabilityTracker and the only way for an agent to leave the unavailable set is for the cooldown to expire.

  • Every non-absolute fire is gated by the StuckClassifier (_stuck_classifier.py) returning StuckKind.STUCK. The absolute SESSION_CEILING_EXCEEDED reason is the ONLY reason that bypasses the gate (it is an operator-set hard cap, not a stuck-detection signal). Every other reason – including CHILDREN_PERSIST_TOO_LONG – is gated: the watchdog consults classify_stuck and returns CONTINUE for any non-STUCK kind so a productive session that has not yet been classified as “stuck” is not killed.

  • The watchdog is the sole owner of in-stream fire decisions; PostExitWatchdog is the sole owner of post-exit fire decisions. The import-time assertion on WatchdogFireReason.__members__ (below) locks the enum set so a future PR cannot silently widen or narrow the fire set without updating the watchdog owner.

Channel freshness gate

The evaluate() method consults a per-channel evidence summary before returning WatchdogVerdict.FIRE. A fire is deferred (WatchdogVerdict.CONTINUE) when any of the following are true:

  • state.is_waiting_state is True (the pipeline has already committed to a wait – this is the strongest signal and is checked first).

  • The connectivity monitor reports offline.

  • A first-party channel (mcp_tool or subagent_output) is fresher than activity_evidence_ttl_seconds.

  • The subagent-liveness side-channel is fresh.

  • The classify_quiet strategy returns WAITING_ON_CHILD or RESUMABLE_CONTINUE (these branches are evaluated by the live classify_quiet callable the watchdog receives from evaluate() – the watchdog stores the most recent callable in self._classify_quiet_provider so the gate can consult it on every _classify_stuck_now call).

The classifier is a deterministic 7-kind enum (THINKING, LOADING, WAITING_ON_CONNECTIVITY, TRANSITIONING, STUCK, DUPLICATE_KILL, SILENT_SUBAGENT) and is a pure function of its inputs. See _stuck_classifier.py for the full contract.

class ralph.agents.idle_watchdog.idle_watchdog.IdleWatchdog(config, clock, listener=None, *, corroborator=None, process_monitor=None, connectivity_state_provider=None)[source]

Bases: object

Tracks agent idle time and decides when to fire the timeout.

The watchdog owns the last_activity timestamp; the caller’s loop must NEVER mutate _last_activity directly. Activity must flow through record_activity(), which preserves the cumulative WAITING_ON_CHILD ceiling while advancing the idle baseline. Direct resets here previously caused a false-negative bug where WAITING_ON_CHILD deferred the deadline forever.

Cumulative WAITING_ON_CHILD time is an absolute ceiling that is preserved across every transition (heartbeat activity, drain windows, classify_quiet outcomes). Once recorded, cumulative time never decays during the session — this mirrors max_session_seconds semantics so neither ceiling can be defeated by a process that alternates between producing output and waiting on children.

The session ceiling (max_session_seconds) is checked first on every evaluate() call and cannot be defeated by activity — record_activity() does not reset it.

Status events are emitted via the optional listener.

  • ENTERED once when WAITING_ON_CHILD deferral begins.

  • PROGRESS at most once per waiting_status_interval_seconds (rate-limited).

  • SUSPECTED_FROZEN once per WAITING run when suspect threshold is crossed.

  • EXITED when transitioning out of WAITING_ON_CHILD.

  • HARD_STOP immediately before returning FIRE for CHILDREN_PERSIST_TOO_LONG.

Listener exceptions are caught and logged at DEBUG; they never propagate.

Per-channel activity evidence (NEW): the watchdog tracks three non-stdout channels in addition to the stdout baseline.

  • mcp_tool: MCP tools/call invocations/completions routed via the Ralph MCP server. Updated by record_mcp_tool_call.

  • subagent: subagent progress signals (heartbeat, phase change) routed from the opencode child_liveness registry. Updated by record_subagent_work.

  • workspace: workspace file change events captured by WorkspaceMonitor. Updated by record_workspace_event, which is invoked by the readers’ on_event callback passed to WorkspaceMonitor.set_on_event (the monitor is constructed in invoke_agent before the per-run watchdog exists, so the readers register the callback on the monitor immediately after the watchdog is created in read_lines; the binding is cleared in the finally block so a stale callback can never fire after the run ends).

The three recorders do NOT touch _last_activity (the stdout baseline); the existing “stdout only resets idle baseline” invariant is preserved. Instead, they update per-channel _last_at timestamps and counters. The verdict hook in evaluate() defers a NO_OUTPUT_DEADLINE fire when ANY non-stdout channel is fresher than activity_evidence_ttl_seconds, returning CONTINUE with a debug log. Absolute ceilings (SESSION_CEILING_EXCEEDED, CHILDREN_PERSIST_TOO_LONG) are checked before the deferral hook and remain absolute.

Parameters:
  • config (TimeoutPolicy)

  • clock (Clock)

  • listener (WaitingStatusListener | None)

  • corroborator (WaitingCorroborator | None)

  • process_monitor (ProcessMonitor | None)

  • connectivity_state_provider (Callable[[], str | None] | None)

property cumulative_waiting_on_child_seconds: float

Cumulative seconds spent in WAITING_ON_CHILD state across all runs.

idle_elapsed_seconds(now)[source]

Seconds since the last recorded activity (the idle duration).

Public accessor so callers (e.g. the process-reader fire log) can report a meaningful idle-elapsed value instead of the raw monotonic clock.

Parameters:

now (float)

Return type:

float

property invocation_elapsed_seconds: float

Return the seconds elapsed since the start of the invocation.

property last_alive_by: AliveBy | None

The corroborator’s alive_by signal at the most recent fire.

None when the watchdog has not fired yet OR when the most recent fire was not NO_PROGRESS_QUIET (the live-child vs dead-child differentiation only matters for the NO_PROGRESS_QUIET path; other fire helpers do not capture alive_by).

Consumed by IdleWatchdogKilledError.child_alive so the failure classifier can read the live-child signal end-to-end via the typed exception’s __cause__ chain.

property last_deferred_kind: StuckKind | None

The StuckKind that deferred the most recent would-be fire.

None when the watchdog has not deferred a fire yet OR when the most recent fire actually FIREd (the gate only sets this when it returns WatchdogVerdict.CONTINUE to defer).

The diagnostic surface for the SILENT_SUBAGENT label described in AC-05: last_fire_reason collapses every non-FIRE deferral to WatchdogFireReason.DEFERRED_BY_STUCK_CLASSIFIER, but last_deferred_kind retains the precise StuckKind (e.g. StuckKind.SILENT_SUBAGENT) so an operator can see WHY a would-be fire was deferred (“a subagent dispatched then went silent for >180s”). See tests/agents/idle_watchdog/test_silent_subagent_runtime.py for the runtime-facing contract test.

property last_fire_reason: WatchdogFireReason | None

The reason the watchdog fired, or None if it hasn’t fired yet.

property last_subagent_progress_description: str | None

The most recent subagent progress description.

Set by record_subagent_work and reset to None by record_invocation_start. Surfaced publicly so operators and tooling can see what the subagent was doing at any moment without needing to supply a full WaitingStatusListener.

record_error_activity(message)[source]

Record an error/repeat line for the repeated-error circuit breaker.

Deliberately does NOT reset the idle baseline: a stream of identical errors must still let the idle deadline advance (so a silent-after-errors agent is also caught), while the repeated-error rule catches a fast retry storm well before the idle timeout. The cumulative WAITING_ON_CHILD run is still flushed for bookkeeping parity.

Parameters:

message (str)

Return type:

None

record_lifecycle_activity()[source]

Record cosmetic, non-meaningful activity (e.g. lifecycle frames).

Resets the idle baseline exactly like record_activity() so the agent is not declared idle, but does NOT reset the repeated-error circuit breaker: cosmetic output interleaved between identical errors must not mask a wedged retry loop. LIFECYCLE frames are deliberately excluded from the NO_OUTPUT_AT_START baseline.

Return type:

None

record_mcp_tool_call(now=None)[source]

Record an MCP tool-call activity signal (new channel).

Increments the mcp_tool channel counter and updates the per-channel _last_at timestamp. Does NOT touch _last_activity (the stdout baseline) — the existing ‘stdout only resets idle baseline’ invariant is preserved. The verdict hook in evaluate() consults the per-channel _last_at via _channel_evidence_active and defers a NO_OUTPUT_DEADLINE fire while the channel is fresher than the configured activity_evidence_ttl_seconds.

Parameters:

now (float | None) – Optional monotonic timestamp override; tests use this to drive FakeClock without time travel. Defaults to the watchdog’s injected clock.

Return type:

None

record_progress_report(message)[source]

Record an explicit report_progress heartbeat from the agent.

A report that REPEATS the previous status (same fingerprint) is a cosmetic heartbeat: it feeds the repeated-error circuit breaker and does NOT reset the idle baseline, so an agent narrating “still stuck” forever can no longer keep itself alive. A report whose status CHANGES is treated as genuine forward progress (resets the idle baseline and the streak).

Parameters:

message (str)

Return type:

None

record_subagent_output(line_count=1, now=None)[source]

Record fresh subagent output as first-party evidence.

This is the channel that captures a subagent’s own output/log stream when it is observable. Each new line read from the subagent’s output advances the subagent_output first-party channel timestamp.

Parameters:
  • line_count (int) – Number of new lines observed; defaults to 1.

  • now (float | None) – Optional monotonic timestamp override.

Return type:

None

record_tool_use_activity()[source]

Record tool-use activity without clearing retry-loop evidence.

A tool-use event is real activity for idle timing, but the invocation itself is not proof of forward progress. Clearing the repetition tracker here would make repeated identical tool-call loops invisible.

Return type:

None

register_default_subagent_activity_listener(listener)[source]

Register a listener that receives every subagent activity event.

The listener is invoked from _emit for every WaitingStatusEvent whose subagent_activity field is non-None. This gives a cheap, real-time view of what the subagent is doing (e.g. the last child progress line) without requiring callers to implement a full WaitingStatusListener.

The listener is reset to None on record_invocation_start so state does not leak across invocations. Listener exceptions are caught and logged at DEBUG; they never propagate.

Parameters:

listener (WaitingStatusListener | None)

Return type:

None

repetition_diagnostic()[source]

Return bounded repeated-error/tool-call diagnostic context.

Return type:

dict[str, str | int]

set_connectivity_state_provider(provider)[source]

Inject a callable returning the current connectivity state label.

The watchdog does not own connectivity; it only mirrors the live state so the classifier can return WAITING_ON_CONNECTIVITY when the network is offline. None disables the connectivity branch of the classifier (returns None for the connectivity_state input, which the classifier treats as “online” - the gate does not defer on the connectivity branch).

Parameters:

provider (Callable[[], str | None] | None)

Return type:

None

set_is_waiting_state(is_waiting_state)[source]

Update the pipeline’s wait-state flag for the StuckClassifier gate.

The run loop calls this once per phase iteration with the live state.is_waiting_state value. The watchdog does not own this state; it only mirrors it so the classifier can return DUPLICATE_KILL when a candidate fire would land during a wait.

Parameters:

is_waiting_state (bool)

Return type:

None

property workspace_kind_counts: dict[str, int]

Defensive copy of the per-kind workspace event counter.

Returns a fresh dict on every access so callers (the post-mortem diagnostic, the operator UX) can mutate the result without affecting the watchdog’s internal state. The keys are the five WorkspaceChangeKind string values (source, log, cache, artifact, other); kinds that have never been observed are absent from the returned dict.

ralph.agents.idle_watchdog.repetition_tracker

Repetition tracker for the agent stuck-loop circuit breaker.

Tracks how often an agent re-emits the same error (or repeats the same cosmetic progress status) without making forward progress. The idle watchdog consults it to fire REPEATED_ERROR_LOOP when an agent is wedged in a retry storm — the failure mode behind a run that logged the identical MCP error -32001: Request timed out every ~34s for ~5 hours.

Two independent trip conditions (either fires):

  • consecutive: consecutive_threshold identical fingerprints in a row with no intervening note_progress().

  • window: window_count occurrences of one fingerprint within the trailing window_seconds — catches the case where a tiny bit of cosmetic output interleaves between errors and keeps resetting the consecutive streak.

Fingerprinting collapses per-occurrence noise (ISO timestamps, UUIDs, long hex ids, epoch-scale integers) so the same underlying error matches across occurrences, while stable signal such as a -32001 error code survives.

A second repetition dimension (mark_tool_call) tracks identical tool-call fingerprints independently. An agent wedged in an identical-tool-call retry loop (e.g. the same Bash command with the same arguments re-issued N times without producing forward progress) trips WatchdogFireReason.REPEATED_IDENTICAL_TOOL_CALL via the same consecutive + window rules. The two dimensions are tracked in parallel deques so a real error-loop and a real tool-call-loop can co-exist without cancelling each other.

The clock is injected so all timing is deterministic in tests.

class ralph.agents.idle_watchdog.repetition_tracker.RepetitionTracker(clock, *, consecutive_threshold, window_count, window_seconds)[source]

Bases: object

Counts repeated same-fingerprint events to detect a wedged retry loop.

Parameters:
  • clock (Clock)

  • consecutive_threshold (int | None)

  • window_count (int | None)

  • window_seconds (float | None)

diagnostic()[source]

Return bounded diagnostic context for any active repetition streak.

Return type:

dict[str, str | int]

property event_buffer_maxlen: int

Return the per-dimension deque maxlen cap.

Read-only, additive, non-breaking. Exposes the bound so callers (and tests) can reason about memory usage and assert the cap is in effect. Both dimensions share the same cap derived from window_count.

static fingerprint(message)[source]

Normalize a message so equivalent occurrences collapse to one key.

Parameters:

message (str)

Return type:

str

mark_tool_call(tool_name, tool_args)[source]

Record one tool-call observation fingerprinted on (name, args).

Independent of note_error() so an identical tool-call wedge (e.g. Bash with the same arguments re-issued N times) trips via the consecutive + window rules on the tool-call dimension without requiring the agent to also emit errors.

Parameters:
  • tool_name (str) – The tool name (e.g. "Bash"). Empty / None tool names are coerced to "unknown" so the fingerprint is always well-formed.

  • tool_args (object) – The tool arguments (any JSON-serializable structure). None is treated as an empty dict.

Return type:

None

note_error(message)[source]

Record one error/repeat occurrence, fingerprinted.

Parameters:

message (str)

Return type:

None

note_progress()[source]

Record genuine forward progress; clears both trip conditions.

Return type:

None

tripped()[source]

Return True when either trip condition is currently satisfied.

Consults BOTH the error / cosmetic-progress dimension AND the tool-call dimension. Either dimension tripping causes the watchdog to fire REPEATED_ERROR_LOOP (or REPEATED_IDENTICAL_TOOL_CALL based on which dimension tripped first).

Return type:

bool

tripped_tool_dimension()[source]

Return True when ONLY the tool-call dimension is tripped.

Convenience accessor for the watchdog’s evaluate method so it can emit REPEATED_IDENTICAL_TOOL_CALL rather than REPEATED_ERROR_LOOP when only the tool-call wedge is active. Returns False when the error dimension is also tripped (the error reason wins).

Return type:

bool

ralph.agents.idle_watchdog.timeout_policy

Timeout policy configuration for the idle watchdog.

class ralph.agents.idle_watchdog.timeout_policy.TimeoutPolicy(idle_timeout_seconds, drain_window_seconds=0.5, max_waiting_on_child_seconds=1800.0, max_session_seconds=None, idle_poll_interval_seconds=0.05, parent_exit_grace_seconds=5.0, descendant_wait_timeout_seconds=30.0, descendant_wait_poll_seconds=0.5, process_exit_wait_seconds=30.0, waiting_status_interval_seconds=30.0, suspect_waiting_on_child_seconds=600.0, max_waiting_on_child_no_progress_seconds=600.0, no_progress_quiet_seconds=240.0, no_progress_quiet_minimum_invocation_seconds=120.0, no_output_at_start_seconds=30.0, post_tool_result_progression_seconds=120.0, repeated_error_consecutive_threshold=5, repeated_error_window_count=8, repeated_error_window_seconds=600.0, activity_evidence_ttl_seconds=30.0, silent_subagent_seconds=180.0, workspace_change_weights=<factory>, process_monitor_enabled=True, subagent_output_capture_enabled=True, subagent_output_poll_interval_seconds=1.0, os_descendant_only_ceiling_seconds=300.0, os_descendant_only_suspect_seconds=60.0, cpu_idle_seconds=60.0, log_growth_seconds=30.0, no_progress_quiet_strictly_stuck_seconds=None, no_progress_quiet_heartbeat_ceiling_seconds=240.0, watchdog_log_throttle_seconds=30.0, watchdog_subagent_progress_interval_seconds=30.0, stuck_job_sub_ceiling_seconds=600.0)[source]

Bases: object

Consolidated timeout configuration for all agent timeout dimensions.

All timeout constants that previously appeared as module-level magic numbers in invoke.py are consolidated here so a single config-built TimeoutPolicy governs every timeout decision.

Precedence of fire conditions (in evaluation order):

  1. SESSION_CEILING_EXCEEDED — absolute wall-clock cap; activity cannot reset it.

  2. CHILDREN_PERSIST_TOO_LONG — cumulative WAITING_ON_CHILD ceiling; this is an absolute ceiling across the session and never decays. Checked before NO_OUTPUT_DEADLINE so the cumulative ceiling fires even while non-stdout activity channels are still fresh.

  3. NO_OUTPUT_DEADLINE (+ drain window) — idle deadline since last output. When activity_evidence_ttl_seconds is set and any non-stdout evidence channel (MCP tool call, subagent work, workspace file change) is fresher than that TTL, the fire is deferred and the watchdog returns CONTINUE. The SESSION_CEILING and CHILDREN_PERSIST_TOO_LONG checks run before this so absolute ceilings remain absolute.

  4. PROCESS_EXIT_HANG — subprocess closed stdout but did not exit within budget.

  5. DESCENDANT_HANG — descendant-wait deadline elapsed with persistent WAITING_ON_CHILD (post-exit only, owned by PostExitWatchdog).

Suspicion is purely informational and does NOT affect any fire condition. The suspect_waiting_on_child_seconds threshold exists only to emit an elevated warning event before the hard stop; crossing it never shortens the hard-stop ceiling.

Parameters:
  • idle_timeout_seconds (float | None)

  • drain_window_seconds (float)

  • max_waiting_on_child_seconds (float)

  • max_session_seconds (float | None)

  • idle_poll_interval_seconds (float)

  • parent_exit_grace_seconds (float)

  • descendant_wait_timeout_seconds (float)

  • descendant_wait_poll_seconds (float)

  • process_exit_wait_seconds (float)

  • waiting_status_interval_seconds (float)

  • suspect_waiting_on_child_seconds (float | None)

  • max_waiting_on_child_no_progress_seconds (float | None)

  • no_progress_quiet_seconds (float | None)

  • no_progress_quiet_minimum_invocation_seconds (float | None)

  • no_output_at_start_seconds (float | None)

  • post_tool_result_progression_seconds (float | None)

  • repeated_error_consecutive_threshold (int | None)

  • repeated_error_window_count (int | None)

  • repeated_error_window_seconds (float | None)

  • activity_evidence_ttl_seconds (float | None)

  • silent_subagent_seconds (float | None)

  • workspace_change_weights (dict[str, float] | None)

  • process_monitor_enabled (bool)

  • subagent_output_capture_enabled (bool)

  • subagent_output_poll_interval_seconds (float)

  • os_descendant_only_ceiling_seconds (float | None)

  • os_descendant_only_suspect_seconds (float | None)

  • cpu_idle_seconds (float | None)

  • log_growth_seconds (float | None)

  • no_progress_quiet_strictly_stuck_seconds (float | None)

  • no_progress_quiet_heartbeat_ceiling_seconds (float | None)

  • watchdog_log_throttle_seconds (float)

  • watchdog_subagent_progress_interval_seconds (float)

  • stuck_job_sub_ceiling_seconds (float | None)

idle_timeout_seconds

Maximum seconds without output before watchdog may fire. None disables the idle-timeout watchdog entirely.

Type:

float | None

drain_window_seconds

After a potential timeout, the watchdog enters a drain window of this duration to allow late output to flush.

Type:

float

max_waiting_on_child_seconds

Hard cumulative ceiling on time spent in WAITING_ON_CHILD state across the entire session. Activity cannot decay or reset it; once exceeded, fires CHILDREN_PERSIST_TOO_LONG even while children are still alive.

Type:

float

max_session_seconds

Absolute wall-clock ceiling for the entire session. Activity cannot reset this ceiling. None means no ceiling (opt-in). When set, must be >= idle_timeout_seconds.

Type:

float | None

idle_poll_interval_seconds

How often the read loop polls for new lines. Values < 0.01s are intended for tests only.

Type:

float

parent_exit_grace_seconds

Grace window after parent rc=0 exit during which we poll for late completion signals or appearing children.

Type:

float

descendant_wait_timeout_seconds

Maximum time to wait for descendant processes to finish before declaring failure.

Type:

float

descendant_wait_poll_seconds

Poll interval for descendant-wait and process-exit-wait loops. Values < 0.01s are intended for tests only.

Type:

float

process_exit_wait_seconds

Maximum time to wait for a subprocess to exit after its stdout closes. Prevents hanging on subprocesses that close stdout but never call exit().

Type:

float

waiting_status_interval_seconds

How often to emit a PROGRESS status event while WAITING_ON_CHILD deferral is active. Controls only the status emission cadence; does NOT affect timeout safety or ceiling math.

Type:

float

suspect_waiting_on_child_seconds

Cumulative WAITING time after which a SUSPECTED_FROZEN event is emitted. Purely informational — does NOT shorten the hard-stop ceiling or change the watchdog verdict. Must be strictly less than max_waiting_on_child_seconds when set. None disables suspicion events.

Type:

float | None

max_waiting_on_child_no_progress_seconds

Hard ceiling on cumulative WAITING_ON_CHILD time when corroboration shows the child is alive but not making progress (e.g., heartbeat-only, stale-label, or OS-descendant-only evidence). When set, must be <= max_waiting_on_child_seconds. When None, the no-progress ceiling is disabled and max_waiting_on_child_seconds is used for all WAITING_ON_CHILD states.

Type:

float | None

post_tool_result_progression_seconds

When set, the watchdog fires STALLED_AFTER_TOOL_RESULT if no follow-up STREAM_DELTA / OUTPUT_LINE activity arrives within this many seconds of a TOOL_RESULT activity. This is a NEW BEHAVIOR for direct wedge detection: pre-fix, the watchdog only fired NO_OUTPUT_DEADLINE at the full idle_timeout_seconds deadline, which meant a post-tool-result wedge was detected in ~300s (the default idle timeout) rather than ~120s (the default post-tool-result budget). When None, the legacy NO_OUTPUT_DEADLINE-only behavior is preserved. Must be > 0 when set.

Type:

float | None

ralph.agents.idle_watchdog.waiting_status_event

Waiting status event for idle watchdog corroboration.

class ralph.agents.idle_watchdog.waiting_status_event.WaitingStatusEvent(kind, cumulative_seconds, current_run_seconds, idle_elapsed_seconds, ceiling_seconds, suspect_threshold_seconds, diagnostic=<factory>, subagent_activity=None, last_subagent_progress_at=None, current_subagent_tool_call=None)[source]

Bases: object

Structured status event emitted by IdleWatchdog during WAITING_ON_CHILD deferral.

This dataclass is frozen so subscribers cannot accidentally mutate shared state.

The diagnostic dict is a forward-compatible extension point for Phase 3 corroborating signals (workspace_event_delta, oldest_child_seconds, scoped_child_active, etc.). This plan ships only the throttle, transition, suspicion, and hard-stop summary semantics; Phase 3 fields are out of scope.

Parameters:
  • kind (WaitingStatusKind)

  • cumulative_seconds (float)

  • current_run_seconds (float)

  • idle_elapsed_seconds (float)

  • ceiling_seconds (float)

  • suspect_threshold_seconds (float | None)

  • diagnostic (dict[str, str | int | float | bool | list[object]])

  • subagent_activity (str | None)

  • last_subagent_progress_at (float | None)

  • current_subagent_tool_call (str | None)

kind

The type of event (ENTERED, PROGRESS, SUSPECTED_FROZEN, EXITED, HARD_STOP).

Type:

WaitingStatusKind

cumulative_seconds

Cumulative WAITING_ON_CHILD seconds across the session so far.

Type:

float

current_run_seconds

Seconds spent in the current WAITING_ON_CHILD run.

Type:

float

idle_elapsed_seconds

Seconds since last record_activity() call.

Type:

float

ceiling_seconds

The active WAITING_ON_CHILD ceiling for this event.

Type:

float

suspect_threshold_seconds

The suspect_waiting_on_child_seconds threshold, or None.

Type:

float | None

diagnostic

Optional dict of extra diagnostic keys for HARD_STOP events.

Type:

dict[str, str | int | float | bool | list[object]]

subagent_activity

Optional short string (truncated to 200 chars by the watchdog at write time) describing the most recent child-progress observation recorded via record_subagent_work. The watchdog captures the latest raw line so operators see which subagent activity was current at the moment of the event (fires, transitions, suspicion, progress). None when no subagent observation has happened yet. Optional with a default so existing positional callers continue to work without changes.

Type:

str | None

last_subagent_progress_at

Optional monotonic timestamp of the most recent subagent observation that populated subagent_activity. Mirrors the watchdog’s last_subagent_progress_at channel-evidence timestamp so every emitted event carries BOTH the textual description and when it was last observed. Optional with a default so existing positional callers continue to work without changes.

Type:

float | None

current_subagent_tool_call

Optional parsed verb: prefix from subagent_activity (the current tool call the subagent is executing). Mirrors the watchdog’s diagnostic_snapshot()["current_subagent_tool_call"] field so both surfaces carry the same parsed value. Optional with a default so existing positional callers continue to work without changes.

Type:

str | None

ralph.agents.idle_watchdog.waiting_status_kind

Enumeration of waiting status kinds for the idle watchdog.

class ralph.agents.idle_watchdog.waiting_status_kind.WaitingStatusKind(*values)[source]

Bases: StrEnum

Kind of waiting-status event emitted by IdleWatchdog.

ENTERED: transition into WAITING_ON_CHILD deferral. PROGRESS: periodic status update while still waiting (rate-limited). SUSPECTED_FROZEN: cumulative wait crossed suspect threshold; child may be frozen. EXITED: transition out of WAITING_ON_CHILD (activity or drain resumed). HARD_STOP: cumulative ceiling crossed; watchdog about to fire CHILDREN_PERSIST_TOO_LONG. SUBAGENT_PROGRESS: per-subagent progress surface for the waiting-status stream. Reuses the parser-layer ActivityEventKind.SUBAGENT_PROGRESS surface (which already exists at the parser layer for every AgentTransport via the cross-transport visibility test) so the waiting-status stream surfaces the live subagent’s current activity. The emit is rate-limited by TimeoutPolicy.watchdog_subagent_progress_interval_seconds so the new event does NOT introduce additional churn versus the existing PROGRESS cadence (both default to 30 s).

ralph.agents.idle_watchdog.watchdog_fire_reason

Enumeration of reasons the idle watchdog fires.

class ralph.agents.idle_watchdog.watchdog_fire_reason.WatchdogFireReason(*values)[source]

Bases: StrEnum

Why the watchdog decided to fire.

IdleWatchdog reasons (in-stream):

NO_OUTPUT_DEADLINE, NO_OUTPUT_AT_START, CHILDREN_PERSIST_TOO_LONG, SESSION_CEILING_EXCEEDED, REPEATED_IDENTICAL_TOOL_CALL, STRICTLY_STUCK.

PostExitWatchdog reasons (post-exit):

PROCESS_EXIT_HANG, DESCENDANT_HANG.

ralph.agents.idle_watchdog.watchdog_verdict

Enumeration of idle watchdog verdict outcomes.

class ralph.agents.idle_watchdog.watchdog_verdict.WatchdogVerdict(*values)[source]

Bases: StrEnum

Result of a watchdog evaluation cycle.

ralph.agents.invoke

Subprocess-based agent invocation with streaming NDJSON parsing.

This module handles invoking AI agents as subprocesses, parsing their streaming NDJSON output, and managing the lifecycle of the process.

Key features: - Line-by-line streaming from subprocess stdout to parser - tqdm progress bar (or rich when TTY) - loguru structured logging for every NDJSON line - watchdog workspace monitoring for file-change events during execution

exception ralph.agents.invoke.AgentInactivityTimeoutError(agent_name, timeout_seconds, parsed_output=None, opts=None)[source]

Bases: AgentInvocationError

Raised when an agent stalls without producing output.

Parameters:
  • agent_name (str)

  • timeout_seconds (float)

  • parsed_output (list[str] | None)

  • opts (InactivityTimeoutOpts | None)

Return type:

None

exception ralph.agents.invoke.AgentInvocationError(agent_name, returncode, stderr='', parsed_output=None)[source]

Bases: Exception

Raised when agent invocation fails.

Parameters:
  • agent_name (str)

  • returncode (int)

  • stderr (str)

  • parsed_output (list[str] | None)

Return type:

None

agent_name

Name of the agent that failed.

returncode

Process exit code.

stderr

Standard error output.

ralph.agents.invoke.BuildCommandOptions

alias of _BuildCommandOptions

ralph.agents.invoke.CompletionCheckOptions

alias of _CompletionCheckOptions

ralph.agents.invoke.IdleStreamTimeoutError

alias of _IdleStreamTimeoutError

class ralph.agents.invoke.InactivityTimeoutOpts(reason=None, session_resume_safe=False, resumable_session_id=None, diagnostic=None)[source]

Bases: object

Optional parameters for AgentInactivityTimeoutError.

Parameters:
  • reason (WatchdogFireReason | None)

  • session_resume_safe (bool)

  • resumable_session_id (str | None)

  • diagnostic (dict[str, str | int | float | bool | list[object]] | None)

exception ralph.agents.invoke.InteractivePermissionPromptError(agent_name, parsed_output)[source]

Bases: AgentInvocationError

Raised when interactive Claude reaches a permission prompt in unattended mode.

Parameters:
  • agent_name (str)

  • parsed_output (list[str])

Return type:

None

class ralph.agents.invoke.InvokeOptions(workspace_monitor_factory=None, model_flag=None, session_id=None, verbose=False, show_progress=True, workspace_path=None, extra_env=None, unsafe_mode=False, idle_timeout_seconds=None, drain_window_seconds=None, max_waiting_on_child_seconds=None, idle_poll_interval_seconds=None, parent_exit_grace_seconds=None, descendant_wait_timeout_seconds=None, descendant_wait_poll_seconds=None, process_exit_wait_seconds=None, max_session_seconds=None, waiting_status_interval_seconds=None, suspect_waiting_on_child_seconds=None, child_progress_ttl_seconds=None, child_heartbeat_ttl_seconds=None, child_stale_label_ttl_seconds=None, child_exit_reconcile_seconds=None, max_waiting_on_child_no_progress_seconds=None, no_progress_quiet_seconds=None, no_progress_quiet_minimum_invocation_seconds=None, no_progress_quiet_heartbeat_ceiling_seconds=<object object>, post_tool_result_progression_seconds=None, repeated_error_consecutive_threshold=None, repeated_error_window_count=None, repeated_error_window_seconds=None, activity_evidence_ttl_seconds=None, workspace_change_weights=None, process_monitor_enabled=None, subagent_output_capture_enabled=None, subagent_output_poll_interval_seconds=None, os_descendant_only_ceiling_seconds=<object object>, os_descendant_only_suspect_seconds=<object object>, cpu_idle_seconds=<object object>, log_growth_seconds=<object object>, pure=False, system_prompt_file=None, waiting_listener=None, pre_output_listener=None, permission_prompt_listener=None, required_artifact=None, explicit_completion_seen=False, captured_session_id=None, initial_session_id=None, settings_json=None, stop_sentinel_path=None, connectivity_state_provider=None, is_waiting_state_provider=None, subagent_pid_registry=None, subagent_pid_source=None)[source]

Bases: object

Options for agent invocation.

Parameters:
  • workspace_monitor_factory (Callable[[Path, WorkspaceChangeClassifier | None], WorkspaceMonitor | None] | None)

  • model_flag (str | None)

  • session_id (str | None)

  • verbose (bool)

  • show_progress (bool)

  • workspace_path (Path | None)

  • extra_env (dict[str, str] | None)

  • unsafe_mode (bool)

  • idle_timeout_seconds (float | None)

  • drain_window_seconds (float | None)

  • max_waiting_on_child_seconds (float | None)

  • idle_poll_interval_seconds (float | None)

  • parent_exit_grace_seconds (float | None)

  • descendant_wait_timeout_seconds (float | None)

  • descendant_wait_poll_seconds (float | None)

  • process_exit_wait_seconds (float | None)

  • max_session_seconds (float | None)

  • waiting_status_interval_seconds (float | None)

  • suspect_waiting_on_child_seconds (float | None)

  • child_progress_ttl_seconds (float | None)

  • child_heartbeat_ttl_seconds (float | None)

  • child_stale_label_ttl_seconds (float | None)

  • child_exit_reconcile_seconds (float | None)

  • max_waiting_on_child_no_progress_seconds (float | None)

  • no_progress_quiet_seconds (float | None)

  • no_progress_quiet_minimum_invocation_seconds (float | None)

  • no_progress_quiet_heartbeat_ceiling_seconds (float | None | object)

  • post_tool_result_progression_seconds (float | None)

  • repeated_error_consecutive_threshold (int | None)

  • repeated_error_window_count (int | None)

  • repeated_error_window_seconds (float | None)

  • activity_evidence_ttl_seconds (float | None)

  • workspace_change_weights (dict[str, float] | None)

  • process_monitor_enabled (bool | None)

  • subagent_output_capture_enabled (bool | None)

  • subagent_output_poll_interval_seconds (float | None)

  • os_descendant_only_ceiling_seconds (float | None | object)

  • os_descendant_only_suspect_seconds (float | None | object)

  • cpu_idle_seconds (float | None | object)

  • log_growth_seconds (float | None | object)

  • pure (bool)

  • system_prompt_file (str | None)

  • waiting_listener (WaitingStatusListener | None)

  • pre_output_listener (Callable[[], None] | None)

  • permission_prompt_listener (Callable[[str], None] | None)

  • required_artifact (RequiredArtifact | None)

  • explicit_completion_seen (bool)

  • captured_session_id (str | None)

  • initial_session_id (str | None)

  • settings_json (str | None)

  • stop_sentinel_path (Path | None)

  • connectivity_state_provider (Callable[[], str | None] | None)

  • is_waiting_state_provider (Callable[[], bool] | None)

  • subagent_pid_registry (SubagentPidRegistry | None)

  • subagent_pid_source (SubagentPidSource | None)

class ralph.agents.invoke.InvokeRuntimeOptions(verbose=False, show_progress=True, workspace_path=None, extra_env=None, pure=False, session_id=None, system_prompt_file=None, waiting_listener=None, pre_output_listener=None, permission_prompt_listener=None, required_artifact=None, connectivity_state_provider=None, is_waiting_state_provider=None)[source]

Bases: object

Non-timeout runtime options for agent invocation.

Parameters:
  • verbose (bool)

  • show_progress (bool)

  • workspace_path (Path | None)

  • extra_env (dict[str, str] | None)

  • pure (bool)

  • session_id (str | None)

  • system_prompt_file (str | None)

  • waiting_listener (WaitingStatusListener | None)

  • pre_output_listener (Callable[[], None] | None)

  • permission_prompt_listener (Callable[[str], None] | None)

  • required_artifact (RequiredArtifact | None)

  • connectivity_state_provider (Callable[[], str | None] | None)

  • is_waiting_state_provider (Callable[[], bool] | None)

exception ralph.agents.invoke.OpenCodeResumableExitError(agent_name, session_id=None, *, last_observed_tool_call=None, last_evidence_summary=None, elapsed_seconds=None, transcript_tail=())[source]

Bases: AgentInvocationError

Raised when an agent session exits without required completion evidence.

The session can be continued; the runner maps this into a session-preserving retry.

The exception carries the captured transport-level session id via resumable_session_id so the failure classifier’s typed-cause branch can thread it through the FailureCategory.AGENT resume path (see module docstring for the full contract).

R7 root-cause diagnostic surface (NEW): the exception also carries four keyword-only diagnostic attributes – last_observed_tool_call, last_evidence_summary, elapsed_seconds, and transcript_tail – that capture the watchdog state at the moment of the rc=0 exit. The four attributes are preserved on the exception for programmatic access; the two bounded-size fields (last_observed_tool_call and elapsed_seconds) are ALSO surfaced in the exception message so a logged traceback is actionable. The verbose fields (last_evidence_summary and transcript_tail) are programmatic-only by design – not rendered into the message to avoid unbounded message size.

Parameters:
  • agent_name (str)

  • session_id (str | None)

  • last_observed_tool_call (str | None)

  • last_evidence_summary (str | None)

  • elapsed_seconds (float | None)

  • transcript_tail (tuple[str, ...])

Return type:

None

exception ralph.agents.invoke.PiContextExhaustedExitError(agent_name)[source]

Bases: AgentInvocationError

Raised when Pi exits cleanly after a length-limited model turn.

Parameters:

agent_name (str)

Return type:

None

ralph.agents.invoke.ProcessReaderCtx

alias of _ProcessReaderCtx

class ralph.agents.invoke.ResolvedInvocationRuntime(agent_env=None, server_env=None, mcp_endpoint=None, cleanup=None)[source]

Bases: object

Resolved runtime configuration for a single agent invocation.

The optional cleanup hook is invoked by invoke_agent in its finally block after the agent subprocess has finished (success, failure, or cancellation). It is the documented release path for transport-specific resources allocated during resolve() — the primary example is the per-invocation Codex CODEX_HOME directory allocated by CodexRuntimeResolver (see ralph.mcp.transport.codex.release_codex_home).

Lifetime contract:

  • cleanup MUST be safe to call exactly ONCE; invoke_agent treats it as idempotent only via the implementation (e.g. release_codex_home returns False on a second call without raising, so it is safe even if a caller races the finally block).

  • cleanup MAY be None for resolvers that allocate no per-invocation resources (Claude, OpenCode, Nanocoder, Agy, Generic, Pi). The invoke_agent finally block tolerates a None hook.

  • cleanup is INVOKED EVEN IF THE SUBPROCESS RAISES. The hook is the mechanism that prevents a long-lived process from accumulating per-invocation temp directories (or other transport resources) when an agent run crashes.

Why a callable and not a single-method protocol: the resolver closes over whatever transport-specific state it needs to release (for Codex, the codex_home path string). Encoding the per-invocation lifetime into a closure keeps the seam narrow without leaking the resource registry’s mutable state into the public API.

Parameters:
  • agent_env (dict[str, str] | None)

  • server_env (dict[str, str] | None)

  • mcp_endpoint (str | None)

  • cleanup (Callable[[], None] | None)

exception ralph.agents.invoke.UnsupportedMcpTransportError[source]

Bases: RuntimeError

Raised when MCP-backed execution is requested for an unsupported transport.

class ralph.agents.invoke.WatchdogFireReason(*values)[source]

Bases: StrEnum

Why the watchdog decided to fire.

IdleWatchdog reasons (in-stream):

NO_OUTPUT_DEADLINE, NO_OUTPUT_AT_START, CHILDREN_PERSIST_TOO_LONG, SESSION_CEILING_EXCEEDED, REPEATED_IDENTICAL_TOOL_CALL, STRICTLY_STUCK.

PostExitWatchdog reasons (post-exit):

PROCESS_EXIT_HANG, DESCENDANT_HANG.

class ralph.agents.invoke.WorkspaceMonitor(workspace_path, *, now=None, on_event=None, classifier=None)[source]

Bases: object

Monitors workspace directory for file changes during agent execution.

This allows the pipeline to detect when an agent has completed significant work by watching for file modifications in the workspace.

Parameters:
  • workspace_path (Path)

  • now (Callable[[], float] | None)

  • on_event (WorkspaceEventCallback | None)

  • classifier (WorkspaceChangeClassifier | None)

property changed_files: set[str]

Set of file paths that changed during monitoring.

classify_path(src_path)[source]

Classify a single workspace path via the configured classifier.

When no classifier is configured, every path is classified as OTHER with weight 1.0 (the legacy behavior: every file change counts as activity). This helper is the canonical seam for tests and dry-run checks that want to inspect the classifier output without recording an event.

Parameters:

src_path (str)

Return type:

tuple[WorkspaceChangeKind, float]

property event_count: int

Number of file change events detected.

property last_event_at: float | None

Monotonic-clock timestamp of the most recent file change event.

Returns None when no event has been observed since the monitor was constructed (or since the last reset_last_event_at call). The watchdog’s per-channel evidence surface consumes this value via the last_workspace_event_at field on CorroborationSnapshot; a fresh workspace channel defers the NO_OUTPUT_DEADLINE verdict while the channel age is below activity_evidence_ttl_seconds.

record_event(src_path)[source]

Record a file change event.

Classifies the event via the configured WorkspaceChangeClassifier (or the legacy OTHER / 1.0 fallback when no classifier is configured). Events with weight 0.0 are dropped without updating last_event_at, the counter, or invoking on_event. Events with weight 1.0 update the timestamp and counter and invoke on_event with the (kind, weight) pair when the callback accepts 2 args.

The watchdog’s per-channel evidence surface consumes this timestamp via the last_workspace_event_at field on CorroborationSnapshot so a workspace-event channel is fresh exactly as long as the production clock is recent.

When an on_event callback has been registered (via the constructor or set_on_event), it is invoked AFTER the timestamp and counter are updated so the watchdog observes a fully-consistent state. The callback is invoked in a try/except so a buggy callback cannot break the file-event path; the failure is logged at DEBUG.

Parameters:

src_path (str) – Path to the changed file.

Return type:

None

reset_last_event_at()[source]

Reset last_event_at (and the event counter) to a clean state.

Intended for test isolation: a long-lived WorkspaceMonitor in a test fixture may have observed events from a prior case; calling this clears the timestamp so the next record_event produces a fresh baseline.

Return type:

None

set_on_event(on_event)[source]

Register (or clear) the per-event callback invoked at the end of record_event.

Production readers construct the WorkspaceMonitor BEFORE the per-run watchdog is created (the monitor is built in invoke_agent while the watchdog lives inside the reader’s read_lines generator), so the constructor cannot bind the watchdog’s record_workspace_event directly. The reader registers the callback here, immediately after the watchdog is created, so every subsequent file change is visible to the activity-aware verdict as workspace channel evidence.

Pass None to clear the callback (e.g. when the per-run watchdog is torn down at run end).

Parameters:

on_event (Callable[[], None] | Callable[[WorkspaceChangeKind, float], None] | None) – Callable invoked with no arguments at the end of record_event (legacy 0-arg form) or with (kind, weight) (production 2-arg form) after the timestamp and counter are updated. Exceptions raised by the callback are suppressed by record_event so a buggy callback cannot break the file-event path.

Return type:

None

start()[source]

Start monitoring the workspace for file changes.

Return type:

None

stop()[source]

Stop monitoring the workspace.

Return type:

None

ralph.agents.invoke.agy_workspace_mcp_endpoint(workspace_path, endpoint, *, unsafe_mode=False)[source]

Write a run-scoped Ralph MCP config to AGY’s global path and restore it after exit.

Concurrency safety: this context manager serialises concurrent callers with a single threading.Lock and writes the merged config atomically (via os.replace) so a parallel AGY session cannot observe a torn write or clobber a sibling session’s restore step. The lock is process-local: it serialises within one Ralph process but does not block a separate AGY launch invoked by another process. Cross-process safety relies on the atomic replace below and on the original-bytes read happening INSIDE the critical section (so a parallel sibling cannot interleave its own write/restore between our read and our restore).

Parameters:
  • workspace_path (Path)

  • endpoint (str)

  • unsafe_mode (bool)

Return type:

Iterator[None]

ralph.agents.invoke.build_command(config, prompt_file, *, options=None)

Build the command line for agent invocation.

Parameters:
  • config (AgentConfig) – Agent configuration.

  • prompt_file (str) – Path to prompt file.

  • options (_BuildCommandOptions | None)

Returns:

List of command arguments.

Return type:

list[str]

ralph.agents.invoke.build_invoke_options_from_config(general_config, runtime=None)[source]

Build InvokeOptions from GeneralConfig, mapping all timeout fields.

Parameters:
  • general_config (GeneralConfig)

  • runtime (InvokeRuntimeOptions | None)

Return type:

InvokeOptions

ralph.agents.invoke.build_nanocoder_mcp_config(existing, endpoint, *, always_allow=(), unsafe_mode=False, workspace_path=None, env=None)[source]

Build a Nanocoder MCP payload with Ralph injected as the managed server.

Parameters:
  • existing (str | None)

  • endpoint (str)

  • always_allow (tuple[str, ...])

  • unsafe_mode (bool)

  • workspace_path (Path | None)

  • env (Mapping[str, str] | None)

Return type:

tuple[str, tuple[UpstreamMcpServer, …]]

ralph.agents.invoke.build_opencode_provider_config(existing, endpoint, *, unsafe_mode=False)[source]

Build a full OpenCode config JSON with Ralph MCP and return it with upstream servers.

Parameters:
  • existing (str | None)

  • endpoint (str)

  • unsafe_mode (bool)

Return type:

tuple[str, tuple[UpstreamMcpServer, …]]

ralph.agents.invoke.check_agent_available(config)[source]

Check if an agent command is available.

Parameters:

config (AgentConfig) – Agent configuration.

Returns:

True if agent command exists and is executable.

Return type:

bool

ralph.agents.invoke.check_process_result(handle, agent_name, parsed_output=None, check_options=None, *, _clock=None)

Check subprocess return code and raise error if non-zero.

For session-continuing agents, exit 0 without required completion evidence raises OpenCodeResumableExitError so the runner can continue the same session. When the process exits but child agents are still running, this function waits up to policy.descendant_wait_timeout_seconds for the tree to quiesce before re-evaluating completion signals.

Parameters:
  • handle (ManagedProcess | ManagedPtyProcess) – Completed managed process.

  • agent_name (str) – Name of the agent.

  • _clock (Clock | None) – Injectable Clock for testing; production callers omit this.

  • parsed_output (list[str] | None)

  • check_options (_CompletionCheckOptions | None)

Raises:
  • AgentInvocationError – If process exited with non-zero code.

  • OpenCodeResumableExitError – If the agent session exited without required completion evidence and no child agents are still running.

Return type:

None

ralph.agents.invoke.extract_transport_session_id(raw_output)[source]

Extract only top-level transport/runtime session IDs from output lines.

Parameters:

raw_output (list[str] | tuple[str, ...])

Return type:

str | None

ralph.agents.invoke.extract_transport_session_id_from_line(line)[source]

Extract only top-level transport/runtime session IDs from one line.

Parameters:

line (str)

Return type:

str | None

ralph.agents.invoke.extract_visible_tui_transport_session_id(text)[source]

Extract transport session IDs from visible TUI text only.

This intentionally excludes generic session_id=... patterns so assistant or tool text cannot masquerade as transport session metadata.

Parameters:

text (str)

Return type:

str | None

ralph.agents.invoke.fresh_session_options(opts, *, prior_session_id=None)[source]

Return a NEW InvokeOptions instance with session_id cleared.

Used by every ordinary new-phase transition so the new phase always starts a fresh session, even when the prior phase recovered via resolve_resume_session_id and threaded an id forward.

The prior_session_id parameter is accepted for forward compatibility but MUST NOT be written back into session_id — ordinary new-phase transitions are explicitly fresh.

Pure function: no side effects, no I/O, no clock reads.

Parameters:
  • opts (InvokeOptions)

  • prior_session_id (str | None)

Return type:

InvokeOptions

ralph.agents.invoke.get_process_manager(*, policy=None)[source]

Return the module-level ProcessManager singleton, creating it on first call.

Parameters:

policy (ProcessManagerPolicy | None)

Return type:

ProcessManager

ralph.agents.invoke.invoke_agent(config, prompt_file, *, options=None, _clock=None)[source]

Invoke agent, yield parsed output lines as they arrive.

Parameters:
  • config (AgentConfig) – Agent configuration specifying command and flags.

  • prompt_file (str) – Path to PROMPT.md file to pass to agent.

  • options (InvokeOptions | None) – Optional invocation options.

  • _clock (Clock | None) – Injectable Clock for testing; production callers omit this.

Yields:

Raw agent output lines (before parsing).

Raises:

AgentInvocationError – If agent exits with non-zero code.

Return type:

Iterator[str]

ralph.agents.invoke.load_existing_agy_upstream_servers(workspace_path=None)[source]

Read AGY’s MCP config files and return any upstream MCP servers found.

Parameters:

workspace_path (Path | None) – Optional workspace path for workspace-level AGY config.

Returns:

Tuple of UpstreamMcpServer objects found in AGY config files.

Return type:

tuple[UpstreamMcpServer, …]

ralph.agents.invoke.load_existing_claude_upstream_servers(workspace_path=None)[source]

Read Claude’s MCP config files and return any upstream MCP servers found.

Parameters:

workspace_path (Path | None)

Return type:

tuple[UpstreamMcpServer, …]

ralph.agents.invoke.load_existing_cursor_upstream_servers(workspace_path=None)[source]

Read Cursor’s MCP config files and return any upstream MCP servers found.

Parameters:

workspace_path (Path | None) – Optional workspace path for the workspace-local .cursor/mcp.json.

Returns:

Tuple of UpstreamMcpServer objects found in Cursor config files. The Ralph entry is filtered out so it does not collide with the run-scoped ralph injection.

Return type:

tuple[UpstreamMcpServer, …]

ralph.agents.invoke.load_existing_nanocoder_upstream_servers(workspace_path, *, env=None)[source]

Load Nanocoder MCP servers from documented config locations.

Parameters:
  • workspace_path (Path | None)

  • env (dict[str, str] | None)

Return type:

tuple[UpstreamMcpServer, …]

ralph.agents.invoke.policy_from_options(opts)

Build a TimeoutPolicy from InvokeOptions, falling back to policy defaults for None fields.

Parameters:

opts (InvokeOptions)

Return type:

TimeoutPolicy

ralph.agents.invoke.prepare_codex_home_with_upstreams(endpoint, *, workspace_path, existing_home, system_prompt_file, unsafe_mode=False)[source]

Prepare an isolated Codex home directory and return its path with upstream servers.

Parameters:
  • endpoint (str | None)

  • workspace_path (Path | None)

  • existing_home (str | None)

  • system_prompt_file (str | None)

  • unsafe_mode (bool)

Return type:

tuple[str, tuple[UpstreamMcpServer, …]]

ralph.agents.invoke.recovery_action_for_failure_reason(failure_reason, *, has_prior_session, reset_tool_registry=False)[source]

Map a stored failure reason to a recovery action.

Used by the pipeline executor to decide whether the next attempt should resume the prior session, request a new_session_with_id (e.g. on a stale-session error), or start fresh (no prior session to resume).

The mapping is intentionally narrow and explicit:

  • AgentInactivityTimeoutError (with a prior session) -> resume

  • OpenCodeResumableExitError (with a prior session) -> resume

  • tool-availability failure (with a prior session AND reset_tool_registry=True) -> resume (NEW BEHAVIOR; the pre-fix code returned fresh here, which made every tool-availability retry re-read the prompt).

  • stale/invalid session id family (with a prior session) -> fresh

  • everything else -> fresh

Parameters:
  • failure_reason (str) – The exception class name (or wire-level error substring) from the last failed attempt. Empty string means “no failure recorded” (the cleared state on the success path).

  • has_prior_session (bool) – True when the orchestrator has a prior session id to resume (or annotate a fresh session with).

  • reset_tool_registry (bool) – NEW BEHAVIOR. When True, indicates the last failure was classified as a tool-availability failure (the live wire-level No such tool available: mcp__<server>__<tool> error). The helper returns 'resume' instead of 'fresh' so the next attempt continues the prior session (the tool registry has been rebuilt via RestartAwareMcpBridge.reset_tool_registry() so a fresh session is unnecessary). Defaults to False to preserve the existing behavior on the pre-existing branches.

Return type:

AgentRetryAction

ralph.agents.invoke.resolve_invocation_runtime(config, extra_env, workspace_path, *, _base_env=None, system_prompt_file=None, unsafe_mode=False)[source]

Build the runtime configuration needed to launch an agent.

Resolves transport-specific environment variables, MCP server configuration, and endpoint address from config and extra_env. Returns a ResolvedInvocationRuntime whose fields are ready to pass to the subprocess launcher.

Parameters:
  • config (AgentConfig)

  • extra_env (dict[str, str] | None)

  • workspace_path (Path | None)

  • _base_env (Mapping[str, str] | None)

  • system_prompt_file (str | None)

  • unsafe_mode (bool)

Return type:

ResolvedInvocationRuntime

ralph.agents.invoke.resolve_resume_session_id(*, has_prior_session, prior_session_id, recovery_action)[source]

Return the session id to thread into the next attempt, or None for fresh.

This is the single decision point for session continuation. The per-transport resume flag SYNTAX is owned separately by each agent’s session_flag template; this helper only decides the id.

Parameters:
  • has_prior_session (bool) – True when the orchestrator has a prior session id to continue from. When False, the helper always returns None.

  • prior_session_id (str | None) – The session id from the prior attempt. May be None when has_prior_session is False; must be non-empty when has_prior_session is True.

  • recovery_action (str) –

    The decision the recovery controller made. One of:

    • "fresh": ignore any prior session id; start anew.

    • "resume": continue the prior session.

    • "new_session_with_id": reuse the supplied id for a new session (transports that accept a creation-time session id).

Returns:

The session id the caller should record in pipeline state and thread into the agent invocation, or None to start a fresh session.

Raises:

ValueError – When recovery_action is unknown or has_prior_session=True but prior_session_id is empty/None.

Return type:

str | None

ralph.agents.invoke.run_subprocess_and_read_lines(cmd, ctx)

Run subprocess and yield output lines.

Parameters:
  • cmd (list[str]) – Command to execute.

  • ctx (_AgentRunCtx) – Agent run context with configuration and options.

Yields:

Output lines from the subprocess.

Return type:

Iterator[str]

ralph.agents.invoke.wait_for_descendants_then_recheck(handle, opts, parsed_output, *, clock=None)

Wait for descendant processes to finish, then re-evaluate completion signals.

Polls the execution strategy’s classify_exit at policy.descendant_wait_poll_seconds intervals until either the tree is quiet (state != WAITING_ON_CHILD) or the deadline elapses. This allows artifacts written by background subagents to become visible before OpenCodeResumableExitError is raised.

Parameters:
  • handle (ManagedProcess | ManagedPtyProcess) – Completed parent process handle.

  • opts (_CompletionCheckOptions) – Completion check options including liveness_probe and policy.

  • parsed_output (list[str]) – Raw NDJSON output lines from the agent.

  • clock (Clock | None) – Injectable Clock; defaults to SystemClock.

Returns:

TERMINAL_COMPLETE if tree quiessed and completion signals present. RESUMABLE_CONTINUE if deadline elapsed with children still alive (fallback to retry rather than silent success). WAITING_ON_CHILD is only returned during the active polling loop, never after deadline.

Return type:

AgentExecutionState

ralph.agents.parsers

Agent output parsers: one per agent transport, plus a generic fallback.

This package converts raw stdout lines from an agent subprocess into structured AgentOutputLine objects for the invocation engine in ralph.agents.invoke.

Main entry points:

  • get_parser(parser_type) — factory function; maps a parser type name string ('claude', 'claude_interactive', 'codex', 'gemini', 'opencode', 'nanocoder', 'pi', 'generic') to the corresponding parser instance. Raises ValueError for unknown names.

  • AgentParser — the protocol that all parsers implement; defines parse.

  • AgentOutputLine — structured parse result (type, content, raw, metadata).

  • ClaudeParser — parses Claude stream-JSON NDJSON output.

  • ClaudeInteractiveParser — parses interactive Claude transcript output.

  • CodexParser — parses Codex per-event JSON output.

  • GeminiParser — parses Gemini output.

  • NanocoderParser — parses Nanocoder interactive PTY output.

  • OpenCodeParser — parses OpenCode NDJSON stream output.

  • PiParser — parses pi.dev AgentSessionEvent NDJSON stream output.

  • GenericParser — fallback parser for unknown or plain-text agent output.

Parser selection is driven by AgentConfig.json_parser (a JsonParserType enum value in ralph.config.enums) or, for agents registered via register_agent_support(), by the agent’s command name when json_parser == JsonParserType.GENERIC. The runtime calls get_parser() with the resolved key and consumes normalized lines through parser.parse().

To add a parser for a new agent transport, create a module in this package, implement AgentParser, and register the class in both the default-catalog seed (ralph.agents.catalog._DEFAULT_BUILTIN_PARSER_TYPES) and __all__.

ralph.agents.parsers.agy

Parser for the AGY v1.0.8 –print wire format.

Source of truth: ralph-workflow/tmp/agy-source-of-truth.txt.

AGY –print mode emits plain-text model responses on stdout, one line at a time. The parser classifies every plain-text line as AgentOutputLine(type='text') (NOT type='raw') so the smoke report’s “Observed output:” section renders model content via _render_text_line (in ralph.pipeline.activity_stream) instead of the literal raw type label via _render_metadata_event_line.

The parser inherits from NdjsonParserBase, which owns the 6 shared NDJSON behaviours: data: SSE prefix strip, [DONE] short-circuit, non-dict-JSON-to-raw fallback, lifecycle-event suppression, error extraction, and JSON-dict dispatch. AGY v1.0.8 –print mode does NOT emit JSON lifecycle or error events; the inherited behaviour is preserved as a safe default for any future AGY –json flag.

The [plain] tool: NAME convention from GenericParser is intentionally NOT classified as tool_use here. That convention is a GenericParser convention, not an AGY wire-format fact documented in the source of truth. AGY tool activity is reported via the persisted smoke_test_result artifact (see smoke_plumbing._agy_tool_activity_seen).

Behaviour specifics:

  • A single plain-text line is buffered, then emitted at iterator exhaustion (or at the next paragraph-boundary flush) as a single text event. This coalesces consecutive short lines into one coherent text block matching the GenericParser coalescing semantics.

  • The Task declared complete: marker is treated as plain text (not a structured completion signal). The smoke detector at smoke_plumbing._explicit_completion_seen scans the raw transcript for the substring, so the marker must pass through as a regular text event rather than being filtered as raw.

  • Empty input (the documented quota-exhausted failure mode in agy-source-of-truth.txt) yields zero events, allowing the smoke plumbing to surface the empty-stdout diagnostic for the live case.

class ralph.agents.parsers.agy.AgyParser(subagent_pid_registry=None, subagent_source_label=None)[source]

Bases: NdjsonParserBase

Plain-text parser for AGY v1.0.8 –print output.

Inherits the NDJSON state machine from NdjsonParserBase (SSE strip, [DONE] short-circuit, lifecycle suppression, error extraction, JSON-dict dispatch, non-dict-JSON-to-raw). Overrides _classify_non_json_line() so the AGY –print plain-text stream is classified as type='text' and coalesced via TextAccumulator into coherent blocks.

Parameters:
  • subagent_pid_registry (SubagentPidRegistry | None)

  • subagent_source_label (str | None)

flush_accumulators()[source]

Drain the text accumulator and yield the buffered text event.

Return type:

Iterator[AgentOutputLine]

ralph.agents.parsers.base

Base types for agent output parsing.

This module defines the parser protocol and shared text-block helpers.

ralph.agents.parsers.base.extract_error_message(obj)[source]

Extract an error message string from a parsed JSON object.

Resolution order (union of all per-parser bodies): 1. obj[‘error’] dict -> message, type, or name field (claude/codex/opencode/gemini) 2. obj[‘error’] non-empty string (codex/generic) 3. obj.get(‘message’) (codex/opencode/generic) 4. obj.get(‘error’) non-dict truthy value (codex fallback) 5. obj.get(‘msg’) (generic) 6. ‘unknown error’

Note: claude.py previously returned ‘unknown’ (not ‘unknown error’) when error_obj was not a dict. The unified helper returns ‘unknown error’ in that branch, a one-character behavior change documented in the module docstring.

Parameters:

obj (object)

Return type:

str

ralph.agents.parsers.base.is_lifecycle_event(event_type)[source]

Return True when event_type is a wire-format lifecycle event.

Single owner of the lifecycle check for every wire-format parser.

Parameters:

event_type (str)

Return type:

bool

ralph.agents.parsers.base.is_lifecycle_kind(kind)[source]

Return True when kind is a transcript-event lifecycle kind.

Single owner of the lifecycle-kind check for the claude_interactive family of parsers.

Parameters:

kind (str)

Return type:

bool

ralph.agents.parsers.base.is_session_metadata_event(parsed_json)[source]

Return True when parsed_json is a transport-level session event.

Delegates to the canonical extract_transport_session_id() so every parser observes the same JSON shape.

Parameters:

parsed_json (object)

Return type:

bool

ralph.agents.parsers.claude

Parser for Claude’s NDJSON streaming format.

class ralph.agents.parsers.claude.ClaudeParser(subagent_pid_registry=None, subagent_source_label=None)[source]

Bases: NdjsonParserBase

Parser for Claude’s NDJSON streaming output with robust delta accumulation.

Text deltas are accumulated into coherent blocks before emission, flushing on: - content_block_stop (end of a content block) - message_stop (end of the message) - \n\n paragraph boundary (incremental surfacing of long responses)

Thinking deltas (thinking_delta) are accumulated separately from text deltas and emitted as type="thinking" lines.

Inherits from NdjsonParserBase and delegates the NDJSON scaffolding (data: strip, [DONE] short-circuit, JSON parse, error extraction, lifecycle interception) to the base layer via classify_line(). Claude-specific behavior stays in subclass hooks:

  • classify_line() first tries the prefixed-transcript parser ([claude]:, claude/...:) and only delegates to the base when that hook returns None.

  • _handle_lifecycle_event() carries the claude-specific lifecycle side effects (message_start recording, message_stop flush, content_block_stop flush) and returns None for lifecycle events the subclass wants to dispatch (e.g. assistant / user / thinking).

  • _dispatch_json_object() maps the per-event vocabulary (stream_event, content_block_delta, content_block_start, assistant, result, error) to AgentOutputLine types and drives the per-content-block accumulator state.

Parameters:
  • subagent_pid_registry (SubagentPidRegistry | None)

  • subagent_source_label (str | None)

classify_line(line)[source]

Classify a single raw NDJSON line.

Order of operations:

  1. Strip the line and short-circuit on empty.

  2. Try the claude-specific prefixed-transcript parser (e.g. claude/sonnet: hello). If it returns a non-None list, yield from it and return.

  3. Delegate the remaining NDJSON path to NdjsonParserBase which owns the data: prefix strip, [DONE] short-circuit, JSON parse dispatch, error extraction, and lifecycle-event interception. The base calls back into _handle_lifecycle_event() for the claude-specific lifecycle side effects (message_start recording, message_stop flush, content_block_stop flush) and into _dispatch_json_object() for the per-event dispatch (which routes claude’s assistant / user / thinking events through the subclass hook rather than suppressing them as the base’s default lifecycle policy would).

Parameters:

line (str)

Return type:

Iterator[AgentOutputLine]

flush_accumulators()[source]

Default no-op flush. Subclasses with accumulator state override.

Return type:

Iterator[AgentOutputLine]

ralph.agents.parsers.codex

Parser for Codex’s NDJSON streaming format.

class ralph.agents.parsers.codex.CodexParser(subagent_pid_registry=None, subagent_source_label=None)[source]

Bases: NdjsonParserBase

Parser for Codex’s NDJSON streaming output with robust delta accumulation.

Text deltas are accumulated into coherent blocks before emission, flushing on: - response.completed / turn.completed / message_stop (end of message) - \n\n paragraph boundary (incremental surfacing of long responses) - Iterator exhaustion (final flush via flush_accumulators())

Inherits from NdjsonParserBase which owns the data: strip, [DONE] short-circuit, JSON parse dispatch, lifecycle suppression, and error extraction. The subclass _dispatch_json_object delegates to _CodexDispatch for the per-event-type routing.

Parameters:
  • subagent_pid_registry (SubagentPidRegistry | None)

  • subagent_source_label (str | None)

flush_accumulators()[source]

Default no-op flush. Subclasses with accumulator state override.

Return type:

Iterator[AgentOutputLine]

ralph.agents.parsers.cursor

Parser for the Cursor Agent CLI --output-format stream-json NDJSON wire format.

The Cursor Agent CLI agent binary, when invoked with --output-format stream-json, emits one JSON line per event. The documented event vocabulary includes (per Cursor’s CLI --help and the documented Cursor Agent stream-json envelope):

  • system - status events carrying the system message as the message or content field. Surfaced as AgentOutputLine(type='status') so the runtime can render the message via the standard status pipeline.

  • user - input echo (the user prompt Ralph already sent; these are NOT the agent’s own output). Suppressed (mirror the pi/agy behavior of not re-emitting already-known input).

  • assistant - assistant turn events. Carries a message object with a content array of typed blocks. Blocks of type text surface as type='text' via TextAccumulator coalescing; blocks of type thinking surface as type='thinking'.

  • thinking - standalone thinking event carrying a text or thinking delta. Surfaced as type='thinking'.

  • tool_call - tool invocation event carrying a toolName (or name) and args. Surfaced as type='tool_use' so the watchdog sees real tool activity.

  • tool_result - tool result event carrying the tool name and a result (or output or content) field. Surfaced as type='tool_result' for the success path; when the event carries is_error=true (or error) it is surfaced as type='error' so the watchdog can see the failure.

  • result - the documented end-of-turn marker. Surfaced as type='stop' (the canonical completion signal) after any pending text/thinking accumulators are flushed.

Cursor’s --stream-partial-output flag (when the operator opts in via [agents.cursor].streaming_flag) emits incremental text deltas on the assistant text_delta sub-events; the parser coalesces those deltas into coherent blocks via the shared TextAccumulator exactly like the pi parser does for its streaming text deltas.

Inherits from NdjsonParserBase which owns the 6 shared NDJSON behaviors: data: strip, [DONE] short-circuit, JSON parse dispatch, lifecycle suppression, error extraction, and JSON-dict dispatch. The subclass _dispatch_json_object routes each event through a per-event-type handler map so the wire-format vocabulary is the single owner of what each event type emits.

class ralph.agents.parsers.cursor.CursorParser(subagent_pid_registry=None, subagent_source_label=None)[source]

Bases: NdjsonParserBase

Parser for the Cursor Agent CLI --output-format stream-json wire format.

Text and thinking deltas are accumulated into coherent blocks via TextAccumulator. Flushing happens on:

  • result (the documented end-of-turn marker)

  • Iterator exhaustion (final flush via flush_accumulators())

Inherits from NdjsonParserBase which owns the 6 shared NDJSON behaviors (data: strip, [DONE] short-circuit, JSON parse dispatch, lifecycle suppression, error extraction, JSON-dict dispatch). The subclass _dispatch_json_object delegates to _CursorDispatch for the per-event-type routing.

Parameters:
  • subagent_pid_registry (SubagentPidRegistry | None)

  • subagent_source_label (str | None)

flush_accumulators()[source]

Default no-op flush. Subclasses with accumulator state override.

Return type:

Iterator[AgentOutputLine]

ralph.agents.parsers.gemini

Parser for Gemini’s SSE+JSON streaming format.

Gemini emits Server-Sent Events (SSE) where each event contains a JSON payload. This parser handles the SSE format and normalizes Gemini output to AgentOutputLine instances with robust delta accumulation.

class ralph.agents.parsers.gemini.GeminiParser(subagent_pid_registry=None, subagent_source_label=None)[source]

Bases: NdjsonParserBase

Parser for Gemini’s SSE+JSON streaming output with robust delta accumulation.

Gemini uses SSE with data: lines containing JSON payloads. Each payload has a “type” field indicating what kind of content it carries. Text deltas are accumulated into coherent blocks before emission, flushing on: - done / stop / message_end (end of message) - \n\n paragraph boundary (incremental surfacing of long responses) - Iterator exhaustion (final flush via flush_accumulators())

Inherits from NdjsonParserBase which owns the data: strip, [DONE] short-circuit, JSON parse dispatch, lifecycle suppression, and error extraction. The subclass _dispatch_json_object delegates to _GeminiDispatch for the per-event-type routing.

Parameters:
  • subagent_pid_registry (SubagentPidRegistry | None)

  • subagent_source_label (str | None)

flush_accumulators()[source]

Default no-op flush. Subclasses with accumulator state override.

Return type:

Iterator[AgentOutputLine]

ralph.agents.parsers.generic

Generic NDJSON parser for other agents.

This parser handles NDJSON output from agents that don’t have a dedicated parser. It attempts to extract text content and error information from common NDJSON formats, with robust delta accumulation for streaming text responses.

class ralph.agents.parsers.generic.GenericParser(subagent_pid_registry=None, subagent_source_label=None)[source]

Bases: NdjsonParserBase

Generic NDJSON parser for unknown or simple agent formats.

This parser handles NDJSON by: 1. Parsing each line as JSON 2. Looking for common text fields (content, text, message, output) 3. Accumulating short text content and flushing on paragraph boundaries 4. Extracting error information 5. Falling back to raw line storage for unparseable content

Text deltas are accumulated into coherent blocks before emission, flushing on: - \n\n paragraph boundary (incremental surfacing of long responses) - Stop/done markers (end of message) - Iterator exhaustion (final flush via flush_accumulators())

Short content (below threshold) that doesn’t end with \n\n is treated as a streaming delta and accumulated. Content at or above the threshold, or ending with \n\n, is emitted immediately.

Field priority for content extraction: 1. content, text, message, output, response, result (type=’text’) 2. thought, reasoning (type=’thinking’) — only when no higher-priority field matches

Inherits from NdjsonParserBase which owns the data: strip, [DONE] short-circuit, JSON parse dispatch, lifecycle suppression, and error extraction. The subclass overrides _classify_non_json_line() to keep the plain [plain] tool: NAME convention and _dispatch_json_object() to drive the per-content extractor + accumulator path.

Parameters:
  • subagent_pid_registry (SubagentPidRegistry | None)

  • subagent_source_label (str | None)

flush_accumulators()[source]

Default no-op flush. Subclasses with accumulator state override.

Return type:

Iterator[AgentOutputLine]

ralph.agents.parsers.nanocoder

Nanocoder interactive output parser.

class ralph.agents.parsers.nanocoder.NanocoderParser(subagent_pid_registry=None, subagent_source_label=None)[source]

Bases: GenericParser

Parser for Nanocoder’s interactive PTY stream.

Nanocoder redraws a terminal UI while it works. Many PTY frames contain only cursor movement, erase codes, or whitespace after VT normalization. The generic parser turns those frames into visible raw events, which floods operator output. Nanocoder keeps GenericParser’s plain-text tool marker support, but suppresses control-only frames.

Parameters:
  • subagent_pid_registry (SubagentPidRegistry | None)

  • subagent_source_label (str | None)

ralph.agents.parsers.opencode

Parser for OpenCode’s NDJSON streaming format.

class ralph.agents.parsers.opencode.OpenCodeParser(subagent_pid_registry=None, subagent_source_label=None)[source]

Bases: NdjsonParserBase

Parser for OpenCode’s NDJSON streaming output with robust delta accumulation.

Text deltas are accumulated into coherent blocks before emission, flushing on: - step_finish / done (end of step/message) - \n\n paragraph boundary (incremental surfacing of long responses) - Iterator exhaustion (final flush via flush_accumulators())

Inherits from NdjsonParserBase which owns the data: strip, [DONE] short-circuit, JSON parse dispatch, lifecycle suppression, and error extraction. The subclass _dispatch_json_object delegates to _OpenCodeDispatch for the per-event-type routing.

Parameters:
  • subagent_pid_registry (SubagentPidRegistry | None)

  • subagent_source_label (str | None)

flush_accumulators()[source]

Default no-op flush. Subclasses with accumulator state override.

Return type:

Iterator[AgentOutputLine]

ralph.agents.parsers.pi

Parser for Pi’s AgentSessionEvent NDJSON streaming format.

Pi (https://pi.dev) is a terminal coding agent that, in --mode json headless mode, emits one JSON line per AgentSessionEvent. The event vocabulary is the documented TypeScript union at https://pi.dev/docs/latest/json:

  • session header line: {type:'session', version, id, timestamp, cwd}

  • agent lifecycle: agent_start, agent_end

  • turn lifecycle: turn_start, turn_end

  • message lifecycle: message_start (in LIFECYCLE_EVENT_TYPES so the base would suppress it; PiParser overrides _handle_lifecycle_event() to fall through to _dispatch_json_object for every event type, and the dispatcher marks message_start silent), message_update (carries assistantMessageEvent), message_end

  • tool execution: tool_execution_start, tool_execution_update, tool_execution_end (with isError boolean)

  • queue: queue_update

  • compaction: compaction_start, compaction_end

  • auto-retry: auto_retry_start, auto_retry_end

The current published AgentSessionEvent union enumerates EXACTLY these 15 union members (10 AgentEvent members + 5 direct members; re-fetched 2026-06-20 from https://pi.dev/docs/latest/json; mirrored in tmp/pi-dev-docs/inventory.md). The session line emitted as the FIRST line of the stream (per the docs: “The first line is the session header”) is a stream-level header, NOT a member of the union. Anything outside the 15-member union plus the stream-level session header is forward-compat, not part of the documented contract.

Forward-compat only (NOT in the current published union):

  • extension_error — defensively accepted so a legacy or future pi.dev build that emits it does not break the parser; routed to type='error'. Deliberately excluded from the committed fixture and from the wire-format spec assertion so the live-doc contract stays aligned with the published pi.dev docs.

The message_update events carry an assistantMessageEvent with its own sub-type union:

  • text_start / text_delta / text_end

  • thinking_start / thinking_delta / thinking_end

  • toolcall_start / toolcall_delta / toolcall_end

  • done (stopReason: 'stop' | 'length' | 'toolUse')

  • error (reason: 'aborted' | 'error')

Pi’s message_start event is the only pi event that overlaps with the shared LIFECYCLE_EVENT_TYPES frozenset, so PiParser overrides _handle_lifecycle_event() to return None for every event type, which causes the base layer to fall through to _dispatch_json_object for the entire pi vocabulary. The dispatcher then routes each event through the per-type handler map (silent for message_start / agent_start / turn_start, typed output for the rest). This keeps the AC-04 invariant: every documented pi event reaches _dispatch_json_object for routing decisions; the dispatch table is the single owner of what each event type emits.

Inherits from NdjsonParserBase which owns the 6 shared NDJSON behaviors (data: strip, [DONE] short-circuit, JSON parse dispatch, lifecycle suppression, error extraction). The subclass _dispatch_json_object delegates to _PiDispatch for the per-event-type routing and accumulator management.

class ralph.agents.parsers.pi.PiParser(subagent_pid_registry=None, subagent_source_label=None)[source]

Bases: NdjsonParserBase

Parser for pi.dev’s AgentSessionEvent NDJSON streaming format.

Text deltas are accumulated into coherent blocks before emission. The terminal snapshot (text_end content or the message_end message.content text block) is the authoritative final text; the parser tracks whether the terminal snapshot has already been emitted for a given content block (keyed by contentIndex) to avoid duplicate emissions when streaming deltas, text_end, and the message_end snapshot all reference the same content.

Flushing happens on:

  • message_update with assistantMessageEvent.type == 'text_end'

  • message_end (when no text_end was seen, the snapshot wins)

  • agent_end / turn_end (final flush via stop events)

  • Iterator exhaustion (final flush via flush_accumulators())

Thinking deltas are accumulated in a SEPARATE accumulator with the same terminal-snapshot rules (thinking_end content or message_end message.content thinking block).

The single consistent isError rule: tool_execution_end.isError=true maps to type='error'; isError=false (or absent) maps to type='tool_result'.

The message_end content array is walked for text, thinking, toolCall, and toolResult blocks. Text and thinking blocks honor the per-block terminal-snapshot rule: if the corresponding *_end snapshot was already emitted for a given contentIndex, the message_end block at that block index is skipped. Other text/thinking blocks (whose contentIndex has NOT had a terminal snapshot yet) are emitted. The toolCall block is ALWAYS emitted (per the plan) so downstream consumers see the same logical tool call in the same place they see text and thinking content from message_end.

Inherits from NdjsonParserBase which owns the 6 shared NDJSON behaviors. The subclass _dispatch_json_object delegates to _PiDispatch for the per-event-type routing.

Parameters:
  • subagent_pid_registry (SubagentPidRegistry | None)

  • subagent_source_label (str | None)

flush_accumulators()[source]

Default no-op flush. Subclasses with accumulator state override.

Return type:

Iterator[AgentOutputLine]

reset_emission_flags()[source]

Clear per-message terminal-snapshot tracking.

Called after a stop event (agent_end / turn_end / done) or after message_end so the next message can emit its own terminal snapshots. The set is keyed by the contentIndex of the streaming *_end event so the next message_end can distinguish blocks that were already terminalised (skip) from blocks that still need to be emitted (emit).

Return type:

None

ralph.agents.parsers.claude_interactive

Convert interactive Claude transcript lines into agent parser output.

class ralph.agents.parsers.claude_interactive.ClaudeInteractiveParser(subagent_pid_registry=None, subagent_source_label=None)[source]

Bases: object

Convert interactive Claude transcript lines into AgentOutputLine events.

Parameters:
  • subagent_pid_registry (SubagentPidRegistry | None)

  • subagent_source_label (str | None)

emit_subagent_activity(line, sink)[source]

Forward a parsed interactive-Claude line to the subagent sink.

Parallel standalone implementation of the ParserTemplateBase.emit_subagent_activity() hook for the interactive-Claude transport (which does NOT inherit from ParserTemplateBase). The hook fires ONLY for the four _EMITTABLE_TYPES kinds so a hard-failing child or a lifecycle heartbeat does not keep the watchdog deferring a kill. Sink exceptions are swallowed so a buggy sink cannot crash the activity stream.

Summary format matches the NDJSON parser layer: tool_use:<name> for tool calls, tool_result:<name> for tool results, text:<first-80-chars> for model text, and thinking:<first-80-chars> for thinking blocks.

Parameters:
Return type:

None

class ralph.agents.parsers.claude_interactive.ClaudeInteractiveTranscriptParser[source]

Bases: object

Extract semantic events from a normalized Claude interactive transcript.

Architecture: the transcript file delivers structured JSON events that tell us which mode the session is in (thinking / output / tool_use). Non-JSON text fragments that arrive between JSON events are classified by mode, not by per-line regex heuristics:

  • thinking mode → every non-JSON fragment is TUI status-bar noise → drop.

  • tool_use mode → every non-JSON fragment is TUI rendering noise → drop.

  • output mode → non-JSON fragments are genuine agent output → emit.

  • idle (no mode yet) → conservative heuristics (drop short / TUI-ish).

This eliminates the whack-a-mole regex approach where every new Claude Code thinking-status variant needed a dedicated pattern.

class ralph.agents.parsers.claude_interactive.InteractiveTranscriptEvent(kind, text)[source]

Bases: object

Semantic event extracted from the interactive Claude transcript surface.

Parameters:
  • kind (str)

  • text (str)

ralph.agents.parsers.claude_interactive_transcript_parser

Semantic parser for VT-normalized Claude interactive transcripts.

class ralph.agents.parsers.claude_interactive_transcript_parser.ClaudeInteractiveTranscriptParser[source]

Bases: object

Extract semantic events from a normalized Claude interactive transcript.

Architecture: the transcript file delivers structured JSON events that tell us which mode the session is in (thinking / output / tool_use). Non-JSON text fragments that arrive between JSON events are classified by mode, not by per-line regex heuristics:

  • thinking mode → every non-JSON fragment is TUI status-bar noise → drop.

  • tool_use mode → every non-JSON fragment is TUI rendering noise → drop.

  • output mode → non-JSON fragments are genuine agent output → emit.

  • idle (no mode yet) → conservative heuristics (drop short / TUI-ish).

This eliminates the whack-a-mole regex approach where every new Claude Code thinking-status variant needed a dedicated pattern.

ralph.agents.parsers.agent_output_line

Legacy normalized agent output line type.

class ralph.agents.parsers.agent_output_line.AgentOutputLine(type, content='', raw='', metadata=<factory>)[source]

Bases: object

Legacy normalised line extracted from agent NDJSON stream.

This type is preserved for backward compatibility while newer cross-layer visibility work adopts the typed activity model.

Parameters:
  • type (str)

  • content (str)

  • raw (str)

  • metadata (dict[str, object])

type

Type of the output line (text, tool_use, tool_result, error, etc.).

Type:

str

content

Text content of the line.

Type:

str

raw

Raw JSON string from the agent.

Type:

str

metadata

Additional metadata extracted from the line.

Type:

dict[str, object]

ralph.agents.parsers.interactive_transcript_event

Semantic event model for interactive Claude transcript parsing.

class ralph.agents.parsers.interactive_transcript_event.InteractiveTranscriptEvent(kind, text)[source]

Bases: object

Semantic event extracted from the interactive Claude transcript surface.

Parameters:
  • kind (str)

  • text (str)

ralph.agents.parsers.text_accumulator

Shared text delta accumulator for parser implementations.

class ralph.agents.parsers.text_accumulator.TextAccumulator(buffer='', raw_lines=<factory>)[source]

Bases: object

Shared delta accumulator for paragraph-boundary text flushing.

Parameters:
  • buffer (str)

  • raw_lines (list[str])

accumulate(text, raw, *, kind='text', keep_current_when_empty)[source]

Append text/raw and yield an AgentOutputLine if a paragraph boundary is reached.

Parameters:
  • text (str) – Text delta to append to the buffer.

  • raw (str) – Raw JSON line to track.

  • kind (str) – Output type for the emitted line (‘text’ or ‘thinking’).

  • keep_current_when_empty (bool) – When True, always keep the current raw line in the tail after a flush even if remaining buffer is empty (unconditional rule). When False, only keep it when remaining is non-empty.

Return type:

Iterator[AgentOutputLine]

flush(*, kind='text', require_strip=False)[source]

Yield remaining buffer content as an AgentOutputLine if non-empty, then reset.

Parameters:
  • kind (str) – Output type for the emitted line (‘text’ or ‘thinking’).

  • require_strip (bool) – When True, only emit if buffer.strip() is non-empty (for thinking accumulators that should suppress whitespace-only content).

Return type:

Iterator[AgentOutputLine]

ralph.agents.registry

Agent registry: the source of truth for which agents Ralph Workflow can invoke.

The AgentRegistry is the in-memory index that maps every agent name Ralph Workflow can route to (e.g. claude, codex, opencode, agy, nanocoder, pi, plus dynamic <agent>/<model> aliases) to the AgentConfig that describes how to invoke that agent.

Public surface at a glance:

  • AgentRegistry — the registry itself; constructed either empty or pre-seeded with the bundled defaults via AgentRegistry.from_config()

  • AgentRegistry.from_config — build a registry from a ralph.config.models.UnifiedConfig, layering user-global, project-local, and CLI overrides in the correct precedence order

  • builtin_agents — the built-in default agent configurations that ship with Ralph Workflow; the registry seeds itself from this map when no explicit catalog override is provided

  • AgentSpec — the internal declarative record that backs every AgentConfig in the registry (see ralph.agents.spec)

When to use this module:

  • You are extending Ralph Workflow with a new agent CLI. Use ralph.agents.registration.register_agent_support_to_catalog() to register the new agent support into the catalog, then construct an AgentRegistry with the catalog injected. The registry does not auto-seed at module import; you opt in by calling AgentRegistry(...) or AgentRegistry.from_config(...).

  • You are debugging a routing failure. The registry is what ralph.pipeline.orchestrator consults to resolve a phase’s declared agent name to a command. If a phase fails with “unknown agent”, the registry is where the missing name should be.

  • You are writing a custom CLI command that needs to know which agents are available. Use AgentRegistry.from_config(unified_config) and inspect the resulting registry rather than reading config files directly.

Side effects:

  • Construction does not spawn subprocesses, hit the network, or write files. The registry is a pure in-memory structure.

  • Resolving an agent name does not require the underlying CLI binary to be installed; ralph.agents.availability.check_agent_availability() is what actually probes PATH.

  • The registry does not own credential handling. Authentication lives in the agent CLI itself (see the agent lifecycle page in the docs).

Invariants:

  • The registry’s keys are the agent names policy references (e.g. claude-headless, agy/Gemini 3.5 Flash (Medium)). The registry does not silently rename or normalize these strings.

  • The registry does not silently drop unknown agent names; resolution raises ralph.agents.unknown_agent_error.UnknownAgentError.

  • Built-in agents are seeded by from_config; an explicitly constructed AgentRegistry(catalog=...) seeds from the injected catalog via _seed_catalog_with_builtins. A bare AgentRegistry() does not seed; pass a catalog or call from_config.

Testing notes:

  • ralph.testing.fake_agent_executor.FakeAgentExecutor swaps the process-execution layer for tests; the registry itself remains a pure index and does not need fakes.

  • The seeded default catalog is reachable as ralph.agents.catalog.default_catalog.

class ralph.agents.registry.AgentRegistry(*, ccs_defaults=None, catalog=None)[source]

Bases: object

Registry of available AI agents.

The registry maintains a mapping of agent names to their configurations. It supports loading agents from UnifiedConfig and resolving agent names at runtime.

Parameters:
  • ccs_defaults (CcsConfig | None)

  • catalog (AgentCatalog | None)

agents

Dictionary mapping agent names to their configurations.

build_subagent_pid_registry(transport)[source]

Construct a per-invocation SubagentPidRegistry + SubagentPidSource.

R1 (Trustworthy Idle Watchdog spec): a single shared SubagentPidRegistry is created per invocation and threaded into both the execution strategy (via subagent_pid_source=) and the parser (via subagent_pid_registry=) so any PID registered by either layer becomes visible to ProcessMonitor.spawned_subagent_count().

The per-transport factory helpers in ralph.process.monitor._subagent_pid_source_providers wrap the shared registry to expose a SubagentPidSource that filters by transport source label. OpenCode’s ChildLivenessSubagentPidSource continues to use its own ChildLivenessRegistry (the registry is shared but the source adapter is transport-specific).

Returns:

A (registry, source) tuple. The registry is the single source of truth (FIFO-bounded at 1024 entries); the source is the per-transport adapter the watchdog consumes.

Parameters:

transport (AgentTransport | str)

Return type:

tuple[SubagentPidRegistry, SubagentPidSource]

property catalog: AgentCatalog

Return the AgentCatalog bound to this registry.

When no catalog is injected at construction time, the registry falls back to ralph.agents.catalog.default_catalog(). register_agent_support uses this property to write into the caller-owned catalog only, so a fresh AgentRegistry(catalog=AgentCatalog()) does not leak registrations into the global default catalog.

classmethod from_config(config)[source]

Create registry from UnifiedConfig.

Parameters:

config (UnifiedConfig) – Unified configuration containing agent definitions.

Returns:

Populated AgentRegistry instance.

Return type:

AgentRegistry

get(name)[source]

Get agent configuration by name.

Parameters:

name (str) – Agent name.

Returns:

AgentConfig if found, None otherwise.

Return type:

AgentConfig | None

get_command(name)[source]

Get the command for an agent.

Parameters:

name (str) – Agent name.

Returns:

Command string if agent found, None otherwise.

Return type:

str | None

list_agents()[source]

List all registered agent names.

Returns:

List of agent names.

Return type:

list[str]

register(name, config)[source]

Register an agent with the registry.

Parameters:
  • name (str) – Agent name.

  • config (AgentConfig) – Agent configuration.

Return type:

None

unregister(name)[source]

Unregister an agent from the registry and the bound catalog.

Parameters:

name (str) – Agent name.

Return type:

None

validate()[source]

Validate all registered agents.

Returns:

List of validation error messages (empty if all valid).

Return type:

list[str]

ralph.agents.registry.builtin_agents()[source]

Return the built-in agent configurations keyed by agent name.

Return type:

dict[str, AgentConfig]

ralph.agents.subprocess_executor

SubprocessAgentExecutor — asyncio subprocess implementation of AgentExecutor.

class ralph.agents.subprocess_executor.SubprocessAgentExecutor(command, *, signal_bridge=None, cwd=None, extra_env=None, activity_router=None, raw_overflow_root=None, subagent_sink=None, _pm=None)[source]

Bases: object

AgentExecutor that spawns a subprocess in its own process group.

Uses ProcessManager.spawn_async with start_new_session=True so the child gets its own process group, enabling escalating tree-kill on cancellation. Success or failure is determined by the coordinator from empirical evidence (artifact submission, git changes) — never from this executor’s exit code.

Parameters:
  • command (Sequence[str])

  • signal_bridge (SignalBridge | None)

  • cwd (Path | None)

  • extra_env (Mapping[str, str] | None)

  • activity_router (ActivityRouter | None)

  • raw_overflow_root (Path | None)

  • subagent_sink (Callable[[str], None] | None)

  • _pm (ProcessManager | None)

drop_unit(unit_id)[source]

Release per-unit state so long parallel sessions don’t accumulate state across waves.

Removes the unit’s raw overflow log entry from self._raw_logs so the memory the log holds (up to DEFAULT_MAX_OVERFLOW_FILE_BYTES per unit) is released when the unit is no longer needed. Calls close() on the log first so any buffered tail bytes reach disk deterministically. Safe to call for a unit that was never added; it just no-ops.

Parameters:

unit_id (str)

Return type:

None

ralph.agents.timeout_clock

Fake test clock for the agent timeout subsystem.

class ralph.agents.timeout_clock.Clock(*args, **kwargs)[source]

Bases: Protocol

Protocol for wall-clock operations used by the timeout subsystem.

monotonic()[source]

Return current monotonic time in seconds.

Return type:

float

sleep(seconds)[source]

Pause execution for the given number of seconds.

Parameters:

seconds (float)

Return type:

None

wait_for_event(event, seconds)[source]

Wait up to seconds for event to be set.

Returns True if the event was set during the wait, False on timeout. Production: uses event.wait() so line arrivals wake the poll loop immediately. Test: advances logical time by seconds and checks event state (no real wait).

Parameters:
  • event (threading.Event)

  • seconds (float)

Return type:

bool

class ralph.agents.timeout_clock.FakeClock(start=0.0)[source]

Bases: object

Test Clock: advances logical time synchronously without real waits.

Parameters:

start (float)

advance(seconds)[source]

Advance logical time by seconds without blocking.

Parameters:

seconds (float)

Return type:

None

class ralph.agents.timeout_clock.SystemClock[source]

Bases: Clock

Production Clock: uses real wall-clock time.

monotonic()[source]

Return current monotonic time in seconds.

Return type:

float

sleep(seconds)[source]

Pause execution for the given number of seconds.

Parameters:

seconds (float)

Return type:

None

wait_for_event(event, seconds)[source]

Wait up to seconds for event to be set.

Returns True if the event was set during the wait, False on timeout. Production: uses event.wait() so line arrivals wake the poll loop immediately. Test: advances logical time by seconds and checks event state (no real wait).

Parameters:
  • event (Event)

  • seconds (float)

Return type:

bool

MCP

The MCP group is the bridge between the agent and the workspace: it exposes the in-process ralph-workflow MCP server, the upstream proxy for third-party MCP servers, the tool surface (read_file / write_file / exec / git_read / websearch / webvisit / artifact submit / plan draft edit), the canonical artifact-submission contract, and the multimodal (image / audio / video / PDF) capability detection. See MCP Architecture for the runtime topology and Artifacts for the artifact-submission contract.

ralph.mcp

Public MCP bridge package.

This package groups together the Ralph-side MCP bridge, artifact helpers, transport abstractions, and access-mode helpers used by both the pipeline and standalone ralph-mcp runtime.

If you are navigating with pydoc, common entry points are MCPBridge for the bridge layer and ralph.mcp.server for standalone server helpers.

ralph.mcp.artifacts

Artifact storage and validation for MCP.

This sub-package contains the artifact store, file backend, and per-type validators (plan, development_result, commit_message) plus the audit adapter. These are the backends that MCP tool handlers call into.

ralph.mcp.artifacts.audit_adapter

Audit adapter utilities for MCP records.

class ralph.mcp.artifacts.audit_adapter.AgentSessionId(value)[source]

Bases: object

Convenience wrapper around a session identifier.

Parameters:

value (str)

class ralph.mcp.artifacts.audit_adapter.AuditCorrelation(run_id=None, generation=None, drain=None, policy_mode=None)[source]

Bases: object

Correlation metadata emitted with a Ralph audit record.

Parameters:
  • run_id (str | None)

  • generation (int | None)

  • drain (str | None)

  • policy_mode (str | None)

class ralph.mcp.artifacts.audit_adapter.AuditMetadata(event_type=McpAuditEventType.TOOL, details=None, correlation=<factory>)[source]

Bases: object

Extended metadata attached to an MCP audit record.

Parameters:
class ralph.mcp.artifacts.audit_adapter.McpAuditCorrelation(run_id=None, generation=None, drain=None, policy_mode=None)[source]

Bases: object

Correlation metadata that comes from the MCP dispatch layer.

Parameters:
  • run_id (str | None)

  • generation (int | None)

  • drain (str | None)

  • policy_mode (PolicyMode | None)

class ralph.mcp.artifacts.audit_adapter.McpAuditEventType(*values)[source]

Bases: StrEnum

Enumeration of MCP audit event categories.

class ralph.mcp.artifacts.audit_adapter.McpAuditRecord(timestamp_nanos, session_id, tool_name, decision, path=None, capability=None, metadata=<factory>)[source]

Bases: object

Audit record emitted by the MCP server dispatch layer.

Parameters:
class ralph.mcp.artifacts.audit_adapter.RalphAuditRecord(session_id, timestamp, capability, outcome, description, duration_ms=None, result_status=None, event_type=None, correlation=None)[source]

Bases: object

Audit record format consumed by Ralph’s audit trail.

Parameters:
  • session_id (AgentSessionId)

  • timestamp (int)

  • capability (RalphCapability)

  • outcome (PolicyOutcome)

  • description (str)

  • duration_ms (int | None)

  • result_status (str | None)

  • event_type (str | None)

  • correlation (AuditCorrelation | None)

class ralph.mcp.artifacts.audit_adapter.RalphAuditSinkAdapter(cap=4096)[source]

Bases: object

Adapter that buffers Ralph audit records produced by MCP.

wt-024 memory-perf AC-02: the _records buffer is bounded by a constructor-injected cap (default _DEFAULT_AUDIT_RECORD_CAP = 4096) using collections.deque(maxlen=cap). deque.append honors maxlen by evicting the OLDEST record FIFO — the same pattern already used by BoundedLinesQueue, execution_history, and other production ring buffers in this codebase. The cap is exposed as a constructor parameter for DI / testability; existing no-arg callers (tests/test_audit_adapter.py:49,69 and any production wiring) keep working unchanged because cap defaults to the production cap.

flush() is now Protocol-correct: it returns None (per the AuditSink Protocol def flush(self) -> None: ...) AND clears the buffer so the buffered memory is released. The previous “documented no-op” was a latent leak enabler — buffered records could be retained until a drain_records() call without any production caller calling drain_records() periodically.

Parameters:

cap (int)

drain_records()[source]

Return buffered records and clear the buffer.

Return type:

list[RalphAuditRecord]

emit(record)[source]

Store a converted audit record in the buffer (FIFO-evicting).

Parameters:

record (McpAuditRecord)

Return type:

None

flush()[source]

Release buffered records (returns None per the AuditSink Protocol).

Clears the FIFO buffer so buffered memory is released without requiring a caller to first drain_records(). Returns None (does NOT return records — that would violate the Protocol’s def flush(self) -> None signature).

Return type:

None

ralph.mcp.artifacts.audit_adapter.outcome_from_decision(decision)[source]

Convert an access decision into a policy outcome.

Parameters:

decision (AccessDecision)

Return type:

PolicyOutcome

ralph.mcp.artifacts.audit_adapter.resolve_audit_capability(record)[source]

Map MCP capability to Ralph capability or fall back to workspace read.

Parameters:

record (McpAuditRecord)

Return type:

Capability

ralph.mcp.artifacts.bridge

MCP bridge.

Bridges Ralph’s phase system with MCP (Model Context Protocol) clients. Exposes tools for agent interactions, artifact submission, and state queries.

class ralph.mcp.artifacts.bridge.BridgeArtifactDeps(backend=<ralph.mcp.artifacts._path_file_backend.PathFileBackend object>, now_iso=<function _utc_now_iso>)[source]

Bases: object

Dependencies injected into bridge artifact operations.

Parameters:
class ralph.mcp.artifacts.bridge.BridgeConfig(artifact_dir=PosixPath('.agent/artifacts'), workspace_root=PosixPath('.'), transport=None, artifact_deps=<factory>, run_id='mcp-bridge')[source]

Bases: object

Configuration bundle handed to ralph.mcp.artifacts.bridge.MCPBridge.

A BridgeConfig is the immutable-ish value object that callers construct once per pipeline run (or per standalone MCP session) and then pass into MCPBridge so the bridge knows where artifacts live, which transport to expose, which dependency seams to use, and which run_id to stamp on every artifact it routes. The dataclass is mutable by default so callers can adjust fields after construction in tests; production callers should treat an instance as effectively read-only once it has been handed to a bridge.

Parameters:
artifact_dir

Directory (relative or absolute) where the bridge reads and writes artifact files. Defaults to .agent/artifacts to match the conventional Ralph workspace layout. The bridge resolves the path against workspace_root when it is relative, so callers should set both consistently.

Type:

Path

workspace_root

Workspace root the bridge treats as the user’s project boundary. Tools that ask the bridge for the workspace see this value; artifact paths are usually resolved under it.

Type:

Path

transport

Optional ralph.mcp.protocol.transport.MCPTransport (e.g. StdioTransport, in-memory transports for tests). When None the bridge picks a transport at start time based on its environment and command-line wiring.

Type:

MCPTransport | None

artifact_deps

Dependency bundle controlling how the bridge creates and reads artifacts. Defaults to the production BridgeArtifactDeps instance; tests can swap in a stub to avoid touching the filesystem.

Type:

BridgeArtifactDeps

run_id

Stable identifier the bridge stamps onto every artifact it produces during this run. Surfaces in the artifact store index and in log lines so a single multi-bridge pipeline run can be traced end-to-end. The default "mcp-bridge" is appropriate for standalone use; pipelines should override it with their per-run identifier.

Type:

str

Invariants:
  • The bridge assumes artifact_dir and workspace_root were set by a trusted caller; passing user-supplied paths directly would cross the trust boundary between the bridge and the agent-facing tool surface.

  • run_id is propagated to artifacts but is not used for authorization decisions; it is a tracing affordance only.

exception ralph.mcp.artifacts.bridge.BridgeError[source]

Bases: Exception

Raised when bridge operations fail.

class ralph.mcp.artifacts.bridge.MCPBridge(config)[source]

Bases: object

MCP bridge for Ralph.

Bridges the phase system with MCP by exposing tools to agents, managing artifact lifecycle, and handling MCP protocol messages.

Parameters:

config (BridgeConfig)

async close()[source]

Close the bridge and transport.

Return type:

None

get_artifact_mcp(name)[source]

Get an artifact via MCP.

Parameters:

name (str) – Artifact name.

Returns:

Artifact data.

Return type:

dict[str, object]

async handle_message(message)[source]

Handle an incoming MCP message.

Parameters:

message (MCPMessage) – The MCP message to process.

Returns:

Optional response message.

Return type:

MCPMessage | None

list_artifacts_mcp()[source]

List all artifacts via MCP.

Returns:

List of artifacts.

Return type:

dict[str, object]

register_tool(name, description, input_schema, handler)[source]

Register an MCP tool.

Parameters:
  • name (str) – Tool name.

  • description (str) – Tool description.

  • input_schema (dict[str, object]) – JSON schema for input validation.

  • handler (_ToolHandler) – Function to call when tool is invoked.

Return type:

None

async run()[source]

Run the bridge message loop.

Return type:

None

start()[source]

Start the MCP bridge.

Return type:

None

submit_artifact_mcp(name, artifact_type, content, metadata=None)[source]

Submit an artifact via MCP.

Parameters:
  • name (str) – Artifact name.

  • artifact_type (str) – Type of artifact.

  • content (dict[str, object]) – Artifact content.

  • metadata (dict[str, object] | None) – Optional metadata.

Returns:

Artifact submission result.

Return type:

dict[str, object]

tool_called(name, arguments)[source]

Handle a tool call from an MCP client.

Parameters:
  • name (str) – Tool name.

  • arguments (dict[str, object]) – Tool arguments.

Returns:

Tool result as a dictionary.

Raises:

BridgeError – If tool is not found or execution fails.

Return type:

dict[str, object]

ralph.mcp.artifacts.canonical_submit

Canonical artifact submission entry point.

This module is the single public writer of run-scoped completion receipts and completion sentinels for canonical artifact types. Every artifact submission that needs to satisfy the completion gate must route through submit_artifact_canonical() so the receipt, sentinel, artifact file, and Markdown handoff are written atomically (or rolled back together).

class ralph.mcp.artifacts.canonical_submit.SubmitResult(artifact_path, receipt_path, sentinel_path, handoff_path, artifact_type, run_id)[source]

Bases: object

Paths written by a canonical artifact submission.

Parameters:
  • artifact_path (Path | None)

  • receipt_path (Path | None)

  • sentinel_path (Path | None)

  • handoff_path (Path | None)

  • artifact_type (str)

  • run_id (str | None)

artifact_path

Path to the canonical artifact JSON file, if written.

Type:

Path | None

receipt_path

Path to the run-scoped receipt, if written.

Type:

Path | None

sentinel_path

Path to the completion sentinel, if written for a single-shot artifact type.

Type:

Path | None

handoff_path

Path to the Markdown handoff, if one is configured for the artifact type.

Type:

Path | None

artifact_type

The canonical artifact type that was submitted.

Type:

str

run_id

The run id used as the receipt/sentinel key.

Type:

str | None

ralph.mcp.artifacts.canonical_submit.promote_fallback_artifact(workspace_root, artifact_type, *, deps=None, run_id=None, receipt_secret=None)[source]

Promote an agent-written fallback file to a canonical submission.

Scans .agent/tmp/<artifact_type>.json then .agent/artifacts/<artifact_type>.json. For the first existing file, parse it (tolerating both the bare inner payload and the outer {name,type,content} envelope) and route it through submit_artifact_canonical() so a receipt is stamped.

Does NOT promote canonical artifacts from .agent/artifacts/ that already have a receipt for ANY run_id (including the current one), preventing stale artifacts from previous runs from being promoted to a new receipt.

Parameters:
  • receipt_secret (str | None) – Optional broker-owned secret to thread into the resolved ArtifactHandlerDeps. When provided, the promoted receipt carries the HMAC binding (run_id, artifact_type) to the secret so the verifier accepts it under the same secret and rejects it under any other. Without this, promotion writes a no-HMAC receipt which the verifier rejects under HMAC enforcement.

  • workspace_root (Path)

  • artifact_type (str)

  • deps (ArtifactHandlerDeps | None)

  • run_id (str | None)

Returns:

The SubmitResult from the canonical submit, or None when no fallback file exists or parsing fails.

Return type:

SubmitResult | None

ralph.mcp.artifacts.canonical_submit.submit_artifact_canonical(workspace_root, artifact_type, parsed_content, *, deps=None, run_id=None, artifact_dir=None, name=None, overwrite=True, metadata=None)[source]

Submit an artifact through the canonical, receipt-stamping path.

The submission is atomic: the artifact file, run-scoped receipt, single-shot completion sentinel, and Markdown handoff are written inside a single rollback-protected operation sequence. If any step fails, all completed steps are undone.

Parameters:
  • workspace_root (Path) – Root of the workspace where artifacts/receipts live.

  • artifact_type (str) – Canonical artifact type to submit.

  • parsed_content (dict[str, object]) – Normalized artifact payload dictionary.

  • deps (ArtifactHandlerDeps | None) – Injectable dependencies; defaults to DEFAULT_ARTIFACT_HANDLER_DEPS.

  • run_id (str | None) – Run identifier used as the receipt/sentinel key.

  • artifact_dir (Path | None) – Directory for the artifact JSON file; defaults to workspace_root / '.agent' / 'artifacts'.

  • name (str | None) – Optional artifact filename stem; defaults to artifact_type.

  • overwrite (bool) – Whether to overwrite an existing artifact file.

  • metadata (dict[str, object] | None) – Optional metadata dictionary for the artifact envelope.

Returns:

A frozen SubmitResult describing the files that were written.

Return type:

SubmitResult

ralph.mcp.artifacts.commit_message

Commit-message artifact helpers.

Canonical commit messages are stored as MCP-style JSON artifacts in .agent/tmp/commit_message.json. The commit artifact content follows a structured schema with either a commit or skip variant. A plain-text mirror in .agent/tmp/commit-message.txt is maintained for CLI compatibility.

ralph.mcp.artifacts.commit_message.commit_message_artifact_path(repo_root)[source]

Return the canonical artifact JSON path for the given repo root.

Parameters:

repo_root (Path)

Return type:

Path

ralph.mcp.artifacts.commit_message.commit_message_text_path(repo_root)[source]

Return the plain-text mirror path for commit messages.

Parameters:

repo_root (Path)

Return type:

Path

ralph.mcp.artifacts.commit_message.delete_commit_message_artifacts(repo_root, *, backend=<ralph.mcp.artifacts._path_file_backend.PathFileBackend object>)[source]

Remove all commit message artifacts and legacy stale files.

Parameters:
Return type:

None

ralph.mcp.artifacts.commit_message.normalize_commit_message_content(content)[source]

Validate and normalize a commit message payload to a canonical dict form.

Parameters:

content (str | dict[str, object])

Return type:

dict[str, object]

ralph.mcp.artifacts.commit_message.read_commit_message_artifact(repo_root, *, backend=<ralph.mcp.artifacts._path_file_backend.PathFileBackend object>)[source]

Read the commit message from the canonical artifact, falling back to the text file.

Parameters:
Return type:

str | None

ralph.mcp.artifacts.commit_message.read_commit_message_from_path(message_file, *, backend=<ralph.mcp.artifacts._path_file_backend.PathFileBackend object>)[source]

Read a commit message from an arbitrary file path (JSON or plain text).

Parameters:
Return type:

str | None

ralph.mcp.artifacts.commit_message.read_commit_message_payload_from_path(message_file, *, backend=<ralph.mcp.artifacts._path_file_backend.PathFileBackend object>)[source]

Read and normalize a commit message payload from JSON or plain text.

Parameters:
Return type:

dict[str, object] | None

ralph.mcp.artifacts.commit_message.render_commit_message_content(content)[source]

Render normalized commit message content as a plain-text commit message string.

Parameters:

content (dict[str, object])

Return type:

str

ralph.mcp.artifacts.commit_message.write_commit_message_artifact(repo_root, message, *, backend=<ralph.mcp.artifacts._path_file_backend.PathFileBackend object>, now_iso=<function _now_iso>)[source]

Persist a commit message as both a JSON artifact and a plain-text file.

Parameters:
  • repo_root (Path)

  • message (str | dict[str, object])

  • backend (FileBackend)

  • now_iso (Callable[[], str])

Return type:

None

ralph.mcp.artifacts.completion_receipts

Run-scoped artifact submission receipts — the single source of truth for “was this artifact submitted in this run?”.

A receipt decouples completion detection from artifact storage. The submission handler writes a receipt the moment it has durably persisted an artifact; the completion gate reads receipts to decide whether the required artifact is present. The gate never recomputes a storage path, so a receipt keyed on (run_id, artifact_type) — both stable identities, never paths — cannot drift away from where the artifact actually landed (.agent/tmp vs .agent/artifacts, a per-worker namespace, or any future layout change).

Storage (RFC-013 P3): receipts are stored in a single WAL-mode SQLite database at <workspace>/.agent/state.db via RunStateDB (one row per (run_id, artifact_type)). This eliminates one-file-per-event state churn under .agent/receipts/<run_id>/ (a measurable share of macOS fseventsd activity under long multi-instance runs). The legacy file path is preserved as a read-fallback during the dual-read rollout window so an in-flight run that was upgraded mid-run still passes its completion gate. Production writes go to the DB only; the file path is read-only fallback.

ralph.mcp.artifacts.completion_receipts.RECEIPT_DIR_RELPATH_FMT = '.agent/receipts/{run_id}'

Directory (workspace-relative) holding every receipt for a single run.

exception ralph.mcp.artifacts.completion_receipts.ReceiptPersistenceError[source]

Bases: RuntimeError

Raised when both the RunStateDB and legacy-file paths fail to persist a receipt.

Without this guard, write_artifact_receipt returns successfully even when no durable receipt was written — letting artifact submission continue against a missing receipt and producing a silent failure downstream when the completion gate reads it. execute_ops_with_rollback already propagates this exception upward to rollback the in-flight submit (the receipt op is the last step, so the artifact and its handoff would also be unrolled).

ralph.mcp.artifacts.completion_receipts.artifact_receipt_present(workspace_root, run_id, artifact_type, *, backend=<ralph.mcp.artifacts._path_file_backend.PathFileBackend object>, receipt_secret=None)[source]

Return True when a valid receipt for (run_id, artifact_type) exists.

Reads the per-workspace .agent/state.db first (RFC-013 P3). When the DB has no row, falls back to the legacy file path .agent/receipts/<run_id>/<artifact_type>.json so receipts left behind by the pre-upgrade release are still honored during the dual-read window.

When receipt_secret is provided the stored HMAC is verified against (run_id, artifact_type); a receipt that exists but fails HMAC verification returns False. This pins the receipt to the broker-owned secret so a model with workspace write capabilities cannot forge a valid receipt.

Parameters:
  • workspace_root (Path)

  • run_id (str)

  • artifact_type (str)

  • backend (FileBackend)

  • receipt_secret (str | None)

Return type:

bool

ralph.mcp.artifacts.completion_receipts.clear_run_receipts(workspace_root, run_id, *, backend=<ralph.mcp.artifacts._path_file_backend.PathFileBackend object>)[source]

Remove every receipt for run_id (no-op when none exist).

Called at the start of each (re)invocation so a resumed session with a reused run_id never inherits a stale “already submitted” signal. Clears both the DB rows and the legacy file paths. Best-effort: a missing or read-only .agent/state.db does not block the call (the legacy file cleanup still proceeds). The DB clear itself is also best-effort — a transient sqlite3.Error during RunStateDB.clear_run_receipts is suppressed so the legacy-file cleanup below always runs, matching the RFC-013 retention contract that a single failure mode cannot abort rerun / session cleanup.

Parameters:
  • workspace_root (Path)

  • run_id (str)

  • backend (FileBackend)

Return type:

None

ralph.mcp.artifacts.completion_receipts.delete_artifact_receipt(workspace_root, run_id, artifact_type, *, backend=<ralph.mcp.artifacts._path_file_backend.PathFileBackend object>)[source]

Remove one receipt (no-op when absent) — the undo for write_artifact_receipt.

Deletes both the DB row and the legacy file path (dual-target) so a stale file from the pre-upgrade release cannot leave a receipt in place after the DB row is gone.

Parameters:
  • workspace_root (Path)

  • run_id (str)

  • artifact_type (str)

  • backend (FileBackend)

Return type:

None

ralph.mcp.artifacts.completion_receipts.write_artifact_receipt(workspace_root, run_id, artifact_type, *, backend=<ralph.mcp.artifacts._path_file_backend.PathFileBackend object>, receipt_secret=None)[source]

Record that artifact_type was durably persisted during run_id.

Must be called only after the artifact itself is committed to storage so the receipt and the artifact appear together (or, on rollback, not at all).

When receipt_secret is provided the receipt includes a hmac field that binds it to the broker-owned secret. A model that can write under .agent/ cannot forge a receipt with a valid HMAC because the secret is never exposed to the agent.

Storage (RFC-013 P3): the canonical store is the per-workspace .agent/state.db. Production writes go to the DB ONLY when the DB write succeeds; the legacy .agent/receipts/<run_id>/<artifact_type>.json file path is then read-only fallback during the dual-read rollout window so receipts left behind by the pre-upgrade release are still honored.

Durable-fallback: when RunStateDB raises sqlite3.Error (locked / corrupt / unsupported WAL) on either open or upsert, this function falls back to writing the legacy file path so the completion gate always has durable evidence. Atomic-rollback for tests and callers using explicit backend kwargs still works because backend continues to control where the legacy bytes land (see FailingBackend pattern). The HMAC is included in both stores when receipt_secret is provided.

Parameters:
  • workspace_root (Path)

  • run_id (str)

  • artifact_type (str)

  • backend (FileBackend)

  • receipt_secret (str | None)

Return type:

None

ralph.mcp.artifacts.development_result

Structured development_result artifact validation helpers.

class ralph.mcp.artifacts.development_result.AnalysisItemProof(*, how_to_fix_item, proof)[source]

Bases: BaseModel

Evidence that a prior analysis item was addressed.

Parameters:
  • how_to_fix_item (Annotated[str, MinLen(min_length=1)])

  • proof (Annotated[str, MinLen(min_length=1)])

model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.mcp.artifacts.development_result.Continuation(*, prior_session_id)[source]

Bases: BaseModel

Reference to a prior session when a development result is partial.

Parameters:

prior_session_id (Annotated[str, MinLen(min_length=1)])

model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.mcp.artifacts.development_result.DevelopmentResult(*, status, summary, files_changed, plan_items_proven=<factory>, analysis_items_addressed=<factory>, next_steps=None, continuation=None)[source]

Bases: BaseModel

Validated schema for a development_result artifact.

Parameters:
  • status (Annotated[str, MinLen(min_length=1)])

  • summary (Annotated[str, MinLen(min_length=1)])

  • files_changed (Annotated[str, MinLen(min_length=1)])

  • plan_items_proven (list[PlanItemProof])

  • analysis_items_addressed (list[AnalysisItemProof])

  • next_steps (str | None)

  • continuation (Continuation | None)

model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

exception ralph.mcp.artifacts.development_result.DevelopmentResultValidationError[source]

Bases: ValueError

Raised when a development_result artifact is malformed.

class ralph.mcp.artifacts.development_result.PlanItemProof(*, plan_item, proof)[source]

Bases: BaseModel

Evidence that a plan item was completed.

Parameters:
  • plan_item (Annotated[str, MinLen(min_length=1)])

  • proof (Annotated[str, MinLen(min_length=1)])

model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

ralph.mcp.artifacts.development_result.normalize_development_result_content(content)[source]

Validate and normalize a raw development_result content dict.

Parameters:

content (dict[str, object])

Return type:

dict[str, object]

ralph.mcp.artifacts.file_backend

Shared file backend abstractions for MCP persistence.

class ralph.mcp.artifacts.file_backend.FileBackend(*args, **kwargs)[source]

Bases: Protocol

Protocol for filesystem I/O required by artifact persistence.

exists(path)[source]

Return True if path currently exists on the backend.

Parameters:

path (Path)

Return type:

bool

glob(path, pattern)[source]

Return all paths under path matching the glob pattern.

Parameters:
  • path (Path)

  • pattern (str)

Return type:

list[Path]

mkdir(path, *, parents=False, exist_ok=False)[source]

Create the directory at path.

Optionally create parents and tolerate an existing directory.

Parameters:
  • path (Path)

  • parents (bool)

  • exist_ok (bool)

Return type:

None

read_text(path, *, encoding='utf-8')[source]

Read and return the textual contents of path decoded with encoding.

Parameters:
  • path (Path)

  • encoding (str)

Return type:

str

replace(source, destination)[source]

Atomically move source to destination, replacing any existing file.

Parameters:
  • source (Path)

  • destination (Path)

Return type:

None

Remove the file at path; if missing_ok, do not raise when the file is absent.

Parameters:
  • path (Path)

  • missing_ok (bool)

Return type:

None

write_text(path, content, *, encoding='utf-8')[source]

Write content to path using encoding, replacing any existing file.

Parameters:
  • path (Path)

  • content (str)

  • encoding (str)

Return type:

None

class ralph.mcp.artifacts.file_backend.PathFileBackend[source]

Bases: object

Concrete FileBackend implementation backed by pathlib.Path operations.

ralph.mcp.artifacts.format_docs

Bundled dumb-proof Markdown reference docs for artifact submission.

ralph.mcp.artifacts.format_docs.format_doc_workspace_path(artifact_type)[source]

Return the workspace-relative path for an artifact format doc.

Parameters:

artifact_type (str)

Return type:

str

ralph.mcp.artifacts.format_docs.format_index_workspace_path()[source]

Return the workspace-relative path for the artifact formats index doc.

Return type:

str

ralph.mcp.artifacts.format_docs.has_format_doc(artifact_type)[source]

Return True if a bundled format doc exists for the given artifact type.

Parameters:

artifact_type (str)

Return type:

bool

ralph.mcp.artifacts.format_docs.load_bundled_format_doc(artifact_type)[source]

Load a bundled Markdown format doc for the given artifact type, or None if unknown.

Parameters:

artifact_type (str)

Return type:

str | None

ralph.mcp.artifacts.format_docs.load_bundled_format_index()[source]

Load the bundled artifact formats index doc.

Return type:

str

ralph.mcp.artifacts.format_docs.materialize_all_format_docs(workspace_root, *, backend=<ralph.mcp.artifacts._path_file_backend.PathFileBackend object>)[source]

Write all bundled format docs and the index into the workspace.

Parameters:
Return type:

list[str]

ralph.mcp.artifacts.format_docs.materialize_format_doc(workspace_root, artifact_type, *, backend=<ralph.mcp.artifacts._path_file_backend.PathFileBackend object>)[source]

Write a bundled format doc into the workspace and return its relative path.

Parameters:
  • workspace_root (Path)

  • artifact_type (str)

  • backend (FileBackend)

Return type:

str | None

ralph.mcp.artifacts.format_docs.materialize_format_index(workspace_root, *, backend=<ralph.mcp.artifacts._path_file_backend.PathFileBackend object>)[source]

Materialize the bundled artifact formats index doc to workspace.

Returns the relative path to the materialized index file.

Parameters:
Return type:

str

ralph.mcp.artifacts.handoffs

Agent/user-facing Markdown handoff helpers.

Structured JSON artifacts remain Ralph’s machine-readable source of truth for validation and routing. This module mirrors selected artifact payloads into Markdown files so downstream agents and users consume a stable, human-readable handoff instead of raw JSON.

ralph.mcp.artifacts.handoffs.delete_markdown_handoff(workspace_root, artifact_type, *, backend=<ralph.mcp.artifacts._path_file_backend.PathFileBackend object>)[source]

Remove a mirrored Markdown handoff if the artifact write rolls back.

Parameters:
  • workspace_root (Path)

  • artifact_type (str)

  • backend (FileBackend)

Return type:

None

ralph.mcp.artifacts.handoffs.ensure_markdown_handoff_from_artifact(workspace_root, artifact_type, artifact_content, *, backend=<ralph.mcp.artifacts._path_file_backend.PathFileBackend object>)[source]

Ensure a Markdown handoff exists from a persisted JSON artifact payload.

Parameters:
  • workspace_root (Path)

  • artifact_type (str)

  • artifact_content (str)

  • backend (FileBackend)

Return type:

str | None

ralph.mcp.artifacts.handoffs.handoff_path_for_artifact(artifact_type)[source]

Return the Markdown handoff path for an artifact type, if any.

Parameters:

artifact_type (str)

Return type:

str | None

ralph.mcp.artifacts.handoffs.render_markdown_handoff(artifact_type, content)[source]

Render an artifact payload into the Markdown handoff users/agents consume.

Parameters:
  • artifact_type (str)

  • content (Mapping[str, object])

Return type:

str

ralph.mcp.artifacts.handoffs.sync_markdown_handoff(workspace_root, artifact_type, content, *, backend=<ralph.mcp.artifacts._path_file_backend.PathFileBackend object>)[source]

Write the Markdown handoff for a machine artifact and return its path.

Parameters:
  • workspace_root (Path)

  • artifact_type (str)

  • content (Mapping[str, object])

  • backend (FileBackend)

Return type:

str | None

ralph.mcp.artifacts.history

Artifact history archival and indexing.

When a phase’s artifact_history policy has enabled=True, the runtime archives the current canonical artifact JSON and its Markdown handoff into a stable history directory before overwriting them. This lets planning agents inspect prior failed plans and analysis decisions across re-planning loops.

Layout under .agent/artifacts/:
history/<artifact_type>/ – history root for a type

<timestamp>_<artifact_type>.json – archived canonical JSON <timestamp>_<artifact_type>.md – archived Markdown handoff (when present) index.md – human-readable summary of archived entries

The canonical latest files (.agent/artifacts/plan.json, .agent/PLAN.md, etc.) are never moved here — they remain the authoritative current state. History contains only prior versions that were overwritten.

ralph.mcp.artifacts.history.archive_artifact_before_overwrite(artifact_dir, workspace_root, artifact_type, *, backend=<ralph.mcp.artifacts._path_file_backend.PathFileBackend object>, now_iso)[source]

Archive the current canonical artifact files before they are overwritten.

Reads the current canonical JSON artifact and its Markdown handoff (if any), writes them into the history directory under a timestamped prefix, then rebuilds the history index.

Parameters:
  • artifact_dir (Path) – The artifacts directory (e.g. .agent/artifacts/).

  • workspace_root (Path) – Workspace root (used to locate Markdown handoff files).

  • artifact_type (str) – The artifact type identifier (e.g. ‘plan’).

  • backend (FileBackend) – File backend for I/O.

  • now_iso (Callable[[], str]) – Callable returning the current timestamp as an ISO 8601 string.

Returns:

List of Paths of files created by this operation (JSON and MD archives, NOT the index). The caller can use these paths to roll back the archive.

Return type:

list[Path]

ralph.mcp.artifacts.history.clear_artifact_history(artifact_dir, artifact_type, *, backend=<ralph.mcp.artifacts._path_file_backend.PathFileBackend object>)[source]

Remove all archived history files for an artifact type.

Deletes all timestamped archive files and the index. The history directory itself is left in place to avoid filesystem churn on repeated planning cycles.

Parameters:
  • artifact_dir (Path)

  • artifact_type (str)

  • backend (FileBackend)

Return type:

None

ralph.mcp.artifacts.history.history_dir_for_artifact(artifact_dir, artifact_type)[source]

Return the history directory for an artifact type.

Parameters:
  • artifact_dir (Path)

  • artifact_type (str)

Return type:

Path

ralph.mcp.artifacts.history.history_index_path(artifact_dir, artifact_type)[source]

Return the path to the history index file for an artifact type.

Parameters:
  • artifact_dir (Path)

  • artifact_type (str)

Return type:

Path

ralph.mcp.artifacts.history.rebuild_history_index(artifact_dir, artifact_type, *, backend=<ralph.mcp.artifacts._path_file_backend.PathFileBackend object>)[source]

Rebuild the history index from files present in the history directory.

Always writes a fresh index.md derived from directory contents so the index stays consistent with the actual archived files regardless of rollback state.

Parameters:
  • artifact_dir (Path)

  • artifact_type (str)

  • backend (FileBackend)

Return type:

None

ralph.mcp.artifacts.history.snapshot_current_artifact(artifact_dir, workspace_root, artifact_type, *, backend=<ralph.mcp.artifacts._path_file_backend.PathFileBackend object>, now_iso)[source]

Snapshot the current canonical artifact and handoff into history.

Unlike archive-before-overwrite, this records the current successful artifact immediately after submission so history exists from the first completed phase.

Parameters:
  • artifact_dir (Path)

  • workspace_root (Path)

  • artifact_type (str)

  • backend (FileBackend)

  • now_iso (Callable[[], str])

Return type:

list[Path]

ralph.mcp.artifacts.plan

Structured planning artifact validation helpers.

Module family (reading order):

_section_models -> _section_registry -> _validation -> _step_edit -> _renderers -> _draft_io -> _noop.

ralph.mcp.artifacts.plan.plan_schema

Structured Pydantic schema models for plan artifacts.

class ralph.mcp.artifacts.plan.plan_schema.AcceptanceCriteria(*, criteria)[source]

Bases: BaseModel

Parameters:

criteria (Annotated[list[AcceptanceCriterion], MinLen(min_length=1), MaxLen(max_length=500)])

model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.mcp.artifacts.plan.plan_schema.AcceptanceCriterion(*, id, description, verification_step=None, evidence_path=None, satisfied_by_steps=<factory>)[source]

Bases: BaseModel

Parameters:
  • id (Annotated[str, MinLen(min_length=1), _PydanticGeneralMetadata(pattern='^[A-Z]+-\\d{2,}$')])

  • description (Annotated[str, MinLen(min_length=1), MaxLen(max_length=8000)])

  • verification_step (Annotated[str | None, MaxLen(max_length=2000)])

  • evidence_path (Annotated[str | None, MaxLen(max_length=1000)])

  • satisfied_by_steps (Annotated[list[int], MaxLen(max_length=50)])

model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.mcp.artifacts.plan.plan_schema.CriticalFiles(*, primary_files, reference_files=<factory>)[source]

Bases: BaseModel

Primary and reference files that define the plan’s surface area.

Parameters:
  • primary_files (Annotated[list[CriticalPrimaryFile], MinLen(min_length=1), MaxLen(max_length=200)])

  • reference_files (Annotated[list[ReferenceFile], MaxLen(max_length=200)])

model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.mcp.artifacts.plan.plan_schema.CriticalPrimaryFile(*, path, action, estimated_changes=None)[source]

Bases: BaseModel

Parameters:
  • path (Annotated[str, MinLen(min_length=1), MaxLen(max_length=1000)])

  • action (Literal['create', 'modify', 'delete'])

  • estimated_changes (Annotated[str | None, MaxLen(max_length=500)])

model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.mcp.artifacts.plan.plan_schema.DesignSection(*, planning_profile=None, constraints=None, non_goals=None, dependency_injection=None, drift_detection=None, testability=None, refactor_strategy=None, acceptance_criteria=None, outcome=None, notes=None)[source]

Bases: BaseModel

Design section aggregating SE-opinionated sub-models.

Collects cross-cutting design choices: planning profile, constraints, non-goals, dependency-injection expectations, drift-detection guards, testability requirements, refactor strategy, and acceptance criteria. When planning_profile is set, the model bias-fills any missing sub-sections from preset defaults; user-provided values always win.

Parameters:
  • planning_profile (Literal['strict', 'balanced'] | None)

  • constraints (DesignConstraints | None)

  • non_goals (NonGoals | None)

  • dependency_injection (DependencyInjection | None)

  • drift_detection (DriftDetection | None)

  • testability (Testability | None)

  • refactor_strategy (RefactorStrategy | None)

  • acceptance_criteria (AcceptanceCriteria | None)

  • outcome (Annotated[str | None, MaxLen(max_length=1000)])

  • notes (Annotated[str | None, MaxLen(max_length=20000)])

model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.mcp.artifacts.plan.plan_schema.EditArea(*, paths=<factory>, directories=<factory>)[source]

Bases: BaseModel

Parameters:
  • paths (list[str])

  • directories (list[str])

model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.mcp.artifacts.plan.plan_schema.ParallelPlanItem(*, id, description, edit_area, depends_on=<factory>)[source]

Bases: BaseModel

A unit of parallelisable work with dependency tracking.

Parameters:
  • id (Annotated[str, MinLen(min_length=1)])

  • description (Annotated[str, MinLen(min_length=1)])

  • edit_area (EditArea)

  • depends_on (list[str])

model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.mcp.artifacts.plan.plan_schema.PlanStep(*, number, title, content, step_type=StepType.ACTION, priority=None, targets=<factory>, location=None, rationale=None, depends_on=<factory>, satisfies=<factory>, expected_evidence=<factory>, verify_command=None)[source]

Bases: BaseModel

One executable step in a plan artifact.

A plan step carries a numbered instruction, its type contract, optional targets, evidence, and dependency/satisfaction metadata. The step_type field determines which extra fields are required: a file_change step must declare targets, while a verify step must declare either verify_command or location.

Parameters:
  • number (Annotated[int, Ge(ge=1)])

  • title (Annotated[str, MinLen(min_length=1), MaxLen(max_length=500)])

  • content (Annotated[str, MinLen(min_length=1), MaxLen(max_length=20000)])

  • step_type (StepType)

  • priority (Literal['critical', 'high', 'medium', 'low'] | None)

  • targets (Annotated[list[StepTarget], MaxLen(max_length=100)])

  • location (Annotated[str | None, MaxLen(max_length=500)])

  • rationale (Annotated[str | None, MaxLen(max_length=8000)])

  • depends_on (Annotated[list[int], MaxLen(max_length=50)])

  • satisfies (Annotated[list[str], MaxLen(max_length=50)])

  • expected_evidence (list[EvidenceRef])

  • verify_command (Annotated[str | None, MaxLen(max_length=2000)])

model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.mcp.artifacts.plan.plan_schema.ReferenceFile(*, path, purpose)[source]

Bases: BaseModel

Parameters:
  • path (Annotated[str, MinLen(min_length=1), MaxLen(max_length=1000)])

  • purpose (Annotated[str, MinLen(min_length=1), MaxLen(max_length=2000)])

model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.mcp.artifacts.plan.plan_schema.RiskMitigation(*, risk, mitigation, severity=None)[source]

Bases: BaseModel

A single identified risk and its mitigating action.

Parameters:
  • risk (Annotated[str, MinLen(min_length=1), MaxLen(max_length=8000)])

  • mitigation (Annotated[str, MinLen(min_length=1), MaxLen(max_length=8000)])

  • severity (Literal['low', 'medium', 'high', 'critical'] | None)

model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.mcp.artifacts.plan.plan_schema.ScopeItem(*, text, count=None, category=None)[source]

Bases: BaseModel

A single bounded work item within a plan’s scope.

Parameters:
  • text (Annotated[str, MinLen(min_length=1), MaxLen(max_length=1000)])

  • count (Annotated[str | None, MaxLen(max_length=200)])

  • category (Literal['bugfix', 'feature', 'refactor', 'test', 'docs', 'infra', 'migration', 'security', 'performance', 'cleanup', 'research', 'unknown', 'file_change', 'prompt', 'other'] | None)

model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.mcp.artifacts.plan.plan_schema.SkillsMcp(*, skills, mcps=<factory>)[source]

Bases: BaseModel

Required skills and optional MCP servers for executing the plan.

Parameters:
  • skills (Annotated[list[str], MinLen(min_length=1), MaxLen(max_length=100)])

  • mcps (Annotated[list[str], MaxLen(max_length=50)])

model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.mcp.artifacts.plan.plan_schema.StepTarget(*, path, action)[source]

Bases: BaseModel

Parameters:
  • path (Annotated[str, MinLen(min_length=1), MaxLen(max_length=1000)])

  • action (Literal['create', 'modify', 'delete', 'read', 'reference'])

model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.mcp.artifacts.plan.plan_schema.Summary(*, context='', intent='', intent_verb='', scope_items, coverage_areas=<factory>)[source]

Bases: BaseModel

Summary section of a plan artifact.

Captures the user-facing context, the explicit intent and intent verb, the scope items that bound the work, and the coverage areas the plan touches. intent_verb is a closed vocabulary used by the executor to choose the right kind of verification and artifact semantics.

Parameters:
  • context (Annotated[str, MaxLen(max_length=8000)])

  • intent (Annotated[str, MaxLen(max_length=500)])

  • intent_verb (str)

  • scope_items (Annotated[list[ScopeItem], MinLen(min_length=3), MaxLen(max_length=200)])

  • coverage_areas (Annotated[list[Literal['bugfix', 'feature', 'refactor', 'test', 'docs', 'infra', 'security', 'performance', 'migration', 'release']], ~annotated_types.MaxLen(max_length=50)])

model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ralph.mcp.artifacts.plan.plan_schema.VerificationStep(*, method, expected_outcome, timeout_seconds=None, cwd=None)[source]

Bases: BaseModel

Parameters:
  • method (Annotated[str, MinLen(min_length=1), MaxLen(max_length=2000)])

  • expected_outcome (Annotated[str, MinLen(min_length=1), MaxLen(max_length=8000)])

  • timeout_seconds (Annotated[int | None, Gt(gt=0), Le(le=3600)])

  • cwd (Annotated[str | None, MaxLen(max_length=500)])

model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

ralph.mcp.artifacts.policy_outcomes

Shared helpers for interpreting MCP approval outcomes.

ralph.mcp.artifacts.policy_outcomes.is_policy_approved(outcome)[source]

Return True if the given policy outcome represents an approval decision.

Parameters:

outcome (object | None)

Return type:

bool

ralph.mcp.artifacts.store

MCP artifact handling.

Provides artifact submission, retrieval, and management for MCP interactions. Artifacts are JSON files stored in the workspace’s .agent/artifacts/ directory.

class ralph.mcp.artifacts.store.Artifact(name, artifact_type, content, created_at=<factory>, updated_at=<factory>, metadata=<factory>)[source]

Bases: object

Represents an MCP artifact.

Parameters:
  • name (str)

  • artifact_type (str)

  • content (dict[str, object])

  • created_at (str)

  • updated_at (str)

  • metadata (dict[str, object])

name

Unique artifact name.

Type:

str

artifact_type

Type identifier (e.g., “planning”, “code”, “review”).

Type:

str

content

Artifact content as a dictionary.

Type:

dict[str, object]

created_at

ISO timestamp when artifact was created.

Type:

str

updated_at

ISO timestamp when artifact was last updated.

Type:

str

metadata

Optional metadata dictionary.

Type:

dict[str, object]

classmethod from_dict(data)[source]

Create an artifact from a dictionary.

Parameters:

data (dict[str, object])

Return type:

Artifact

to_dict()[source]

Convert artifact to dictionary for JSON serialization.

Return type:

dict[str, object]

exception ralph.mcp.artifacts.store.ArtifactExistsError[source]

Bases: ArtifactError

Raised when attempting to create an artifact that already exists.

exception ralph.mcp.artifacts.store.ArtifactNotFoundError[source]

Bases: ArtifactError

Raised when an artifact is not found.

class ralph.mcp.artifacts.store.ArtifactPersistence(backend=<ralph.mcp.artifacts._path_file_backend.PathFileBackend object>, now_iso=<function _utc_now_iso>)[source]

Bases: object

Backend and clock dependencies for artifact persistence operations.

Parameters:
class ralph.mcp.artifacts.store.ArtifactSubmitOptions(metadata=None, overwrite=False, persistence=<factory>)[source]

Bases: object

Options for artifact submission.

Parameters:
class ralph.mcp.artifacts.store.ArtifactUpdateOptions(content=None, metadata=None, persistence=<factory>)[source]

Bases: object

Options for updating an existing artifact.

Parameters:
  • content (dict[str, object] | None)

  • metadata (dict[str, object] | None)

  • persistence (ArtifactPersistence)

ralph.mcp.artifacts.store.delete_artifact(artifact_dir, name, *, backend=<ralph.mcp.artifacts._path_file_backend.PathFileBackend object>)[source]

Delete an artifact.

Parameters:
  • artifact_dir (Path) – Directory where artifacts are stored.

  • name (str) – Artifact name.

  • backend (FileBackend)

Raises:

ArtifactNotFoundError – If artifact does not exist.

Return type:

None

ralph.mcp.artifacts.store.get_artifact(artifact_dir, name, *, backend=<ralph.mcp.artifacts._path_file_backend.PathFileBackend object>)[source]

Retrieve an artifact by name.

Parameters:
  • artifact_dir (Path) – Directory where artifacts are stored.

  • name (str) – Artifact name.

  • backend (FileBackend)

Returns:

The artifact.

Raises:

ArtifactNotFoundError – If artifact does not exist.

Return type:

Artifact

ralph.mcp.artifacts.store.list_artifacts(artifact_dir, *, backend=<ralph.mcp.artifacts._path_file_backend.PathFileBackend object>)[source]

List all artifacts in the directory.

Parameters:
  • artifact_dir (Path) – Directory where artifacts are stored.

  • backend (FileBackend)

Returns:

List of artifacts.

Return type:

list[Artifact]

ralph.mcp.artifacts.store.submit_artifact(artifact_dir, name, artifact_type, content, options=None)[source]

Submit a new artifact.

Parameters:
  • artifact_dir (Path) – Directory to store artifacts (e.g., .agent/artifacts/).

  • name (str) – Unique artifact name.

  • artifact_type (str) – Type of artifact.

  • content (dict[str, object]) – Artifact content.

  • options (ArtifactSubmitOptions | None) – Optional submission options.

Returns:

The created artifact.

Raises:

ArtifactExistsError – If artifact exists and overwrite is False.

Return type:

Artifact

ralph.mcp.artifacts.store.update_artifact(artifact_dir, name, options=None)[source]

Update an existing artifact.

Parameters:
  • artifact_dir (Path) – Directory where artifacts are stored.

  • name (str) – Artifact name.

  • content – New content (merged with existing).

  • metadata – New metadata (merged with existing).

  • options (ArtifactUpdateOptions | None)

Returns:

The updated artifact.

Raises:

ArtifactNotFoundError – If artifact does not exist.

Return type:

Artifact

ralph.mcp.artifacts.smoke_test_result

Structured smoke_test_result artifact validation helpers.

exception ralph.mcp.artifacts.smoke_test_result.SmokeTestResultValidationError[source]

Bases: ValueError

Raised when a smoke_test_result artifact is malformed.

ralph.mcp.artifacts.smoke_test_result.normalize_smoke_test_result_content(content)[source]

Validate and normalize a raw smoke_test_result content dict.

Parameters:

content (dict[str, object])

Return type:

dict[str, object]

ralph.mcp.artifacts.smoke_test_result.read_smoke_test_result_artifact(repo_root)[source]

Read and validate the persisted smoke_test_result artifact content from the workspace.

Returns None when the artifact file does not exist, cannot be read, or fails schema validation against SmokeTestResult. This prevents a malformed or incomplete artifact from influencing pass/fail decisions.

Parameters:

repo_root (Path)

Return type:

dict[str, object] | None

ralph.mcp.artifacts.product_spec

Structured product_spec artifact validation helpers and PROMPT.md rendering.

class ralph.mcp.artifacts.product_spec.ProductSpec(*, title, scope, goals, users, constraints=<factory>, success_criteria, product_behavior=<factory>, ux_ui_requirements=<factory>, scope_boundaries=<factory>, open_questions=<factory>)[source]

Bases: BaseModel

Validated schema for a product_spec artifact.

Parameters:
  • title (Annotated[str, MinLen(min_length=1)])

  • scope (Annotated[str, MinLen(min_length=1)])

  • goals (Annotated[list[str], MinLen(min_length=1)])

  • users (Annotated[list[str], MinLen(min_length=1)])

  • constraints (list[str])

  • success_criteria (Annotated[list[str], MinLen(min_length=1)])

  • product_behavior (list[str])

  • ux_ui_requirements (list[str])

  • scope_boundaries (list[str])

  • open_questions (list[str])

model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

exception ralph.mcp.artifacts.product_spec.ProductSpecValidationError[source]

Bases: Exception

Raised when product_spec content fails validation.

ralph.mcp.artifacts.product_spec.normalize_product_spec_content(content)[source]

Validate and normalize a raw product_spec content dict.

Parameters:

content (dict[str, object])

Return type:

dict[str, object]

ralph.mcp.artifacts.product_spec.read_product_spec_artifact(repo_root)[source]

Read the persisted product_spec artifact content from the workspace.

Parameters:

repo_root (Path)

Return type:

dict[str, object] | None

ralph.mcp.artifacts.product_spec.render_product_spec_as_prompt(spec)[source]

Render a product_spec dict as a PROMPT.md-formatted string.

The output follows the canonical PROMPT.md structure: - # Goal: title (bold) + scope paragraph - ## Context: goals, users, constraints, product_behavior, ux_ui_requirements - ## Acceptance criteria: success_criteria bullets - ## Notes: scope_boundaries + open_questions (only if non-empty)

Parameters:

spec (dict[str, object])

Return type:

str

ralph.mcp.artifacts.typed_artifacts

Structured validation for typed non-plan artifact payloads.

Covers: issues, fix_result, and analysis decision artifacts.

exception ralph.mcp.artifacts.typed_artifacts.TypedArtifactValidationError[source]

Bases: ValueError

Raised when a typed artifact payload is malformed.

ralph.mcp.artifacts.typed_artifacts.normalize_analysis_decision_content(content, *, allowed_statuses=None)[source]

Validate and normalize an analysis decision artifact content dict.

Parameters:
  • content (dict[str, object])

  • allowed_statuses (Collection[str] | None)

Return type:

dict[str, object]

ralph.mcp.artifacts.typed_artifacts.normalize_commit_cleanup_content(content)[source]

Validate and normalize a raw commit_cleanup artifact content dict.

Parameters:

content (dict[str, object])

Return type:

dict[str, object]

ralph.mcp.artifacts.typed_artifacts.normalize_fix_result_content(content)[source]

Validate and normalize a raw fix_result artifact content dict.

Parameters:

content (dict[str, object])

Return type:

dict[str, object]

ralph.mcp.artifacts.typed_artifacts.normalize_issues_content(content)[source]

Validate and normalize a raw issues artifact content dict.

Parameters:

content (dict[str, object])

Return type:

dict[str, object]

ralph.mcp.artifacts.state_db

Per-workspace SQLite store for machine-only run bookkeeping.

Replaces one-file-per-event bookkeeping under .agent/ (receipts, completion sentinels) with a single WAL-mode database at .agent/state.db. Motivation: on long multi-instance runs the per-event file creates were a measurable share of the macOS fseventsd event storm, and the files accumulated without bound.

Scope rule: ONLY machine-only state belongs here. Anything an agent or a human reads through workspace file tools (PLAN.md, prompts, artifact JSON, exec spills) stays a plain file.

Concurrency: the MCP server process writes while the engine process reads. WAL mode plus a busy timeout covers that on a local filesystem. Every public method opens no extra connections; one connection per RunStateDB instance, serialized by SQLite itself.

ralph.mcp.artifacts.state_db.CLEARED_SENTINEL_HMAC: Final[str] = '__ralph_internal_cleared__'

Tombstone marker written to completion_sentinels.hmac when a delete_completion_sentinel call raises sqlite3.Error so the downstream reader (_db_sentinel_lookup) honours the cleared state even though the row could not be physically removed. A model with workspace write access cannot forge a sentinel with this exact marker because the read path treats it as “not completed” and the HMAC secret is owned by the broker, not the agent.

class ralph.mcp.artifacts.state_db.RunStateDB(workspace_root)[source]

Bases: object

Handle to the workspace bookkeeping database (create-on-open).

Parameters:

workspace_root (Path)

mark_completion_sentinel_cleared(run_id)[source]

Write a tombstone marker so the reader honours the cleared state.

Used as the durable-fallback when delete_completion_sentinel raises sqlite3.Error (locked / corrupt / unsupported WAL): physically removing the row is best-effort, but the read path must observe the cleared state so a reused run_id cannot inherit a previous run’s “completed” verdict.

_db_sentinel_lookup recognises CLEARED_SENTINEL_HMAC and returns (False, None) so _check_completion_sentinel falls through to the legacy-file path. A successful upsert here replaces any existing row (including a valid HMAC row), so even if a future retry of delete_completion_sentinel fails the cleared state remains authoritative.

Parameters:

run_id (str)

Return type:

None

prune_older_than(cutoff, *, keep_run_id=None)[source]

Delete aged rows from both tables. Returns total row count removed.

Used by the run-start retention sweep (RFC-013 P3) so DB rows do not accumulate alongside the file-glob bookkeeping sweep.

When keep_run_id is provided, rows for that run are skipped regardless of age — mirrors the file-path keep_run_id contract so the DB-backed retention behavior matches the on-disk convention during the rollout.

Parameters:
  • cutoff (float)

  • keep_run_id (str | None)

Return type:

int

ralph.mcp.protocol

Shared MCP protocol plumbing.

This sub-package contains transport, session, and capability-mapping code used by both the Ralph MCP server (Ralph → agents) and the upstream client (Ralph → external MCPs). Keeping these in a neutral location avoids import cycles between server/ and upstream/.

ralph.mcp.protocol.capability_mapping

MCP capability mapping for Ralph sessions.

Ports the Rust capability-mapping layer used to translate session drain and policy outcomes into MCP access-control decisions.

class ralph.mcp.protocol.capability_mapping.AccessDecision(allowed, reason=None, code=None)[source]

Bases: object

Result of an MCP access decision.

Parameters:
classmethod allow()[source]

Build an allow decision.

Return type:

AccessDecision

classmethod deny(reason, code)[source]

Build a deny decision.

Parameters:
Return type:

AccessDecision

is_allowed()[source]

Return whether access is allowed.

Return type:

bool

class ralph.mcp.protocol.capability_mapping.AccessDeniedCode(*values)[source]

Bases: StrEnum

Categorical access-denial codes.

class ralph.mcp.protocol.capability_mapping.AccessMode(*values)[source]

Bases: StrEnum

Server access mode for MCP tool dispatch.

allows_write()[source]

Return whether this access mode allows write operations.

Return type:

bool

class ralph.mcp.protocol.capability_mapping.Capability(*values)[source]

Bases: StrEnum

Internal Ralph capability vocabulary.

class ralph.mcp.protocol.capability_mapping.DrainClass(*values)[source]

Bases: StrEnum

Drain class used for capability defaults.

allows_write()[source]

Return whether this drain class allows write operations.

Return type:

bool

class ralph.mcp.protocol.capability_mapping.McpCapability(*values)[source]

Bases: StrEnum

Typed MCP capability vocabulary.

class ralph.mcp.protocol.capability_mapping.PolicyMode(*values)[source]

Bases: StrEnum

Runtime policy mode enforced by the MCP server.

access_mode()[source]

Return the matching access mode.

Return type:

AccessMode

class ralph.mcp.protocol.capability_mapping.PolicyOutcome(status, reason=None, restriction=None)[source]

Bases: object

Normalized policy outcome payload.

Parameters:
class ralph.mcp.protocol.capability_mapping.PolicyOutcomeStatus(*values)[source]

Bases: StrEnum

Normalized policy outcome status.

class ralph.mcp.protocol.capability_mapping.SessionDrain(*values)[source]

Bases: StrEnum

Pipeline drain identity for a Ralph session.

ralph.mcp.protocol.capability_mapping.check_mcp_capability_policy(capability, ephemeral, tracked, mapped_outcome)[source]

Decide access for an MCP capability from session policy outcomes.

Parameters:
  • capability (McpCapability | str)

  • ephemeral (object)

  • tracked (object)

  • mapped_outcome (tuple[TypeAliasForwardRef('ralph.mcp.protocol.capability_mapping.Capability') | str, object] | None)

Return type:

AccessDecision

ralph.mcp.protocol.capability_mapping.drain_class_for_session(drain, agents_policy=None)[source]

Classify a session drain into its drain class.

Resolution is policy-defined only: callers must supply agents_policy and the drain must be declared there with an explicit drain_class.

Parameters:
Return type:

DrainClass

ralph.mcp.protocol.capability_mapping.drain_to_access_mode(drain, agents_policy=None)[source]

Determine the MCP access mode for a session drain.

Parameters:
Return type:

AccessMode

ralph.mcp.protocol.capability_mapping.drain_to_policy_mode(drain, agents_policy=None)[source]

Map a session drain to the matching policy mode.

Accepts any policy-declared drain name by resolving its class through drain_class_for_session. DrainClass and PolicyMode share the same vocabulary, so the mapping is a direct value lookup.

Parameters:
Return type:

PolicyMode

ralph.mcp.protocol.capability_mapping.evaluate_mapped_capability(capability, mapped_outcome)[source]

Evaluate access for a capability that maps directly to a Ralph capability.

Parameters:
  • capability (McpCapability | str)

  • mapped_outcome (tuple[TypeAliasForwardRef('ralph.mcp.protocol.capability_mapping.Capability') | str, object] | None)

Return type:

AccessDecision

ralph.mcp.protocol.capability_mapping.evaluate_workspace_write(ephemeral, tracked)[source]

Evaluate the composite workspace-write policy.

Parameters:
  • ephemeral (object)

  • tracked (object)

Return type:

AccessDecision

ralph.mcp.protocol.capability_mapping.lookup_ralph_capability(capability)[source]

Look up the Ralph capability mapped from an MCP capability.

Parameters:

capability (McpCapability | str)

Return type:

TypeAliasForwardRef(‘ralph.mcp.protocol.capability_mapping.Capability’) | None

ralph.mcp.protocol.capability_mapping.policy_from_outcome(outcome)[source]

Convert a Ralph policy outcome to an MCP access decision.

Parameters:

outcome (object)

Return type:

AccessDecision

ralph.mcp.protocol.env

Shared Ralph MCP environment variable names.

class ralph.mcp.protocol.env.McpEnvVar(*values)[source]

Bases: StrEnum

Environment variable names used to configure the Ralph MCP connection.

ralph.mcp.protocol.session

Shared session metadata for standalone Ralph MCP processes.

class ralph.mcp.protocol.session.AgentSession(session_id, run_id, drain, capabilities=<factory>, policy_flags=None, created_at=<factory>, parallel_worker=False, edit_area_result=None, worker_artifact_dir=None, worker_namespace=None, allowed_roots=<factory>, media_manifest=<factory>, model_identity=MultimodalModelIdentity(provider='unknown', model_id=None, transport=None), stored_capability_profile=None, broker_secret=None, tool_output_sink_entry=None)[source]

Bases: object

Lightweight session holder used by standalone Ralph MCP tooling.

Parameters:
  • session_id (str)

  • run_id (str)

  • drain (str)

  • capabilities (set[str])

  • policy_flags (set[str] | None)

  • created_at (float)

  • parallel_worker (bool)

  • edit_area_result (object)

  • worker_artifact_dir (Path | None)

  • worker_namespace (Path | None)

  • allowed_roots (tuple[Path, ...])

  • media_manifest (MediaManifest)

  • model_identity (MultimodalModelIdentity)

  • stored_capability_profile (ResolvedCapabilityProfile | None)

  • broker_secret (str | None)

  • tool_output_sink_entry (ToolOutputSinkEntry | None)

broker_secret: str | None = None

broker-owned secret threaded into the run-scoped receipt / completion sentinel HMAC. None means the pre-P3 contract (no HMAC enforcement). The broker process owns the secret; the agent never sees it.

Type:

RFC-013 P3

property capability_profile: ResolvedCapabilityProfile

Return the stored profile when present, otherwise resolve from model_identity.

current_thread_tool_output_sink()[source]

Return the sink only when the calling thread owns it.

Dispatches capture this once at composition time; chunks from a request’s subprocess reader threads then flow through the captured sink, immune to a concurrent request re-swapping the shared attribute. The (owner, sink) pair is read with a single attribute load, so a concurrent swap can never produce a torn owner/sink combination.

Return type:

Callable[[dict[str, object]], None] | None

tool_output_sink_entry: ToolOutputSinkEntry | None = None

Atomic (owner thread ident, sink) pair for exec output streaming. The session is shared across concurrent request threads; without ownership, overlapping exec streams route output to whichever connection swapped the sink last. Stored as ONE attribute so readers can never tear it.

class ralph.mcp.protocol.session.McpSession(*args, **kwargs)[source]

Bases: Protocol

Full structural contract for MCP server session objects.

Both implementations — the in-memory AgentSession (used by tests) and the production FileBackedSession (standalone server via session_from_env) — must satisfy this protocol. session_from_env returns this type, so mypy ralph/ (run by make verify) enforces structural conformance of both; surface drift between the two shipped a production AttributeError that hung MCP clients (the -32001 retry storm).

property allowed_roots: tuple[Path, ...]

Tuple of filesystem roots the session is permitted to read or write.

property broker_secret: str | None

broker-owned secret threaded into the run-scoped receipt / completion sentinel HMAC. None means the pre-P3 contract (no HMAC enforcement).

Both implementations expose this as a read-only attribute: AgentSession declares it as a dataclass field (with a default of None) and FileBackedSession exposes it as a property backed by the constructor-supplied value.

Type:

RFC-013 P3

property capabilities: set[str]

Set of capability identifiers granted to the session by the agent’s auth contract.

property capability_profile: ResolvedCapabilityProfile | None

Effective capability profile, falling back to model_identity when uncached.

check_capability(capability, /)[source]

Return whether the session may use capability (approved/denied or structured).

Parameters:

capability (str)

Return type:

object

check_edit_area(path, /)[source]

Return whether path is inside the session’s allowed edit area.

Parameters:

path (str)

Return type:

object

property created_at: float

Unix timestamp (seconds) at which the session was first instantiated.

current_thread_tool_output_sink()[source]

Return the tool-output sink only when the calling thread owns it.

Return type:

Callable[[dict[str, object]], None] | None

property drain: str

Logical phase drain the session is bound to (e.g. planning, development).

property edit_area_result: object

Cached result of the edit-area validation for this session’s worker, if any.

is_parallel_worker()[source]

Return True if the session is a parallel-worker subprocess rather than the main agent.

Return type:

bool

property media_manifest: MediaManifest

Per-session manifest tracking media references for upstream / proxy responses.

property model_identity: MultimodalModelIdentity

Identity of the active multimodal model used to resolve capability profiles.

property parallel_worker: bool

True if the session is a parallel-worker subprocess rather than the main agent.

property policy_flags: set[str] | None

Optional set of policy-flag identifiers that further restrict the session’s surface.

property run_id: str

Run identifier that owns this session, used for cross-record correlation.

property session_id: str

Stable identifier for the session, unique per logical MCP server invocation.

property stored_capability_profile: ResolvedCapabilityProfile | None

Cached resolved capability profile for the active model, or None to re-resolve.

property worker_artifact_dir: Path | None

Directory the worker writes its per-worker artifact evidence under, or None.

property worker_namespace: Path | None

Per-worker scratch namespace, isolated from sibling workers and the main checkout.

class ralph.mcp.protocol.session.MediaManifest(_entries=<factory>, _identity_index=<factory>, max_entries=256)[source]

Bases: object

Session-scoped manifest of all multimodal resource references.

Parameters:
  • _entries (OrderedDict[str, ManifestEntry])

  • _identity_index (dict[str, str])

  • max_entries (int)

add(*, title, mime_type, modality, raw_bytes, extras=None)[source]

Add or replace an artifact and return its manifest entry.

Re-adding an EXISTING identity dedups in place and PRESERVES the original insertion order so the resources/list output order stays stable across duplicate adds (wt-024 analysis feedback: reordering on re-add was an externally visible behavior change that the memory cap did not require).

Parameters:
  • title (str)

  • mime_type (str)

  • modality (str)

  • raw_bytes (bytes)

  • extras (MediaEntryExtras | None)

Return type:

ManifestEntry

get(artifact_id)[source]

Retrieve a manifest entry by artifact_id, or None if not found.

Parameters:

artifact_id (str)

Return type:

ManifestEntry | None

is_empty()[source]

Return True if no artifacts have been stored.

Return type:

bool

list_entries()[source]

Return all manifest entries in insertion order.

Return type:

list[ManifestEntry]

ralph.mcp.protocol.session.ToolOutputSinkEntry

An exec-streaming sink paired with the thread ident that owns it. The pair lives in ONE attribute so a concurrent reader performs a single (atomic) attribute load and can never observe a torn owner/sink combination — the TOCTOU that routed one request’s exec output onto another’s connection. An owner of None means “any thread” — used by single-tenant embeddings where the exec reader threads run on a different thread than the request thread; the production _FallbackHttpHandler stamps the real request-thread ident.

alias of tuple[int | None, Callable[[dict[str, object]], None]]

ralph.mcp.protocol.session.session_has_capability(granted, requested)[source]

Return True if the requested capability is present in the granted set.

Parameters:
  • granted (set[str])

  • requested (str)

Return type:

bool

ralph.mcp.protocol.startup

MCP server startup helpers ported from ralph-workflow/src/mcp_server/startup.rs.

class ralph.mcp.protocol.startup.HeartbeatPolicy(interval)[source]

Bases: object

Supervision interval configuration for active MCP health monitoring.

Parameters:

interval (timedelta)

class ralph.mcp.protocol.startup.HttpEndpointTarget(address, host_header, path)[source]

Bases: object

Resolved address, Host header, and path for an HTTP MCP endpoint.

Parameters:
  • address (tuple[str, int])

  • host_header (str)

  • path (str)

ralph.mcp.protocol.startup.HttpJsonRpcWithSessionFn

alias of object

ralph.mcp.protocol.startup.HttpPostFn

alias of object

exception ralph.mcp.protocol.startup.PreflightError[source]

Bases: Exception

Base class for MCP preflight failures.

exception ralph.mcp.protocol.startup.SessionBridgeError[source]

Bases: Exception

Raised when the session bridge fails to start or preflight fails.

ralph.mcp.protocol.startup.access_mode_for_drain(drain, agents_policy=None)[source]

Expose the MCP access mode mapping from the Rust startup module.

Thin public wrapper around ralph.mcp.protocol.capability_mapping.drain_to_access_mode() that re-exports the MCP access-mode mapping under the ralph.mcp.protocol.startup namespace. Callers that already depend on the startup module (heartbeat, preflight, JSON-RPC helpers) do not need a second import path; the mapping logic itself lives in the capability-mapping module.

A drain is one of the named session-drain values declared by an agent policy. The returned access mode describes whether the MCP session opened for that drain may read only or also write to the workspace.

Parameters:
  • drain (str) – Drain name to resolve. Accepts a SessionDrain instance or a string. Unknown drain names fall through to READ_ONLY because the resolver cannot determine that a write-capable class is allowed.

  • agents_policy (AgentsPolicy | None) – Optional AgentsPolicy whose declared drains should be consulted. When None (the default), the resolver falls back to the default agent policy loaded by ralph.mcp.protocol.capability_mapping, which is the conservative choice for callers that have not parsed an agents.toml yet.

Returns:

READ_WRITE when the drain’s class explicitly allows writes; READ_ONLY for every other case (read-only drains, unknown drains, or an empty agents_policy).

Return type:

ralph.mcp.protocol._access_mode.AccessMode

Side effects:

None. The function is a pure mapping from drain name to access mode and does not touch the filesystem, network, or any runtime state.

See also

ralph.mcp.protocol.capability_mapping.drain_to_access_mode() contains the actual implementation; this function exists for callers that import from the startup namespace.

ralph.mcp.protocol.startup.ensure_no_preflight_error(label, error)[source]

Raise PermanentPreflightError when error is not None.

Parameters:
  • label (str)

  • error (object)

Return type:

None

ralph.mcp.protocol.startup.extract_preflight_tool_names(result, label)[source]

Extract tool names from a JSON-RPC tools/list result object.

Parameters:
  • result (object)

  • label (str)

Return type:

list[str]

ralph.mcp.protocol.startup.heartbeat_policy_from_env(env=None)[source]

Return the configured MCP supervision check interval.

Parameters:

env (Mapping[str, str] | None)

Return type:

HeartbeatPolicy

ralph.mcp.protocol.startup.initialize_request()[source]

Build the JSON-RPC initialize request payload.

Return type:

dict[str, object]

ralph.mcp.protocol.startup.initialized_notification()[source]

Build the JSON-RPC notifications/initialized payload.

Return type:

dict[str, object]

ralph.mcp.protocol.startup.legacy_sse_jsonrpc_exchange(endpoint, requests, *, timeout_s)[source]

Exchange a sequence of JSON-RPC messages over a legacy SSE stream.

Connects to the SSE endpoint, reads the message endpoint URL, posts each request, and collects responses for requests that carry an id.

Raises:

PermanentPreflightError – On connection failure, unexpected HTTP status, or malformed JSON-RPC payload.

Parameters:
  • endpoint (str)

  • requests (Iterable[dict[str, object]])

  • timeout_s (float)

Return type:

list[dict[str, object]]

ralph.mcp.protocol.startup.looks_like_legacy_sse_endpoint(endpoint)[source]

Return True when endpoint ends with the legacy /sse path.

Parameters:

endpoint (str)

Return type:

bool

ralph.mcp.protocol.startup.mcp_preflight_timeout_from_env(env=None)[source]

Return the configured MCP preflight timeout duration.

Parameters:

env (Mapping[str, str] | None)

Return type:

timedelta

ralph.mcp.protocol.startup.mcp_probe_timeout_from_env(env=None)[source]

Return the configured MCP responsiveness probe timeout duration.

Parameters:

env (Mapping[str, str] | None)

Return type:

timedelta

ralph.mcp.protocol.startup.parse_http_endpoint(endpoint)[source]

Parse an HTTP MCP endpoint URL into an HttpEndpointTarget.

Raises:

ValueError – For unsupported schemes or missing host.

Parameters:

endpoint (str)

Return type:

HttpEndpointTarget

ralph.mcp.protocol.startup.parse_tcp_endpoint(endpoint)[source]

Parse a tcp:// endpoint URL into a (host, port) tuple.

Parameters:

endpoint (str)

Return type:

tuple[str, int]

ralph.mcp.protocol.startup.post_http_jsonrpc(endpoint_or_target, target_or_payload, payload=None)[source]

Post a single JSON-RPC request and return the response payload.

Parameters:
Return type:

dict[str, object]

ralph.mcp.protocol.startup.post_http_jsonrpc_with_session(endpoint_or_target, target_or_payload, payload=None, *, session_id=None, post_fn=<function _default_http_post>)[source]

Post a JSON-RPC request and return the response plus session id.

Accepts either an endpoint URL plus explicit payload, or an HttpEndpointTarget plus payload. Propagates the mcp-session-id header when the server returns one.

Raises:
  • RetryablePreflightError – On transport-level failures.

  • PermanentPreflightError – On HTTP error status or unparseable JSON.

Parameters:
  • endpoint_or_target (str | HttpEndpointTarget)

  • target_or_payload (HttpEndpointTarget | dict[str, object])

  • payload (dict[str, object] | None)

  • session_id (str | None)

  • post_fn (object)

Return type:

tuple[dict[str, object], str | None]

ralph.mcp.protocol.startup.preflight_http_attempt(endpoint, target, required_tools, remaining, *, post_with_session_fn=None)[source]

Verify an HTTP MCP endpoint by performing initialize + tools/list.

Automatically chooses the legacy SSE exchange when the endpoint path ends with /sse; otherwise uses stateful HTTP JSON-RPC posts.

Raises:
  • PermanentPreflightError – On protocol errors or missing tools.

  • RetryablePreflightError – On transient network failures.

Parameters:
Return type:

None

ralph.mcp.protocol.startup.preflight_http_mcp_server_tools(endpoint, required_tools, timeout)[source]

Run preflight tool verification against an HTTP MCP endpoint.

Parameters:
  • endpoint (str)

  • required_tools (Iterable[str])

  • timeout (timedelta)

Return type:

None

ralph.mcp.protocol.startup.preflight_mcp_server_tools(endpoint, required_tools, timeout)[source]

Ensure the MCP server reports every tool that Ralph exposes.

Parameters:
  • endpoint (str)

  • required_tools (Iterable[str])

  • timeout (timedelta)

Return type:

None

ralph.mcp.protocol.startup.probe_mcp_http_endpoint(endpoint, timeout)[source]

Probe endpoint with a bounded timeout and no required tools.

Wraps preflight_http_attempt so the timeout is applied to every underlying HTTP call. Raises the same errors as preflight_http_attempt.

Parameters:
  • endpoint (str)

  • timeout (timedelta)

Return type:

None

ralph.mcp.protocol.startup.read_jsonrpc_response(reader)[source]

Read and parse a Content-Length-framed JSON-RPC response from a stream.

Parameters:

reader (io.BufferedReader)

Return type:

JsonRpcResponse

ralph.mcp.protocol.startup.read_legacy_sse_message_endpoint(endpoint, lines)[source]

Read the SSE event that advertises the JSON-RPC message endpoint.

Parameters:
  • endpoint (str)

  • lines (Iterable[str])

Return type:

str

ralph.mcp.protocol.startup.tools_list_request()[source]

Build the JSON-RPC tools/list request payload.

Return type:

dict[str, object]

ralph.mcp.protocol.startup.write_jsonrpc_request(sock, value)[source]

Serialise and send a JSON-RPC message with a Content-Length header.

Parameters:
  • sock (socket)

  • value (dict[str, object])

Return type:

None

ralph.mcp.protocol.transport

MCP transport layer.

Provides transport abstractions for MCP communication. Supports stdio and HTTP/SSE connections to MCP clients and servers.

class ralph.mcp.protocol.transport.MCPMessage(method, params=None, msg_id=None, error=None)[source]

Bases: object

Represents an MCP message.

Parameters:
  • method (str)

  • params (dict[str, object] | None)

  • msg_id (str | int | None)

  • error (dict[str, object] | None)

ralph.mcp.protocol.transport.MCPTransport

alias of StdioTransport

class ralph.mcp.protocol.transport.StdioTransport(command, cwd=None, *, process_factory=None, thread_factory=None)[source]

Bases: object

MCP transport over stdio.

Communicates with an MCP server process via stdin/stdout. Each line is a JSON-RPC message.

Parameters:
  • command (list[str])

  • cwd (str | None)

  • process_factory (Callable[[list[str], str | None], ProcessLike] | None)

  • thread_factory (Callable[[Callable[[], None], bool], ThreadLike] | None)

async close()[source]

Close the transport.

Return type:

None

async recv()[source]

Receive messages as an async iterator.

Return type:

AsyncIterator[MCPMessage]

async send(message)[source]

Send a message to the MCP server.

Parameters:

message (MCPMessage)

Return type:

None

start()[source]

Start the MCP server process.

Return type:

None

exception ralph.mcp.protocol.transport.TransportError[source]

Bases: Exception

Raised when transport operations fail.

ralph.mcp.server

Standalone MCP server exports.

This package separates standalone server startup/shutdown helpers from the rest of the bridge implementation so callers can either launch an in-process session bridge or run the dedicated ralph-mcp HTTP runtime.

ralph.mcp.server.factory

Protocol and handle types for the MCP server factory abstraction.

class ralph.mcp.server.factory.McpServerFactory(*args, **kwargs)[source]

Bases: Protocol

Protocol that every MCP server factory implementation must satisfy.

class ralph.mcp.server.factory.McpServerHandle(endpoint, pid, shutdown)[source]

Bases: object

Return value from McpServerFactory.build carrying endpoint, PID, and shutdown hook.

Parameters:
  • endpoint (str)

  • pid (int)

  • shutdown (Callable[[], None])

ralph.mcp.server.factory_impl

Concrete MCP server factory that allocates dynamically bound localhost endpoints.

DynamicBindingMcpServerFactory is the production implementation of McpServerFactory. It reserves a unique TCP port per worker session, starts an MCP server subprocess via lifecycle.start_mcp_server, and returns a McpServerHandle that callers can use to reach the server or shut it down.

class ralph.mcp.server.factory_impl.DynamicBindingMcpServerFactory(workspace, *, reserve_port=None, start_server=<function start_mcp_server>, lifecycle_deps=None)[source]

Bases: McpServerFactory

Build MCP server handles with dynamically allocated localhost endpoints.

Parameters:

ralph.mcp.server.lifecycle

MCP server lifecycle helpers using a standalone localhost HTTP process.

class ralph.mcp.server.lifecycle.LifecycleDeps(reserve_port, create_session_file, subprocess_env, spawn_process, preflight, preflight_timeout, probe=None, probe_timeout=None)[source]

Bases: object

Injectable dependencies for MCP server lifecycle management.

Parameters:
  • reserve_port (Callable[[], int])

  • create_session_file (Callable[[Path, SessionLike], Path])

  • subprocess_env (Callable[[Path], dict[str, str]])

  • spawn_process (SpawnProcess)

  • preflight (PreflightFn)

  • preflight_timeout (Callable[[], timedelta])

  • probe (Callable[[str, timedelta], None] | None)

  • probe_timeout (Callable[[], timedelta] | None)

class ralph.mcp.server.lifecycle.McpRestartPolicy(max_restarts=20)[source]

Bases: object

Bounded restart policy for the MCP server bridge.

Parameters:

max_restarts (int)

exception ralph.mcp.server.lifecycle.McpServerError(message, *, restart_count)[source]

Bases: Exception

Raised when the MCP server fails and the restart budget is exhausted.

Parameters:
  • message (str)

  • restart_count (int)

Return type:

None

class ralph.mcp.server.lifecycle.McpServerExtras(phase=None, extra_env=None, restart_policy=None)[source]

Bases: object

Optional runtime extras for start_mcp_server.

Parameters:
  • phase (str | None)

  • extra_env (dict[str, str] | None)

  • restart_policy (McpRestartPolicy | None)

class ralph.mcp.server.lifecycle.ProcessLike(*args, **kwargs)[source]

Bases: Protocol

Subset of ManagedProcess API required by the MCP server lifecycle.

class ralph.mcp.server.lifecycle.RestartAwareMcpBridge(inner, *, restart_fn, restart_policy, run_id, probe_fn=None, probe_timeout_fn=None)[source]

Bases: object

SessionBridgeLike wrapper that auto-restarts the MCP server on crash.

Bounded restart budget prevents unbounded retry loops. All process spawning continues to flow through ProcessManager via the injected LifecycleDeps so ProcessManager remains the single process authority.

The endpoint URI is stable for the full bridge lifetime: the same host/port is reused on every restart so agents never see a changed MCP_ENDPOINT_ENV. Thread-safe: a lock guards all inner-process mutations so the background McpSupervisor can safely call check_health_and_restart_if_needed() while the main thread is executing agent output streaming.

Parameters:
check_health_and_restart_if_needed()[source]

Check if MCP server is alive and responsive; restart if not.

Treats the bridge as unhealthy when either (a) the subprocess has exited or (b) the subprocess is alive but the responsiveness probe times out or fails. On an unhealthy result the stale process is terminated, a new one is spawned via restart_fn (which reruns full preflight), and the bounded restart counter is incremented.

Returns True when a restart was performed. Raises McpServerError when the restart budget is exhausted. Thread-safe: may be called from a background McpSupervisor thread.

Return type:

bool

property process: ProcessLike

The currently running MCP server process.

Satisfies the _BridgeWithProcess protocol so consumers (e.g. the parallel worker session factory) can read the server pid. Reads the inner process under the lock because a concurrent health-check restart may swap it.

reset_session_budget()[source]

Re-arm the inner subprocess’s soft wrap-up nag for a fresh attempt.

The McpServer is a per-subprocess singleton; the bridge is its only client. Each attempt boundary (e.g. the start of every effect_executor._run_attempt) must re-arm the inner subprocess’s soft nag so a retried agent does not inherit the prior attempt’s elapsed time. The reset is exposed as the custom JSON-RPC method notifications/reset_wrapup over HTTP; the McpServer handles it via McpServer._dispatch_request by calling McpServer.reset_session_budget().

Best-effort: transport errors are logged and swallowed (the recovery controller’s attempt loop will retry on the next attempt boundary). The HTTP POST is bounded by _RESET_WRAPUP_TIMEOUT_S so audit_mcp_timeout stays green; a wedged or just-respawned inner process does not stall the attempt.

When the inner process is a MagicMock (e.g. in unit tests that do not exercise the HTTP path) the reset is a no-op. Same pattern as _verify_alias_present() so the bridge tests stay hermetic (a real HTTP POST against a fake endpoint would otherwise hit the 1.0-second per-test timeout and time out the suite).

Thread-safe: holds self._lock to serialize with concurrent check_health_and_restart_if_needed and reset_tool_registry calls so a respawn and a reset cannot interleave.

Note: the intra-bridge respawn paths (check_health_and_restart_if_needed() and reset_tool_registry()) do NOT call this method. A fresh subprocess starts with elapsed=0 by construction, so the next attempt boundary’s reset is sufficient — adding a second reset inside the bridge would only matter on a tight crash-respawn cycle and would risk blocking on a 1.0s test-timeout-budget in unit tests that use a FakeProcess (which has poll() but no real HTTP endpoint). The attempt-boundary wire-up at the top of effect_executor._run_attempt() is the single production seam that drives the reset.

Return type:

None

reset_tool_registry()[source]

Rebuild the visible tool list by rerunning the restart path.

Called by the recovery controller when the failure classifier flags a tool-availability failure (the post-tool-result wedge: live server reports No such tool available: mcp__<server>__<tool> because the agent’s tools/list snapshot lost the alias after a prior restart, retry, or transient recovery).

This is a separate counter from _restart_count so the orchestrator can distinguish a tool-registry rebuild from a crash restart. The cap is _TOOL_REGISTRY_MAX_RESETS.

Raises:

McpServerError – when the cap is exhausted. The message contains the substring 'tool-registry-reset exhausted' so the orchestrator can branch on it.

Return type:

None

property run_id: str

The session’s run identity — the receipt key the gate reads.

See ralph.mcp.protocol._session_bridge_like.SessionBridgeLike for the full contract; the property is the bridge-side realization of the protocol declaration.

property tool_registry_resets: int

Number of times reset_tool_registry() has been called.

Independent of restart_count (which tracks crash restarts) and the recovery controller’s max_recovery_attempts (which tracks agent-invocation retries). The orchestrator can inspect this counter to diagnose which cap fired on a wedged run.

class ralph.mcp.server.lifecycle.SessionBridgeLike(*args, **kwargs)[source]

Bases: Protocol

Protocol describing the session bridge interface used here.

agent_endpoint_uri()[source]

Return the agent-facing endpoint URI.

Return type:

str

endpoint_uri()[source]

Return the raw endpoint URI used for transport-level preflight.

Return type:

str

property run_id: str

The session’s run identity (the receipt key for the completion gate).

The submission handler stamps receipts with this run_id, and the completion gate looks them up with the same value. Anything that derives an MCP_RUN_ID_ENV or otherwise references “which run?” MUST read this property — never an independent label — so the receipt and the gate cannot disagree about run identity.

shutdown()[source]

Shut down the bridge.

Return type:

None

start()[source]

Start accepting MCP connections.

Return type:

None

class ralph.mcp.server.lifecycle.SpawnProcess(*args, **kwargs)[source]

Bases: Protocol

Callable that spawns the MCP server subprocess.

The phase keyword argument, when set, is used to label the process phase:<phase>:mcp-server so it is reaped by the phase-scope cleanup.

class ralph.mcp.server.lifecycle.StandaloneMcpProcess(endpoint, process, session_file)[source]

Bases: object

A running standalone MCP HTTP server process with its endpoint and session file.

Parameters:
  • endpoint (str)

  • process (ProcessLike)

  • session_file (Path)

ralph.mcp.server.lifecycle.check_mcp_bridge_health(bridge)[source]

Perform a health check on the MCP bridge, restarting if it crashed.

Only has an effect when bridge is a RestartAwareMcpBridge. Raises McpServerError when the restart budget is exhausted.

Parameters:

bridge (SessionBridgeLike)

Return type:

None

ralph.mcp.server.lifecycle.session_payload_json(session)[source]

Serialize the session metadata to a compact JSON string for MCP handshake.

Parameters:

session (ralph.mcp.protocol.startup.SessionLike)

Return type:

str

ralph.mcp.server.lifecycle.shutdown_mcp_server(bridge)[source]

Shutdown MCP server process.

Parameters:

bridge (SessionBridgeLike)

Return type:

None

ralph.mcp.server.lifecycle.start_mcp_server(session, workspace, *, upstream_registry=None, deps=None, extras=None)[source]

Start a standalone Ralph MCP HTTP subprocess and verify tool reachability.

Returns a RestartAwareMcpBridge that can auto-restart the server on crash up to the extras.restart_policy budget (default: 20 restarts, defined by McpRestartPolicy).

Parameters:
Return type:

RestartAwareMcpBridge

ralph.mcp.server.runtime

Standalone MCP HTTP server runtime for Ralph tools.

Runs the Ralph MCP server as a long-lived HTTP process that AI agents connect to over the MCP protocol. The server exposes Ralph’s tool registry (file operations, git commands, artifact submission, coordination, etc.) through the production streamable-HTTP transport (_FallbackStandaloneServer).

The architecture is intentionally single-path: there is exactly one server transport — the production _FallbackHttpHandler (constructed by _FallbackStandaloneServer) — and every behavior (tool dispatch, streaming, session handling, concurrency control, error framing) lives on that one path. See docs/agents/architecture.md for the rationale.

Key responsibilities:

  • RalphmcpServer - the main server class; call start(config) to launch and stop() to shut down gracefully. A health-check endpoint listens on /health; liveness is polled by ralph.process.mcp_supervisor.

  • Environment handshake: the server reads MCP_SESSION (session JSON) and MCP_SESSION_FILE env vars to populate the agent session, which governs which capabilities and upstream MCP servers are enabled.

  • Tool capability filtering: tools are registered or skipped based on the session’s declared McpCapability set so each agent only sees the tools it needs.

  • Upstream MCP registry: load_upstream_mcp_servers discovers additional MCP servers from UPSTREAM_MCP_CONFIG and mounts them alongside Ralph tools.

The server is launched by ralph.process.manager via the ralph-mcp entry point (ralph/mcp/server/__main__.py).

class ralph.mcp.server.runtime.FileBackedSession(path, *, loader=None, session_id_factory=None, run_id_factory=None, env_getter=None, broker_secret=None)[source]

Bases: object

Session view backed by a JSON file updated by the parent Ralph process.

Parameters:
  • path (Path)

  • loader (Callable[[Path], dict[str, object]] | None)

  • session_id_factory (Callable[[], str] | None)

  • run_id_factory (Callable[[], str] | None)

  • env_getter (Callable[[str], str | None] | None)

  • broker_secret (str | None)

property broker_secret: str | None

broker-owned secret threaded into the run-scoped receipt / completion sentinel HMAC. The value is set at FileBackedSession construction time and never round-trips through the on-disk payload (the secret must not be visible to the on-disk session file).

Type:

RFC-013 P3

current_thread_tool_output_sink()[source]

Return the sink only when the calling thread owns it (single atomic read).

Return type:

Callable[[dict[str, object]], None] | None

property worker_artifact_dir: Path | None

Return worker artifact dir from environment variable.

For parallel workers, the parent process sets WORKER_ARTIFACT_DIR in the subprocess environment. This property reads that value so that artifact submission can route to the correct per-worker namespace.

class ralph.mcp.server.runtime.JsonRpcRequest(jsonrpc, method, params=None, msg_id=None)[source]

Bases: object

Parsed representation of an incoming JSON-RPC request.

Parameters:
  • jsonrpc (str)

  • method (str)

  • params (dict[str, object] | None)

  • msg_id (object)

class ralph.mcp.server.runtime.McpServer(session, workspace, registry, *, expose_mcp_aliases=True, wrapup_provider=None, metrics=None, mcp_activity_sink=None)[source]

Bases: object

Lightweight MCP server that dispatches JSON-RPC requests to Ralph tools.

Per-invocation reset contract: a single McpServer is a per-subprocess singleton; it may be reused across multiple agent attempts within the same command-line invocation. The soft wrap-up nag (and the hard ceiling it warns about) is owned by ONE agent attempt: each attempt boundary MUST call reset_session_budget() (in-process) or send the wire-level notifications/reset_wrapup JSON-RPC method (over HTTP from RestartAwareMcpBridge) so the budget is re-armed. See ralph.mcp.server._session_wrapup for the underlying contract and ralph.pipeline.effect_executor._run_attempt for the production wire-up at the per-attempt boundary.

Parameters:
  • session (McpSession)

  • workspace (FsWorkspace)

  • registry (ToolBridge)

  • expose_mcp_aliases (bool)

  • wrapup_provider (Callable[[], str | None] | None)

  • metrics (McpMetrics | None)

  • mcp_activity_sink (Callable[[str], None] | None)

reset_session_budget()[source]

Re-arm the soft wrap-up nag (and the hard ceiling) for a fresh attempt.

Called by the orchestrator at the top of every _run_attempt in ralph.pipeline.effect_executor so a retried agent (e.g. after an artifact-missing failure) starts with elapsed=0 on the very first tool result instead of inheriting the prior attempt’s elapsed time.

The reset creates a fresh SessionWrapupBudget backed by the production SystemClock and the canonical SESSION_SOFT_WRAPUP_SECONDS / MAX_SESSION_SECONDS defaults from ralph.timeout_defaults. The previous budget is replaced in-place; the new provider retains the same Callable[[], str | None] signature so no caller signature changes.

No-op when wrapup_provider was None at construction time (the default; tests that do not exercise the nag have no provider to reset). The reset is also reachable over the wire via the notifications/reset_wrapup JSON-RPC method (see _dispatch_request()).

Return type:

None

class ralph.mcp.server.runtime.McpServerExtras(session=None, upstream_registry=None, mcp_config=None)[source]

Bases: object

Optional DI parameters for building standalone MCP servers.

The dataclass is the dependency-injection bundle threaded through build_standalone_http_server and run_standalone_server. Every field is optional (None means “use the production default”) so callers can override exactly one seam (e.g. an upstream registry for a test) without rebuilding the rest of the composition root.

Parameters:
session

Pre-built McpSession (handshake, capabilities, upstream mounts). When None the runtime reads MCP_SESSION / MCP_SESSION_FILE from the environment via session_from_env.

Type:

McpSession | None

upstream_registry

Pre-loaded UpstreamRegistry for upstream MCP servers. When None the runtime loads the registry from UPSTREAM_MCP_CONFIG / UPSTREAM_MCP_TOOL_CATALOG_ENV via load_runtime_upstream_servers.

Type:

UpstreamRegistry | None

mcp_config

Pre-parsed McpConfig (mcp.toml model). When None the runtime calls load_mcp_config with the user-global path.

Type:

McpConfig | None

class ralph.mcp.server.runtime.ServerState(*values)[source]

Bases: StrEnum

Lifecycle state of a running MCP server instance.

ralph.mcp.server.runtime.build_standalone_http_server(workspace_root, *, host='127.0.0.1', port=8000, extras=None)[source]

Build a standalone HTTP MCP server backed by the Ralph tool registry.

Parameters:
  • workspace_root (Path)

  • host (str)

  • port (int)

  • extras (McpServerExtras | None)

Return type:

_StandaloneHttpServer

ralph.mcp.server.runtime.load_runtime_upstream_servers(mcp_config, env=None)[source]

Merge upstream MCP servers from the environment variable and mcp.toml.

Parameters:
  • mcp_config (McpConfig)

  • env (Mapping[str, str] | None)

Return type:

tuple[UpstreamMcpServer, …]

ralph.mcp.server.runtime.main(argv=None)[source]

CLI entrypoint for the standalone Ralph MCP HTTP server.

The handler is the ralph-mcp console script entry point declared in pyproject.toml (ralph-mcp = ralph.mcp.server.runtime:main). It parses --workspace, --host, and --port via parse_args and delegates to run_standalone_server with the production DEFAULT_TRANSPORT (streamable-http). The environment handshake (MCP_SESSION / MCP_SESSION_FILE) is performed inside run_standalone_server; this entry point does not touch the environment directly.

Parameters:

argv (Sequence[str] | None) – Optional argv override. When None, argparse reads from sys.argv[1:]; tests pass a sequence of strings to avoid mutating process state.

Returns:

None. The handler blocks until the server is shut down (Ctrl-C / SIGTERM). The process exit code is whatever the underlying HTTP server / signal handler produces.

Return type:

None

Side effects:

Starts a long-lived HTTP server bound to --host:--port and a /health endpoint; the liveness probe is polled by ralph.process.mcp_supervisor. Reads the MCP session environment variables.

ralph.mcp.server.runtime.parse_args(argv=None)[source]

Parse standalone MCP server CLI arguments.

Parameters:

argv (Sequence[str] | None)

Return type:

argparse.Namespace

ralph.mcp.server.runtime.run_standalone_server(workspace_root, *, transport='streamable-http', host='127.0.0.1', port=8000)[source]

Run the standalone Ralph MCP server over HTTP.

Parameters:
  • workspace_root (Path)

  • transport (str)

  • host (str)

  • port (int)

Return type:

None

ralph.mcp.server.runtime.session_from_env(env=None, *, session_id_factory=None, run_id_factory=None)[source]

Load optional session metadata from the environment.

Returns the structural McpSession protocol — NOT a cast. mypy verifies both return branches (FileBackedSession and AgentSession) against the full session contract, so a public member added to one implementation but not the other is a type error in make verify, not a production AttributeError.

Parameters:
  • env (Mapping[str, str] | None)

  • session_id_factory (Callable[[], str] | None)

  • run_id_factory (Callable[[], str] | None)

Return type:

McpSession | None

ralph.mcp.server.runtime_session

Session implementations for the Ralph MCP server.

Provides FileBackedSession (backed by a JSON file written by the parent Ralph process) and session_from_env (reads session state from environment variables).

class ralph.mcp.server.runtime_session.FileBackedSession(path, *, loader=None, session_id_factory=None, run_id_factory=None, env_getter=None, broker_secret=None)[source]

Bases: object

Session view backed by a JSON file updated by the parent Ralph process.

Parameters:
  • path (Path)

  • loader (Callable[[Path], dict[str, object]] | None)

  • session_id_factory (Callable[[], str] | None)

  • run_id_factory (Callable[[], str] | None)

  • env_getter (Callable[[str], str | None] | None)

  • broker_secret (str | None)

property broker_secret: str | None

broker-owned secret threaded into the run-scoped receipt / completion sentinel HMAC. The value is set at FileBackedSession construction time and never round-trips through the on-disk payload (the secret must not be visible to the on-disk session file).

Type:

RFC-013 P3

current_thread_tool_output_sink()[source]

Return the sink only when the calling thread owns it (single atomic read).

Return type:

Callable[[dict[str, object]], None] | None

property worker_artifact_dir: Path | None

Return worker artifact dir from environment variable.

For parallel workers, the parent process sets WORKER_ARTIFACT_DIR in the subprocess environment. This property reads that value so that artifact submission can route to the correct per-worker namespace.

ralph.mcp.server.runtime_session.session_from_env(env=None, *, session_id_factory=None, run_id_factory=None)[source]

Load optional session metadata from the environment.

Returns the structural McpSession protocol — NOT a cast. mypy verifies both return branches (FileBackedSession and AgentSession) against the full session contract, so a public member added to one implementation but not the other is a type error in make verify, not a production AttributeError.

Parameters:
  • env (Mapping[str, str] | None)

  • session_id_factory (Callable[[], str] | None)

  • run_id_factory (Callable[[], str] | None)

Return type:

McpSession | None

ralph.mcp.server.exec_sse_streaming

SSE streaming core for the exec tool fallback HTTP path.

ralph.mcp.server.exec_sse_streaming.exec_sse_streaming_post(request, session, handle_request, state, *, write_frame)[source]

Stream exec output chunks as SSE notification frames, then write the final response.

Installs an atomic (owner thread, sink) entry on the session for the duration of the dispatch; the exec handler captures it once, on this thread, so output chunks flow to THIS connection and no other. The entry is cleared compare-and-swap on exit. Returns the updated ServerState from handle_request (or the original on error).

Parameters:
Return type:

ServerState

ralph.mcp.server.__main__

Entry point for python -m ralph.mcp.server.

ralph.mcp.multimodal

Ralph multimodal platform package.

Provides the shared contract for multimodal artifacts, provider/model capability detection, resource URI handling, session-scoped manifests, and failure taxonomy. All runtime layers that need multimodal behavior must derive their decisions from this package rather than duplicating provider or format knowledge.

ralph.mcp.multimodal.artifacts

Normalized multimodal artifact types for the MCP surface.

Defines the resource_reference content block shape that represents media artifacts that cannot be delivered inline (PDFs, audio, video, large images), as well as the modality class constants used across the multimodal platform.

class ralph.mcp.multimodal.artifacts.AudioContent(uri, mime_type, title, type='audio', delivery='typed_block')[source]

Bases: object

Typed audio content block referencing a manifest artifact.

Parameters:
  • uri (str)

  • mime_type (str)

  • title (str)

  • type (str)

  • delivery (str)

to_dict()[source]

Serialize to MCP-compatible content block dictionary.

Return type:

dict[str, object]

class ralph.mcp.multimodal.artifacts.DocumentContent(uri, mime_type, title, type='document', delivery='typed_block')[source]

Bases: object

Typed document content block referencing a manifest artifact.

Parameters:
  • uri (str)

  • mime_type (str)

  • title (str)

  • type (str)

  • delivery (str)

to_dict()[source]

Serialize to MCP-compatible content block dictionary.

Return type:

dict[str, object]

class ralph.mcp.multimodal.artifacts.ImageContent(data, mime_type, type='image', delivery='inline_image')[source]

Bases: object

Inline image content block delivered as base64-encoded bytes.

Parameters:
  • data (str)

  • mime_type (str)

  • type (str)

  • delivery (str)

to_dict()[source]

Serialize to MCP-compatible content block dictionary.

Return type:

dict[str, object]

class ralph.mcp.multimodal.artifacts.PdfContent(uri, mime_type, title, type='pdf', delivery='typed_block')[source]

Bases: object

Typed PDF content block referencing a manifest artifact.

Parameters:
  • uri (str)

  • mime_type (str)

  • title (str)

  • type (str)

  • delivery (str)

to_dict()[source]

Serialize to MCP-compatible content block dictionary.

Return type:

dict[str, object]

class ralph.mcp.multimodal.artifacts.ResourceReferenceContent(uri, mime_type, title, modality, type='resource_reference', delivery='resource_reference')[source]

Bases: object

Content block representing a media artifact via resource reference.

The delivery field distinguishes two cases:

  • 'resource_reference_replay': a Ralph-owned ralph://media/... artifact stored in the session manifest. The agent calls read_media with the URI to retrieve or replay the artifact. Always pass delivery=DeliveryMode.RESOURCE_REFERENCE_REPLAY explicitly when constructing these blocks.

  • 'resource_reference' (default): a URI-preserving upstream reference. The URI points to an external resource, not a Ralph-owned artifact. Used when an upstream MCP tool returns a URI-backed media block.

Parameters:
  • uri (str)

  • mime_type (str)

  • title (str)

  • modality (str)

  • type (str)

  • delivery (str)

to_dict()[source]

Serialize to MCP-compatible content block dictionary.

Return type:

dict[str, object]

class ralph.mcp.multimodal.artifacts.VideoContent(uri, mime_type, title, type='video', delivery='typed_block')[source]

Bases: object

Typed video content block referencing a manifest artifact.

Parameters:
  • uri (str)

  • mime_type (str)

  • title (str)

  • type (str)

  • delivery (str)

to_dict()[source]

Serialize to MCP-compatible content block dictionary.

Return type:

dict[str, object]

ralph.mcp.multimodal.artifacts.infer_modality_and_mime(extension)[source]

Return (modality, mime_type) for a file extension, or None if unknown.

Parameters:

extension (str)

Return type:

tuple[str, str] | None

ralph.mcp.multimodal.capabilities

Multimodal capability detection and delivery policy.

This module is the single source of truth for provider/model identity, capability detection, and delivery policy decisions. All runtime layers that need to determine whether a modality can be delivered must derive their answer from this module rather than re-declaring provider knowledge elsewhere.

class ralph.mcp.multimodal.capabilities.CapabilityVerdict(modality, delivery, provider, model_id=None, reason='', block_type=None)[source]

Bases: object

Result of checking whether a modality/delivery mode is supported.

Parameters:
  • modality (str)

  • delivery (DeliveryMode)

  • provider (str)

  • model_id (str | None)

  • reason (str)

  • block_type (str | None)

is_inline()[source]

Return True if inline image delivery is used.

Return type:

bool

is_resource_reference()[source]

Return True if resource-reference replay delivery will be used.

Return type:

bool

is_supported()[source]

Return True if the modality has any supported delivery mode.

Return type:

bool

is_typed_block()[source]

Return True if typed block delivery will be used.

Return type:

bool

class ralph.mcp.multimodal.capabilities.DeliveryMode(*values)[source]

Bases: StrEnum

How a multimodal artifact will be delivered to the model.

class ralph.mcp.multimodal.capabilities.MultimodalModelIdentity(provider, model_id=None, transport=None)[source]

Bases: object

Identifies the provider and model for capability detection.

Parameters:
  • provider (str)

  • model_id (str | None)

  • transport (str | None)

is_known()[source]

Return True if the provider identity is resolved (not ‘unknown’).

Return type:

bool

class ralph.mcp.multimodal.capabilities.ResolvedCapabilityProfile(identity, verdicts)[source]

Bases: object

Pre-computed capability verdicts for a resolved model identity.

This is the runtime-owned contract for multimodal delivery decisions. Downstream layers consume this profile from the session rather than re-calling get_delivery_mode() at each use site.

Parameters:
to_payload()[source]

Serialize to a JSON-compatible dict for session payload persistence.

Return type:

dict[str, object]

verdict_for(modality)[source]

Return the pre-computed verdict, or compute fresh for unlisted modalities.

Parameters:

modality (str)

Return type:

CapabilityVerdict

ralph.mcp.multimodal.capabilities.get_delivery_mode(identity, modality)[source]

Determine how to deliver a modality for the given model identity.

Returns a CapabilityVerdict indicating the delivery mode:

  • INLINE_IMAGE: provider accepts inline base64 image data.

  • TYPED_BLOCK: provider accepts a named typed block (pdf, document, audio, video).

  • RESOURCE_REFERENCE_REPLAY: unknown provider; multimodal surface stays visible via resource reference replay handle.

  • UNSUPPORTED: provider cannot accept this modality via Ralph’s managed path.

Unknown providers default to RESOURCE_REFERENCE_REPLAY (safe, keeps multimodal surface available without false typed-delivery promises).

Parameters:
Return type:

CapabilityVerdict

ralph.mcp.multimodal.capabilities.profile_from_payload(raw)[source]

Rehydrate a ResolvedCapabilityProfile from a serialized session payload dict.

Parameters:

raw (dict[str, object])

Return type:

ResolvedCapabilityProfile

ralph.mcp.multimodal.capabilities.resolve_capability_profile(identity)[source]

Build a pre-computed capability profile for all supported modalities.

Parameters:

identity (MultimodalModelIdentity)

Return type:

ResolvedCapabilityProfile

ralph.mcp.multimodal.errors

Shared multimodal failure taxonomy for Ralph’s managed MCP runtime path.

All code that needs to emit or classify a multimodal failure must use these types rather than constructing ad hoc error strings. This keeps failure messages consistent and machine-inspectable across capability detection, tool handlers, upstream normalization, and invoke-time checks.

class ralph.mcp.multimodal.errors.MultimodalFailure(kind, message, modality=None, provider=None, model_id=None)[source]

Bases: object

A structured description of why a multimodal operation could not complete.

Parameters:
  • kind (MultimodalFailureKind)

  • message (str)

  • modality (str | None)

  • provider (str | None)

  • model_id (str | None)

user_message()[source]

Return a human-readable failure message suitable for tool output.

Return type:

str

class ralph.mcp.multimodal.errors.MultimodalFailureKind(*values)[source]

Bases: StrEnum

Enumerated reasons why a multimodal operation failed.

ralph.mcp.multimodal.resources

URI builder/parser and session-scoped manifest for ralph://media resources.

The manifest tracks every resource_reference artifact emitted during a session so they can be listed via resources/list and retrieved via resources/read.

class ralph.mcp.multimodal.resources.ManifestEntry(artifact_id, uri, mime_type, title, modality, identity_key='', cache_path='', source_path='', source_uri='', _raw_bytes=None, _byte_loader=None)[source]

Bases: object

An entry in the session-scoped multimodal manifest.

Parameters:
  • artifact_id (str)

  • uri (str)

  • mime_type (str)

  • title (str)

  • modality (str)

  • identity_key (str)

  • cache_path (str)

  • source_path (str)

  • source_uri (str)

  • _raw_bytes (bytes | None)

  • _byte_loader (Callable[[], bytes | None] | None)

load_bytes()[source]

Return artifact bytes from memory or a backing replay source.

Return type:

bytes | None

property raw_bytes: bytes

Return artifact bytes, rehydrating from the loader when needed.

resource_list_entry()[source]

Return the entry shape for a resources/list response.

Return type:

dict[str, object]

set_replay_source(*, cache_path='', source_path='', source_uri='', byte_loader=None, retain_raw_bytes=False)[source]

Attach a durable replay source and optionally release in-memory bytes.

Parameters:
  • cache_path (str)

  • source_path (str)

  • source_uri (str)

  • byte_loader (Callable[[], bytes | None] | None)

  • retain_raw_bytes (bool)

Return type:

None

class ralph.mcp.multimodal.resources.MediaEntryExtras(cache_path='', source_path='', source_uri='', identity_key='', byte_loader=None, artifact_id='')[source]

Bases: object

Optional extras when adding a media artifact to the manifest.

Parameters:
  • cache_path (str)

  • source_path (str)

  • source_uri (str)

  • identity_key (str)

  • byte_loader (ByteLoader | None)

  • artifact_id (str)

class ralph.mcp.multimodal.resources.MediaManifest(_entries=<factory>, _identity_index=<factory>, max_entries=256)[source]

Bases: object

Session-scoped manifest of all multimodal resource references.

Parameters:
  • _entries (OrderedDict[str, ManifestEntry])

  • _identity_index (dict[str, str])

  • max_entries (int)

add(*, title, mime_type, modality, raw_bytes, extras=None)[source]

Add or replace an artifact and return its manifest entry.

Re-adding an EXISTING identity dedups in place and PRESERVES the original insertion order so the resources/list output order stays stable across duplicate adds (wt-024 analysis feedback: reordering on re-add was an externally visible behavior change that the memory cap did not require).

Parameters:
  • title (str)

  • mime_type (str)

  • modality (str)

  • raw_bytes (bytes)

  • extras (MediaEntryExtras | None)

Return type:

ManifestEntry

get(artifact_id)[source]

Retrieve a manifest entry by artifact_id, or None if not found.

Parameters:

artifact_id (str)

Return type:

ManifestEntry | None

is_empty()[source]

Return True if no artifacts have been stored.

Return type:

bool

list_entries()[source]

Return all manifest entries in insertion order.

Return type:

list[ManifestEntry]

class ralph.mcp.multimodal.resources.MediaSource(source_path='', source_uri='', raw_bytes=None)[source]

Bases: object

Source data for a media artifact (at most one field is set).

Parameters:
  • source_path (str)

  • source_uri (str)

  • raw_bytes (bytes | None)

ralph.mcp.multimodal.resources.build_media_identity(*, modality, mime_type, title, source=None)[source]

Build a stable identity for deduping repeated live artifacts.

Parameters:
  • modality (str)

  • mime_type (str)

  • title (str)

  • source (MediaSource | None)

Return type:

str

ralph.mcp.multimodal.resources.build_media_uri(artifact_id)[source]

Build a ralph://media/{artifact_id} URI.

Parameters:

artifact_id (str)

Return type:

str

ralph.mcp.multimodal.resources.new_artifact_id()[source]

Generate a new random artifact UUID.

Return type:

str

ralph.mcp.multimodal.resources.parse_media_uri(uri)[source]

Parse a ralph://media/{artifact_id} URI and return artifact_id, or None.

Parameters:

uri (str)

Return type:

str | None

ralph.mcp.effective_session_mcp_plan

Canonical effective MCP inventory for one agent session.

class ralph.mcp.effective_session_mcp_plan.EffectiveSessionMcpPlan(custom_servers, agent_upstream_servers, effective_servers, provider_visible_server_names=('ralph',))[source]

Bases: object

Canonical effective MCP inventory for one agent session.

Parameters:

ralph.mcp.session_plan

Central runtime planner for per-session MCP availability.

This module is the single runtime source of truth for what MCP capabilities a new agent session should receive and what upstream MCP environment must be injected into the Ralph MCP subprocess for that session.

class ralph.mcp.session_plan.EffectiveSessionMcpPlan(custom_servers, agent_upstream_servers, effective_servers, provider_visible_server_names=('ralph',))[source]

Bases: object

Canonical effective MCP inventory for one agent session.

Parameters:
class ralph.mcp.session_plan.SessionMcpPlan(capabilities, server_env=None, model_identity=MultimodalModelIdentity(provider='unknown', model_id=None, transport=None), capability_profile=None)[source]

Bases: object

Resolved MCP plan capturing capability grants and server environment for a session.

Parameters:
class ralph.mcp.session_plan.SessionModelOpts(model_identity=None, model_flag=None)[source]

Bases: object

Optional model resolution parameters for build_session_mcp_plan.

Parameters:
ralph.mcp.session_plan.build_session_mcp_plan(*, transport, drain, workspace_path, agents_policy=None, model_opts=None, model_flag=None)[source]

Build the runtime MCP plan for a new agent session.

The result captures both session capability grants and any upstream MCP environment that must be present in the Ralph MCP subprocess so its runtime tool registry matches what the agent is expected to see.

Identity resolution precedence: 1. model_identity (explicit, if provided) 2. model_flag resolved via resolve_model_identity(transport, model_flag) 3. UNKNOWN_IDENTITY fallback

Parameters:
Return type:

SessionMcpPlan

ralph.mcp.session_plan.default_prompt_capability_identifiers(drain)[source]

Return prompt-visible default capabilities from the runtime plan rules.

Parameters:

drain (SessionDrain)

Return type:

frozenset[str]

ralph.mcp.session_plan.effective_session_mcp_plan_from_servers(custom_servers, *, agent_upstream_servers=(), provider_visible_server_names=('ralph',))[source]

Build the canonical effective session MCP inventory from preloaded servers.

Parameters:
Return type:

EffectiveSessionMcpPlan

ralph.mcp.session_plan.resolve_effective_session_mcp_plan(workspace_path, *, agent_upstream_servers=(), provider_visible_server_names=('ralph',))[source]

Return the canonical effective MCP inventory for a session.

provider_visible_server_names captures the direct provider-visible MCP entries (typically just ralph), while effective_servers captures the merged custom + agent-native upstream server set that Ralph will proxy.

Parameters:
  • workspace_path (Path | None)

  • agent_upstream_servers (tuple[UpstreamMcpServer, ...])

  • provider_visible_server_names (tuple[str, ...])

Return type:

EffectiveSessionMcpPlan

ralph.mcp.session_plan.resolve_model_identity(transport, model_flag=None)[source]

Resolve multimodal model identity from agent transport and model flag.

Returns UNKNOWN_IDENTITY when the provider cannot be determined. For OpenCode transport, attempts a catalog lookup to determine the provider. On catalog failure or unmapped model, returns an unknown-provider identity that still carries model_id and transport so delivery falls back safely.

Parameters:
Return type:

MultimodalModelIdentity

ralph.mcp.tools

MCP tool implementations Ralph exposes to agents.

This sub-package contains the handlers for every tool the Ralph MCP server advertises to connected AI agents (read_file, write_file, git_status, exec, submit_artifact, etc.). Ralph is the MCP server here.

ralph.mcp.tools.artifact

MCP artifact submission handlers.

The artifact surface is the single canonical entry point for agent artifacts (plan, development_result, review, fix_result, commit_message, smoke_test_result, typed artifacts). Every public handler routes through ralph.mcp.artifacts.canonical_submit and the supporting persistence / history modules, so the audit_artifact_submission_canonical_path audit can prove the single-writer contract.

Exported surface:

  • handle_submit_artifact — the public handler for fully-formed artifacts. Resolves the artifact directory (per-worker for parallel workers, shared .agent/artifacts/ otherwise), loads the policy bundle to compute the history_enabled flag, and delegates to submit_artifact_canonical. Capability: artifact.submit.

  • handle_submit_plan_section / handle_submit_plan_sections — public handlers for the incremental plan draft surface. They mutate the staged plan draft, validate the section shape against the per-section Pydantic model, and merge entries in a single batch path. Capability: artifact.plan_write.

  • handle_finalize_plan — the public handler that locks a finalized plan, writes the history snapshot, and emits the canonical artifact file. Capability: artifact.plan_write.

  • handle_get_plan_draft / handle_validate_plan_draft / handle_discard_plan_draft — public handlers for the resume-after-restart surface. get returns the current draft (or the finalized response when the draft is older than the finalized record). validate is dry-run: it returns the per-section validation result without writing. discard removes the staged draft. Capability: plan_draft.read / plan_draft.read.

  • ArtifactHandlerDeps — the dependency-injection bundle (custom FileBackend, now_iso callable, history_enabled flag) threaded through every handler. DEFAULT_ARTIFACT_HANDLER_DEPS is the production default.

  • _resolve_artifact_dir / _resolve_history_enabled — helpers that resolve the per-session / per-drain artifact path and the policy-declared history flag.

Trust boundary: every public handler is gated on a McpCapability declared by the agent session. The plan-draft write capability (artifact.plan_write) is intentionally narrower than the broader artifact.submit capability so that only the planning drain can stage or finalize plans; the broad capability is required for the canonical-submit path used by every other artifact type.

Side effects: writes the canonical artifact file under the resolved artifact_dir (per-worker or shared), writes the completion receipt, updates the artifact history index when history_enabled is true, and (for the plan-draft handlers) mutates the staged plan draft file. The submit / finalize / draft-discard paths are all mediated by submit_artifact_canonical / delete_plan_draft / finalize_plan_draft so the audit can prove the single-writer contract. No subprocess is spawned, no network call is made.

class ralph.mcp.tools.artifact.ArtifactHandlerDeps(backend=<ralph.mcp.artifacts._path_file_backend.PathFileBackend object>, now_iso=<function _noop_now_iso>, history_enabled=False, receipt_secret=None)[source]

Bases: object

Injectable dependencies for artifact handler operations.

Parameters:
  • backend (FileBackend)

  • now_iso (Callable[[], str])

  • history_enabled (bool)

  • receipt_secret (str | None)

class ralph.mcp.tools.artifact.SubmitOp(run, undo)[source]

Bases: object

An ordered submit step paired with its rollback action.

Parameters:
  • run (Callable[[], object])

  • undo (Callable[[], None])

ralph.mcp.tools.artifact.execute_ops_with_rollback(ops)[source]

Execute a sequence of ops; on failure roll back all completed ops in reverse.

Parameters:

ops (list[SubmitOp])

Return type:

None

ralph.mcp.tools.artifact.handle_discard_plan_draft(session, workspace, params, *, deps=None)[source]

Delete the on-disk plan draft so the agent can start over.

Parameters:
  • session (CoordinationSessionLike) – Agent session carrying the capability set, run id, and optional worker_artifact_dir override for parallel workers.

  • workspace (WorkspaceLike) – Workspace surface that resolves the artifact root.

  • params (dict[str, object]) – Reserved for future filters. Currently ignored.

  • deps (ArtifactHandlerDeps | None) – Optional dependency-injection bundle. When None, DEFAULT_ARTIFACT_HANDLER_DEPS is used.

Returns:

A ToolResult with "Plan draft discarded." when a draft existed and was deleted, or "No plan draft to discard." when there was no draft on disk.

Raises:

CapabilityDeniedError – When the session does not declare plan_draft.write.

Return type:

ToolResult

Side effects:

Resolves the artifact directory (per-worker for parallel workers, the shared .agent/artifacts/ otherwise) and calls delete_plan_draft to remove the staged plan draft file when present. The handler does NOT touch the finalized plan artifact (that is the write path’s concern) and does NOT mutate the history index. No subprocess is spawned, no network call is made.

ralph.mcp.tools.artifact.handle_finalize_plan(session, workspace, params, *, deps=None)[source]

Validate the staged draft as a whole plan and write plan.json.

The handler validates every staged section of the plan draft as a single artifact, writes the canonical plan.json under the resolved artifact_dir, takes a history snapshot when history_enabled is true, and returns the canonical finalize response that downstream phases consume.

Parameters:
  • session (CoordinationSessionLike) – Agent session carrying the capability set, run id, and optional worker_artifact_dir override for parallel workers.

  • workspace (WorkspaceLike) – Workspace surface that resolves the artifact root.

  • params (dict[str, object]) – Optional overrides accepted by the canonical finalize path (e.g. timestamp). Unknown keys are ignored.

  • deps (ArtifactHandlerDeps | None) – Optional dependency-injection bundle. When None, DEFAULT_ARTIFACT_HANDLER_DEPS is used.

Returns:

A ToolResult with the canonical finalize response JSON, including the validated plan content, the final updated_at timestamp, and the source discriminator ("finalized").

Raises:
  • CapabilityDeniedError – When the session does not declare artifact.plan_write.

  • PlanArtifactValidationError – When the assembled draft fails the whole-plan schema check (e.g. missing required sections, step number collision, cross-section reference violation).

Return type:

ToolResult

Side effects:

Resolves the artifact directory (per-worker for parallel workers, the shared .agent/artifacts/ otherwise) and delegates to submit_artifact_canonical, which writes the canonical plan.json artifact file, the completion receipt, and (when the active policy declares artifact_history.enabled = true) the history snapshot under the resolved artifact_dir. The structured JSON artifact is mirrored into a Markdown handoff via sync_markdown_handoff so downstream phases never need to read raw JSON directly. No subprocess is spawned, no network call is made.

ralph.mcp.tools.artifact.handle_get_plan_draft(session, workspace, params, *, deps=None)[source]

Return the current plan draft so an agent can resume after a restart.

The handler is the resume-after-restart entry point. It returns the current plan draft (or the finalized response when the draft is older than the finalized record) so an agent that resumes after a crash or restart can pick up where it left off. The params argument is reserved for future filters and is currently ignored.

Parameters:
  • session (CoordinationSessionLike) – Agent session carrying the capability set, run id, and optional worker_artifact_dir override for parallel workers.

  • workspace (WorkspaceLike) – Workspace surface that resolves the artifact root.

  • params (dict[str, object]) – Reserved for future filters. Currently ignored.

  • deps (ArtifactHandlerDeps | None) – Optional dependency-injection bundle. When None, DEFAULT_ARTIFACT_HANDLER_DEPS is used.

Returns:

A ToolResult with the plan-draft response JSON (staged_sections list, started_at / updated_at timestamps, draft sections dict, and source discriminator — "draft" or "finalized").

Raises:

CapabilityDeniedError – When the session does not declare plan_draft.read.

Return type:

ToolResult

Side effects:

None. Read-only: resolves the artifact directory (per-worker or shared), loads the staged draft via load_plan_draft and the finalized response via load_plan_artifact_sections, and returns whichever is newer. Does NOT write .agent/artifacts/plan.json and does NOT delete the in-progress draft. No subprocess is spawned, no network call is made.

ralph.mcp.tools.artifact.handle_submit_artifact(session, workspace, params, *, deps=None)[source]

Validate and persist an artifact submitted by an MCP agent.

The handler is the public MCP entry point for any fully-formed artifact (development_result, review / issues, fix_result, commit_message, smoke_test_result, typed artifacts). It resolves the artifact directory (per-worker for parallel workers, the shared .agent/artifacts/ otherwise), loads the policy bundle to compute the history_enabled flag, and delegates the persistence side effects to submit_artifact_canonical so the audit_artifact_submission_canonical_path audit can prove the single-writer contract.

Parameters:
  • session (CoordinationSessionLike) – Agent session carrying the capability set, run id, and optional worker_artifact_dir override for parallel workers.

  • workspace (WorkspaceLike) – Workspace surface that resolves the artifact root.

  • params (dict[str, object]) – artifact_type (string) and content (object or raw JSON text) per the artifact contract. The handler also reads the optional draft / format keys used by the canonical submission path.

  • deps (ArtifactHandlerDeps | None) – Optional dependency-injection bundle (custom FileBackend, now_iso callable, history_enabled override). When None, DEFAULT_ARTIFACT_HANDLER_DEPS is used.

Returns:

Artifact submitted: <type>). The canonical artifact file, completion receipt, and optional history snapshot are written as side effects.

Return type:

A success ToolResult (text

Raises:
  • CapabilityDeniedError – When the session does not declare artifact.submit. The handler enforces default-deny.

  • Pydantic ValidationError (wrapped in the artifact's typedValidationError subclass — for example, PlanArtifactValidationError) when the payload fails the per-type schema check. The canonical submit path converts these into actionable per-field error messages.

Side effects:

Resolves the artifact directory (per-worker for parallel workers, the shared .agent/artifacts/ otherwise) and delegates to submit_artifact_canonical, which writes the canonical artifact file, the completion receipt, and (when the active policy declares artifact_history.enabled = true) the history snapshot under the resolved artifact_dir. No subprocess is spawned, no network call is made.

ralph.mcp.tools.artifact.handle_submit_plan_section(session, workspace, params, *, deps=None)[source]

Stage a single plan section and report validation warnings.

The handler is the public MCP entry point for the incremental plan-draft surface. It accepts a single section name (one of PLAN_SECTION_NAMES), a mode (replace | append | merge), and a JSON-serializable payload, validates the payload against the per-section Pydantic model, and persists the merged draft under the resolved artifact_dir.

Parameters:
  • session (CoordinationSessionLike) – Agent session carrying the capability set, run id, and optional worker_artifact_dir override for parallel workers.

  • workspace (WorkspaceLike) – Workspace surface that resolves the artifact root.

  • params (dict[str, object]) – section (string), mode (string, default "replace"), and payload (JSON-serializable object or raw JSON text).

  • deps (ArtifactHandlerDeps | None) – Optional dependency-injection bundle. When None, DEFAULT_ARTIFACT_HANDLER_DEPS is used.

Returns:

A ToolResult with the staged-section text and any non-fatal validation warnings surfaced inline.

Raises:
  • CapabilityDeniedError – When the session does not declare plan_draft.write.

  • InvalidParamsError – When section is missing, unknown, or the payload fails the per-section schema check. The handler formats the error so the agent sees a clear [section] marker and the underlying PlanArtifactValidationError detail.

Return type:

ToolResult

Side effects:

Resolves the artifact directory (per-worker for parallel workers, the shared .agent/artifacts/ otherwise) and persists the merged draft under the resolved artifact_dir via save_plan_draft. Schema-invalid but shape-valid sections are staged with validation_warnings and do NOT block persistence — the strict gates are ralph_validate_draft and ralph_finalize_plan. No subprocess is spawned, no network call is made.

ralph.mcp.tools.artifact.handle_submit_plan_sections(session, workspace, params, *, deps=None)[source]

Stage a batch of plan sections in a single round-trip.

Parses EVERY entry before any merge; if any entry is structurally malformed, the entire batch is rejected and the on-disk draft is unchanged. Schema-invalid but valid JSON sections are staged with validation_warnings so validate/finalize can be the strict gates.

Parameters:
  • session (CoordinationSessionLike) – Agent session carrying the capability set, run id, and optional worker_artifact_dir override for parallel workers.

  • workspace (WorkspaceLike) – Workspace surface that resolves the artifact root.

  • params (dict[str, object]) – entries (list of {section, mode, content} dicts; each entry may inline the content or carry it as a JSON-encoded string). The handler also accepts a single-JSON-string container or a pre-coerced list. Unknown top-level keys are ignored.

  • deps (ArtifactHandlerDeps | None) – Optional dependency-injection bundle. When None, DEFAULT_ARTIFACT_HANDLER_DEPS is used.

Returns:

A ToolResult with the canonical batch response JSON on success: {"submitted": [...], "staged_sections": [...], "total_bytes": <int>, "validation_warnings": [...]} with is_error=False. On a malformed envelope (entries missing or wrong type) or a single structurally malformed entry: {"submitted": [], "failed_at": <int>, "error": <message>} with is_error=True (the entire batch is rejected and the on-disk draft is unchanged).

Raises:
  • CapabilityDeniedError – When the session does not declare plan_draft.write.

  • InvalidParamsError – When entries is missing, is the wrong container type, or cannot be coerced from the supplied JSON text. The handler formats the error so the agent sees a clear entries envelope marker.

Return type:

ToolResult

Side effects:

Resolves the artifact directory (per-worker for parallel workers, the shared .agent/artifacts/ otherwise) and persists the merged draft under the resolved artifact_dir via save_plan_draft. Schema-invalid but shape-valid sections are staged with validation_warnings and do NOT block persistence — the strict gates are ralph_validate_draft and ralph_finalize_plan. A structurally malformed entry short-circuits before any merge, so the on-disk draft is unchanged when the batch is rejected. No subprocess is spawned, no network call is made.

ralph.mcp.tools.artifact.handle_validate_plan_draft(session, workspace, params, *, deps=None)[source]

Run the full PlanArtifact cross-section validator on the staged draft.

Read-only: does NOT write .agent/artifacts/plan.json and does NOT delete the in-progress draft. The same checks run at finalize_plan in the write path; this tool exposes them in a read-only path so the agent can dry-run validation before committing.

Parameters:
  • session (CoordinationSessionLike) – Agent session carrying the capability set, run id, and optional worker_artifact_dir override for parallel workers.

  • workspace (WorkspaceLike) – Workspace surface that resolves the artifact root.

  • params (dict[str, object]) – Reserved for future filters. Currently ignored.

  • deps (ArtifactHandlerDeps | None) – Optional dependency-injection bundle. When None, DEFAULT_ARTIFACT_HANDLER_DEPS is used.

Returns:

A ToolResult with the canonical validation response JSON. On success: {"valid": true, "errors": [], "staged_sections": [...]} with is_error=False. On a missing draft: {"valid": false, "errors": [{"message": ..., "type": "InvalidDraftState"}], "staged_sections": []} with is_error=False (the missing-draft case is reported in-body, not as an MCP error). On a schema failure: {"valid": false, "errors": [{"message": ..., "type": "PlanArtifactValidationError"}]} with is_error=False.

Raises:

CapabilityDeniedError – When the session does not declare plan_draft.read.

Return type:

ToolResult

Side effects:

None. Read-only: resolves the artifact directory (per-worker or shared) and runs finalize_plan_draft(draft) for its PlanArtifactValidationError side effect — any per-section PlanArtifactValidationError is converted into the {"valid": false, "errors": [...]} JSON response and the in-progress draft is left untouched. Does NOT write .agent/artifacts/plan.json and does NOT delete the draft. No subprocess is spawned, no network call is made.

ralph.mcp.tools.artifact.prepare_artifact_submission(params, *, session_drain=None, base_path=None, backend=<ralph.mcp.artifacts._path_file_backend.PathFileBackend object>)[source]

Validate and canonicalize artifact submission params, returning (artifact_type, params).

Parameters:
  • params (dict[str, object])

  • session_drain (str | None)

  • base_path (Path | None)

  • backend (FileBackend)

Return type:

tuple[str, dict[str, object]]

ralph.mcp.tools.artifact.submit_ops_for_artifact(artifact_type, workspace_root, artifact_dir, parsed_content, *, deps, run_id=None)[source]

Return the ordered (op, undo) pairs for a complete artifact submit.

When run_id is provided, the final op stamps a run-scoped completion receipt for artifact_type. Because it is the last op in the rollback-protected sequence, the receipt exists only when the artifact and its handoff were fully persisted — binding “submitted” and “gate-visible” into one atomic fact regardless of where the artifact bytes landed.

Parameters:
  • artifact_type (str)

  • workspace_root (Path)

  • artifact_dir (Path)

  • parsed_content (dict[str, object])

  • deps (ArtifactHandlerDeps)

  • run_id (str | None)

Return type:

list[SubmitOp]

ralph.mcp.tools.plan_draft_edit

Precise MCP handlers for plan draft step edits.

ralph.mcp.tools.plan_draft_edit.handle_insert_plan_step(session, workspace, params, *, deps=None)[source]

Insert a single plan step and reindex the draft deterministically.

Auto-reindexes the remaining steps, rewrites every depends_on array in the surviving steps to use the new step numbers, and rewrites every AC.satisfied_by_steps reference in the design sub-section to use the new step numbers; the provided step.number is ignored.

Returns a JSON echo payload with the new step number, the reindex map, the list of step numbers whose depends_on was rewritten, the list of AC ids whose satisfied_by_steps was rewritten, the list of AC ids whose satisfied_by_steps entries were dropped, and the new total step count.

Parameters:
Return type:

ToolResult

ralph.mcp.tools.plan_draft_edit.handle_move_plan_step(session, workspace, params, *, deps=None)[source]

Move a single plan step to a new index and reindex the draft deterministically.

Auto-reindexes the surviving steps, rewrites every depends_on array in the surviving steps to use the new step numbers, and rewrites every AC.satisfied_by_steps reference in the design sub-section to use the new step numbers; the provided step.number is ignored.

Returns a JSON echo payload with the source and target step numbers (typically identical since move preserves step numbers), the reindex map (typically a no-op), the list of step numbers whose depends_on was rewritten, the list of AC ids whose satisfied_by_steps was rewritten, the list of AC ids whose satisfied_by_steps entries were dropped, and the new total step count.

Parameters:
Return type:

ToolResult

ralph.mcp.tools.plan_draft_edit.handle_patch_step(session, workspace, params, *, deps=None)[source]

Partial-update a single plan step (shallow-merge).

Pass step_number and a step dict with ANY SUBSET of step fields; the missing fields are preserved from the existing step. The provided step.number is ignored (replace_plan_step forces the number to step_number). The step-mutation auto-reindex of depends_on and AC.satisfied_by_steps runs as for ralph_replace_plan_step. Returns the same echo payload shape as handle_replace_plan_step.

Parameters:
Return type:

ToolResult

ralph.mcp.tools.plan_draft_edit.handle_remove_plan_step(session, workspace, params, *, deps=None)[source]

Remove a single plan step and reindex the draft deterministically.

Auto-reindexes the remaining steps, rewrites every depends_on array in the surviving steps to use the new step numbers, and rewrites every AC.satisfied_by_steps reference in the design sub-section to use the new step numbers; the provided step.number is ignored.

References to the removed step are preserved as unresolved staged JSON and reported in validation_warnings so ralph_validate_draft / ralph_finalize_plan can reject the final plan without losing data. Returns a JSON echo payload with the removed step number, the reindex map, rewritten reference summaries, validation_warnings, and the new total step count.

Parameters:
Return type:

ToolResult

ralph.mcp.tools.plan_draft_edit.handle_replace_plan_step(session, workspace, params, *, deps=None)[source]

Replace a single plan step and reindex the draft deterministically.

Auto-reindexes the remaining steps, rewrites every depends_on array in the surviving steps to use the new step numbers, and rewrites every AC.satisfied_by_steps reference in the design sub-section to use the new step numbers; the provided step.number is ignored.

Returns a JSON echo payload with the (unchanged) step number, the reindex map (typically a no-op), the list of step numbers whose depends_on was rewritten, the list of AC ids whose satisfied_by_steps was rewritten, the list of AC ids whose satisfied_by_steps entries were dropped, and the new total step count.

Parameters:
Return type:

ToolResult

ralph.mcp.tools.bridge

MCP tool registry and handler dispatch.

This module ports the Rust mcp_server::tool_bridge registry layer into Python. It owns tool metadata registration, duplicate protection, lookup, and dispatch. The default registry builder mirrors the Rust bridge by registering lazy handler wrappers for the Ralph MCP tool modules.

class ralph.mcp.tools.bridge.LazyToolHandler(*, module_name, handler_name, session, workspace, extra_kwargs=None)[source]

Bases: object

Lazy wrapper that imports the real MCP tool handler on demand.

Parameters:
  • module_name (str)

  • handler_name (str)

  • session (object)

  • workspace (object)

  • extra_kwargs (dict[str, object] | None)

class ralph.mcp.tools.bridge.RegisteredTool(metadata, handler)[source]

Bases: object

A registered tool and its executable handler.

Parameters:
class ralph.mcp.tools.bridge.RegistrationHandler(*args, **kwargs)[source]

Bases: Protocol

Callable protocol for MCP tool handler functions registered in the tool bridge.

class ralph.mcp.tools.bridge.ToolBridge(session=None)[source]

Bases: object

Registry for MCP tools and dispatcher for tool invocations.

Parameters:

session (object | None)

dispatch(name, params=None, *, host_session=None, workspace=None)[source]

Dispatch a tool invocation to its registered handler.

Parameters:
  • name (str)

  • params (JsonObject | None)

  • host_session (object | None)

  • workspace (object | None)

Return type:

object

get(name)[source]

Return a registered tool or raise if it does not exist.

Parameters:

name (str)

Return type:

RegisteredTool

has_tool(name)[source]

Return whether a tool is registered.

Parameters:

name (str)

Return type:

bool

list_definitions()[source]

Return public tool definitions in registration order.

Return type:

list[ToolDefinition]

list_metadata()[source]

Return tool metadata in registration order.

Return type:

list[ToolMetadata]

register(metadata, handler)[source]

Register a tool definition and handler.

Parameters:
Return type:

None

register_spec(spec, *, session, workspace)[source]

Register a tool from a complete lazy-loading spec.

Parameters:
  • spec (ToolSpec)

  • session (object)

  • workspace (object)

Return type:

None

set_client_capabilities(capabilities)[source]

Set the client declared capabilities from MCP initialize handshake.

Parameters:

capabilities (set[str] | None)

Return type:

None

exception ralph.mcp.tools.bridge.ToolBridgeError[source]

Bases: Exception

Base exception for tool bridge failures.

class ralph.mcp.tools.bridge.ToolDefinition(name, description, input_schema)[source]

Bases: object

Immutable description of one tool that the MCP bridge exposes to agents.

A ToolDefinition is the wire-format description a Ralph MCP bridge returns for every registered tool when an agent asks the server for its capabilities (the MCP tools/list response). It is also the shape Ralph uses internally when it logs or diffs registered tools, so the fields here match the public MCP contract rather than the bridge’s internal handler-bound representation.

The dataclass is frozen to guarantee that a tool’s advertised surface cannot drift after the bridge registers it; mutating an instance raises dataclasses.FrozenInstanceError. New variants must be created via dataclasses.replace() instead.

Parameters:
  • name (str)

  • description (str)

  • input_schema (JsonObject)

name

Stable, dot-free tool name surfaced to agents (e.g. "read_file", "submit_artifact"). The name is the tool field an agent sends back when invoking the tool, so it must be unique within a single bridge and must remain stable across releases (rename = breaking change).

Type:

str

description

Human-readable one-paragraph description of what the tool does, shown to agents when they enumerate the server’s capabilities. Plain text; embedded Markdown is rendered literally. Author for clarity rather than brevity.

Type:

str

input_schema

JSON Schema (draft 2020-12) describing the tool’s accepted arguments. Must round-trip through ralph.mcp.tools.bridge._types.JsonObject() and validate the bridge’s pre-dispatch payload. Schemas with additionalProperties: false and explicit required arrays are preferred because they make agent-prompted errors unambiguous.

Type:

JsonObject

Invariants:
  • name is treated as part of the public API; renaming breaks any agent prompt that calls the tool by name.

  • input_schema is consumed verbatim by agents and by the bridge’s argument validator, so changes to it must keep the existing accepted shape backward-compatible or be paired with a deprecation note.

exception ralph.mcp.tools.bridge.ToolDispatchError[source]

Bases: ToolBridgeError

Raised when tool dispatch fails.

class ralph.mcp.tools.bridge.ToolMetadata(definition, required_capability, is_mutating=None, is_multimodal=False)[source]

Bases: object

Ralph-side metadata paired with a ToolDefinition for registration.

Where ToolDefinition is the public agent-facing shape, a ToolMetadata carries the Ralph-only annotations the bridge and authorization layer need to use the tool safely:

  • the capability an agent must hold before the dispatcher will forward a call,

  • whether the tool mutates workspace state (driving access-mode enforcement), and

  • whether the tool accepts multimodal (image or audio) payloads.

The dataclass is frozen and stored in the bridge’s tool registry; every invocation consults required_capability against the agent’s declared capabilities to decide whether to accept or reject the call.

Parameters:
  • definition (ToolDefinition)

  • required_capability (str)

  • is_mutating (bool | None)

  • is_multimodal (bool)

definition

The agent-facing ToolDefinition advertised by tools/list. Carries the public name, description, and JSON schema; ToolMetadata adds the Ralph-side annotations.

Type:

ToolDefinition

required_capability

Name of the capability a calling agent must hold (e.g. "write_files", "submit_artifact"). The bridge uses this to gate calls: an agent without the capability sees PermissionDenied instead of a dispatch. The default-deny invariant in ralph.mcp documents the policy that every Ralph-managed tool must declare one.

Type:

str

is_mutating

True when the tool can change workspace or artifact-store state, False for strictly read-only tools. None is treated as conservative (True) so an unknown tool is assumed to mutate until the registration proves otherwise. The bridge uses this flag together with ralph.mcp.protocol.startup.access_mode_for_drain() to decide whether a read-only session can still call the tool.

Type:

bool | None

is_multimodal

True when the tool accepts binary content (images, audio) alongside its text schema. The bridge surfaces multimodal tools through a different callTool content shape and tags them so agents can discover them via the metadata flag.

Type:

bool

Invariants:
  • required_capability must be a non-empty capability name recognized by ralph.mcp.capabilities; unknown capabilities cause the dispatcher to reject every call.

  • The combination of is_mutating=False and required_capability granting write access is allowed but downgrades the runtime access mode on a read-only session.

  • ToolMetadata is constructed once, at registry build time, and is then immutable for the lifetime of the bridge.

exception ralph.mcp.tools.bridge.ToolRegistrationError[source]

Bases: ToolBridgeError

Raised when tool registration is invalid.

class ralph.mcp.tools.bridge.ToolSpec(metadata, module_name, handler_name)[source]

Bases: object

Full registration spec, including lazy import target.

Parameters:
  • metadata (ToolMetadata)

  • module_name (str)

  • handler_name (str)

class ralph.mcp.tools.bridge.UpstreamProxyHandler(alias, upstream_registry)[source]

Bases: object

Proxy handler that forwards tool calls to an upstream MCP registry.

Parameters:
ralph.mcp.tools.bridge.build_ralph_tool_registry(session, workspace, *, upstream_registry=None, mcp_config=None)[source]

Build the default Ralph MCP tool registry.

Parameters:
Return type:

ToolBridge

ralph.mcp.tools.bridge.tool_specs(mcp_config)[source]

Build the full ordered list of tool specifications for the MCP bridge.

Parameters:

mcp_config (McpConfig)

Return type:

tuple[ToolSpec, …]

ralph.mcp.tools.coordination

MCP tool call coordination handlers.

Ports the Rust coordination handlers that support progress reporting, completion declaration, workspace coordination, and environment reads.

Exported surface:

  • handle_report_progress — emits a run.report_progress event to the pipeline. Used by the agent to publish heartbeat / status updates while it is still working. The text response is suffixed with PROGRESS_PIPELINE_MARKER so the idle watchdog can key on it.

  • handle_declare_complete — finalizes the run with an artifact.submit-gated completion sentinel. Writes .agent/completion_seen_<run_id>.json (HMAC-signed when the broker secret is provided) so the failure classifier / recovery controller can verify the completion signal even if the MCP JSON-RPC envelope is lost.

  • handle_coordinateartifact.plan_write-gated workspace coordination: planning-drain agents publish actions / work-unit payloads that the parent process observes. The text response is suffixed with [Coordination event emitted to pipeline].

  • handle_read_envenv.read-gated environment variable read. Returns <name>=<value> or <name>=[not found]. The agent diagnostic / orchestrator uses this to inspect the run environment.

  • require_capability — the canonical capability check used by every public handler in this module (and re-exported for exec.py, git_read.py, websearch.py, and webvisit.py).

  • format_progress_text / format_coordination_text / _write_completion_sentinel — formatting and persistence helpers.

Trust boundary: every public handler is gated on a McpCapability declared by the agent session. The four capability strings (RUN_REPORT_PROGRESS_CAPABILITY, ARTIFACT_SUBMIT_CAPABILITY, ARTIFACT_PLAN_WRITE_CAPABILITY, ENV_READ_CAPABILITY) are the contract between the agent’s session declaration and the handler-side default-deny check.

Side effects: handle_declare_complete writes a completion sentinel to .agent/; the other handlers are pure with respect to the workspace and only emit pipeline events. No subprocess is spawned, no network call is made.

exception ralph.mcp.tools.coordination.CapabilityDeniedError[source]

Bases: ToolError

Raised when a required session capability is not available.

exception ralph.mcp.tools.coordination.CompletionSentinelPersistenceError[source]

Bases: RuntimeError

Raised when handle_declare_complete cannot persist a durable sentinel.

The completion gate reads .agent/completion_seen_<run_id>.json (or the DB-backed equivalent) to verify that the run actually finished; if neither the RunStateDB row nor the legacy sentinel file is written, the agent may falsely claim “done” against a sentinel the completion gate cannot see. This exception is the fail-closed signal that handle_declare_complete converts into a ToolResult(is_error=True).

class ralph.mcp.tools.coordination.CoordinationSessionLike(*args, **kwargs)[source]

Bases: Protocol

Minimum session surface required by coordination handlers.

Sessions may optionally carry a broker_secret (RFC-013 P3) — a broker-owned string the agent never sees directly, used to HMAC the run-scoped receipt and completion sentinel so a model with workspace write capabilities cannot forge either. None means no HMAC enforcement at write or read time (the pre-P3 contract); the handlers downcast gracefully when the attribute is absent.

property broker_secret: str | None

RFC-013 P3 broker-owned HMAC secret. Read-only at the protocol surface; the implementation decides whether the value is supplied by a constructor arg, a property backed by one, or a dataclass field with a None default.

check_capability(capability)[source]

Return a policy outcome for the requested capability.

Parameters:

capability (str)

Return type:

object

exception ralph.mcp.tools.coordination.InvalidParamsError[source]

Bases: ToolError

Raised when tool parameters are missing or invalid.

ralph.mcp.tools.coordination.PROGRESS_PIPELINE_MARKER = '[Progress event emitted to pipeline]'

Stable machine-readable marker appended to every progress report. The idle watchdog’s activity classifier keys on this to route repeated progress reports into the repeated-error circuit breaker (so a cosmetic “still stuck” heartbeat cannot keep a wedged agent alive forever). Keep it in sync with any consumer.

class ralph.mcp.tools.coordination.ToolContent(type, text)[source]

Bases: object

Single text tool response content block.

Parameters:
  • type (str)

  • text (str)

classmethod json_content(payload)[source]

Create a text content block carrying a JSON-serialized payload.

The payload is json.dumps-serialized with sort_keys=False so dict insertion order is preserved (helps the agent read the response in the same order the handler built it). The text content type is reused so downstream consumers that only know how to parse text fields keep working; agents that need a typed object can json.loads(content[0].text).

Parameters:

payload (Mapping[str, object] | list[object])

Return type:

ToolContent

classmethod text_content(text)[source]

Create a text content block.

Parameters:

text (str)

Return type:

ToolContent

to_dict()[source]

Serialize the content block to a dictionary.

Return type:

dict[str, str]

exception ralph.mcp.tools.coordination.ToolError[source]

Bases: Exception

Base error raised by MCP tool handlers.

class ralph.mcp.tools.coordination.ToolResult(content, is_error=None)[source]

Bases: object

Serializable MCP tool result.

Parameters:
  • content (list[ContentBlock])

  • is_error (bool | None)

to_dict()[source]

Serialize the result to an MCP-compatible dictionary.

Return type:

dict[str, object]

class ralph.mcp.tools.coordination.WorkspaceLike(*args, **kwargs)[source]

Bases: Protocol

Placeholder workspace protocol for handler parity.

absolute_path(path)[source]

Return an absolute workspace path for the provided relative path.

Parameters:

path (str)

Return type:

str

ralph.mcp.tools.coordination.format_coordination_text(action, session_id, timestamp, work_unit_id, payload)[source]

Format the coordination response text.

Parameters:
  • action (str)

  • session_id (str)

  • timestamp (int)

  • work_unit_id (str | None)

  • payload (object | None)

Return type:

str

ralph.mcp.tools.coordination.format_progress_text(status, note, timestamp)[source]

Build the progress report response text.

Parameters:
  • status (str)

  • note (str)

  • timestamp (int)

Return type:

str

ralph.mcp.tools.coordination.handle_coordinate(session, _workspace, params, *, now_fn=<function _timestamp>)[source]

Coordinate parallel worker activities.

Parameters:
  • session (CoordinationSessionLike) – Agent session; must declare artifact.plan_write.

  • _workspace (WorkspaceLike) – Unused; kept for tool-handler signature parity.

  • params (dict[str, object]) – Mapping with required action (string), optional work_unit_id (string) and payload (object).

  • now_fn (Callable[[], int]) – Optional injected wall-clock provider for the timestamp in the formatted text. Defaults to _timestamp.

Returns:

A ToolResult whose text content is the formatted coordination line (suffixed with [Coordination event emitted to pipeline]).

Raises:

CapabilityDeniedError – When the session does not declare artifact.plan_write.

Return type:

ToolResult

Side effects:

Pure with respect to the workspace. Emits a workspace coordination event for the planning drain to observe. No subprocess, no network call.

ralph.mcp.tools.coordination.handle_declare_complete(session, workspace, params, *, now_fn=<function _timestamp>)[source]

Declare that the agent has completed its assigned task.

Parameters:
  • session (CoordinationSessionLike) – Agent session; must declare artifact.submit and carry a valid run_id used to name the sentinel file.

  • workspace (WorkspaceLike) – Workspace surface whose root resolves .agent/completion_seen_<run_id>.json.

  • params (dict[str, object]) – Mapping with optional summary (string, defaults to "No summary provided").

  • now_fn (Callable[[], int]) – Optional injected wall-clock provider for the timestamp in the response. Defaults to _timestamp.

Returns:

A ToolResult whose text content is the completion summary line (suffixed with [Completion event emitted to pipeline]).

Raises:

CapabilityDeniedError – When the session does not declare artifact.submit.

Return type:

ToolResult

Side effects:

Writes .agent/completion_seen_<run_id>.json (HMAC-signed when the broker secret is provided) so the failure classifier / recovery controller can verify the completion signal even if the MCP JSON-RPC envelope is lost. OSError from the sentinel write is suppressed (best-effort) so a transient filesystem issue cannot mask the completion event.

ralph.mcp.tools.coordination.handle_read_env(session, _workspace, params, *, env=environ({'BUNDLER_ORIG_BUNDLER_SETUP': 'BUNDLER_ENVIRONMENT_PRESERVER_INTENTIONALLY_NIL', 'BUNDLER_ORIG_BUNDLER_VERSION': 'BUNDLER_ENVIRONMENT_PRESERVER_INTENTIONALLY_NIL', 'BUNDLER_ORIG_BUNDLE_BIN_PATH': 'BUNDLER_ENVIRONMENT_PRESERVER_INTENTIONALLY_NIL', 'BUNDLER_ORIG_BUNDLE_GEMFILE': '/Volumes/Crucial X9/ext-Projects/Ralph-Site/Gemfile', 'BUNDLER_ORIG_BUNDLE_LOCKFILE': 'BUNDLER_ENVIRONMENT_PRESERVER_INTENTIONALLY_NIL', 'BUNDLER_ORIG_GEM_HOME': 'BUNDLER_ENVIRONMENT_PRESERVER_INTENTIONALLY_NIL', 'BUNDLER_ORIG_GEM_PATH': 'BUNDLER_ENVIRONMENT_PRESERVER_INTENTIONALLY_NIL', 'BUNDLER_ORIG_MANPATH': '/Users/mistlight/.nvm/versions/node/v24.15.0/share/man:/usr/share/man:/usr/local/share/man:/Applications/kitty.app/Contents/Resources/man:', 'BUNDLER_ORIG_PATH': '/Users/mistlight/.rbenv/versions/4.0.1/bin:/opt/homebrew/Cellar/rbenv/1.3.2/libexec:/Users/mistlight/.opencode/bin:/Users/mistlight/.kilo/bin:/Users/mistlight/.local/bin:/Users/mistlight/.pyenv/shims:/Users/mistlight/.sdkman/candidates/sbt/current/bin:/Users/mistlight/.bun/bin:/opt/homebrew/opt/openjdk/bin:/Users/mistlight/.cabal/bin:/Users/mistlight/.ghcup/bin:/Users/mistlight/.local/bin:/Users/mistlight/.nvm/versions/node/v24.15.0/bin:/Users/mistlight/.local/bin:/Users/mistlight/.rbenv/shims:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/pkg/env/global/bin:/Library/Apple/usr/bin:/opt/homebrew/bin:/opt/podman/bin:/Users/mistlight/.cargo/bin:/Applications/kitty.app/Contents/MacOS', 'BUNDLER_ORIG_RB_USER_INSTALL': 'BUNDLER_ENVIRONMENT_PRESERVER_INTENTIONALLY_NIL', 'BUNDLER_ORIG_RUBYLIB': '/opt/homebrew/Cellar/rbenv/1.3.2/rbenv.d/exec/gem-rehash:', 'BUNDLER_ORIG_RUBYOPT': 'BUNDLER_ENVIRONMENT_PRESERVER_INTENTIONALLY_NIL', 'BUNDLER_SETUP': '/Users/mistlight/.rbenv/versions/4.0.1/lib/ruby/site_ruby/4.0.0/bundler/setup', 'BUNDLER_VERSION': '4.0.6', 'BUNDLE_BIN_PATH': '/Users/mistlight/.rbenv/versions/4.0.1/lib/ruby/gems/4.0.0/gems/bundler-4.0.6/exe/bundle', 'BUNDLE_GEMFILE': '/Volumes/Crucial X9/ext-Projects/Ralph-Site/Gemfile', 'BUNDLE_LOCKFILE': '/Volumes/Crucial X9/ext-Projects/Ralph-Site/Gemfile.lock', 'BUN_INSTALL': '/Users/mistlight/.bun', 'COLORTERM': 'truecolor', 'COMMAND_MODE': 'unix2003', 'GEM_HOME': '/Users/mistlight/.rbenv/versions/4.0.1/lib/ruby/gems/4.0.0', 'HOME': '/Users/mistlight', 'JAVA_HOME': '/opt/homebrew/opt/openjdk/libexec/openjdk.jdk/Contents/Home', 'KITTY_INSTALLATION_DIR': '/Applications/kitty.app/Contents/Resources/kitty', 'KITTY_PID': '19330', 'KITTY_PUBLIC_KEY': '1:vLQfC_&{PKGIa!O4<!bQx}P;#u5DH5n6siaKj>t8', 'KITTY_WINDOW_ID': '5', 'LANG': 'en_US.UTF-8', 'LESS': '-R', 'LOGNAME': 'mistlight', 'LSCOLORS': 'Gxfxcxdxbxegedabagacad', 'LS_COLORS': 'di=1;36:ln=35:so=32:pi=33:ex=31:bd=34;46:cd=34;43:su=30;41:sg=30;46:tw=30;42:ow=30;43', 'MANPATH': '/Users/mistlight/.nvm/versions/node/v24.15.0/share/man:/usr/share/man:/usr/local/share/man:/Applications/kitty.app/Contents/Resources/man:', 'NVM_BIN': '/Users/mistlight/.nvm/versions/node/v24.15.0/bin', 'NVM_CD_FLAGS': '-q', 'NVM_DIR': '/Users/mistlight/.nvm', 'NVM_INC': '/Users/mistlight/.nvm/versions/node/v24.15.0/include/node', 'OSLogRateLimit': '64', 'PAGER': 'less', 'PATH': '/Volumes/Crucial X9/ext-Projects/Ralph-Site/tmp/docs_build/ralph-workflow/.venv/bin:/Users/mistlight/.rbenv/versions/4.0.1/lib/ruby/gems/4.0.0/bin:/Users/mistlight/.rbenv/versions/4.0.1/bin:/opt/homebrew/Cellar/rbenv/1.3.2/libexec:/Users/mistlight/.opencode/bin:/Users/mistlight/.kilo/bin:/Users/mistlight/.local/bin:/Users/mistlight/.pyenv/shims:/Users/mistlight/.sdkman/candidates/sbt/current/bin:/Users/mistlight/.bun/bin:/opt/homebrew/opt/openjdk/bin:/Users/mistlight/.cabal/bin:/Users/mistlight/.ghcup/bin:/Users/mistlight/.nvm/versions/node/v24.15.0/bin:/Users/mistlight/.rbenv/shims:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/pkg/env/global/bin:/Library/Apple/usr/bin:/opt/homebrew/bin:/opt/podman/bin:/Users/mistlight/.cargo/bin:/Applications/kitty.app/Contents/MacOS', 'PWD': '/Users/mistlight/Projects/ext-Projects/Ralph-Site', 'PYENV_ROOT': '/Users/mistlight/.pyenv', 'PYENV_SHELL': 'zsh', 'PYTHONPATH': '/Volumes/Crucial X9/ext-Projects/Ralph-Site/tmp/docs_build/ralph-workflow', 'RBENV_DIR': '/Users/mistlight/Projects/ext-Projects/Ralph-Site', 'RBENV_HOOK_PATH': '/Users/mistlight/.rbenv/rbenv.d:/opt/homebrew/Cellar/rbenv/1.3.2/rbenv.d:/usr/etc/rbenv.d:/opt/homebrew/etc/rbenv.d:/etc/rbenv.d:/usr/lib/rbenv/hooks', 'RBENV_ORIG_PATH': '/Users/mistlight/.opencode/bin:/Users/mistlight/.kilo/bin:/Users/mistlight/.local/bin:/Users/mistlight/.pyenv/shims:/Users/mistlight/.sdkman/candidates/sbt/current/bin:/Users/mistlight/.bun/bin:/opt/homebrew/opt/openjdk/bin:/Users/mistlight/.cabal/bin:/Users/mistlight/.ghcup/bin:/Users/mistlight/.local/bin:/Users/mistlight/.nvm/versions/node/v24.15.0/bin:/Users/mistlight/.local/bin:/Users/mistlight/.rbenv/shims:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/pkg/env/global/bin:/Library/Apple/usr/bin:/opt/homebrew/bin:/opt/podman/bin:/Users/mistlight/.cargo/bin:/Applications/kitty.app/Contents/MacOS', 'RBENV_ROOT': '/Users/mistlight/.rbenv', 'RBENV_SHELL': 'zsh', 'RBENV_VERSION': '4.0.1', 'RUBYLIB': '/Users/mistlight/.rbenv/versions/4.0.1/lib/ruby/site_ruby/4.0.0:/opt/homebrew/Cellar/rbenv/1.3.2/rbenv.d/exec/gem-rehash', 'RUBYOPT': '-r/Users/mistlight/.rbenv/versions/4.0.1/lib/ruby/site_ruby/4.0.0/bundler/setup', 'SBT_HOME': '/Users/mistlight/.sdkman/candidates/sbt/current', 'SDKMAN_BROKER_API': 'https://broker.sdkman.io', 'SDKMAN_CANDIDATES_API': 'https://api.sdkman.io/2', 'SDKMAN_CANDIDATES_DIR': '/Users/mistlight/.sdkman/candidates', 'SDKMAN_DIR': '/Users/mistlight/.sdkman', 'SDKMAN_PLATFORM': 'darwinarm64', 'SHELL': '/bin/zsh', 'SHLVL': '1', 'SSH_AUTH_SOCK': '/var/run/com.apple.launchd.pOb69mNDyw/Listeners', 'TERM': 'xterm-kitty', 'TERMINFO': '/Applications/kitty.app/Contents/Resources/kitty/terminfo', 'TMPDIR': '/var/folders/y0/wlfyz_7s4d9_wwcd0bc74s8c0000gn/T/', 'TWINE_PASSWORD': 'pypi-AgEIcHlwaS5vcmcCJDY3MjM3NDRmLTRkZGItNGNhZC1hNGY4LTkxZGM3YTY4NDQ4MAACFlsxLFsicmFscGgtd29ya2Zsb3ciXV0AAixbMixbIjcyMjEyMjkwLWU0NjgtNGU1Yi1hYzc5LWZmZDQzZGJmZDdhNCJdXQAABiAC2YNw2c7doCKOZ_tC3v5z6wzvAEICkvt4YDHz8_BIPw', 'TWINE_USERNAME': '__token__', 'USER': 'mistlight', 'UV': '/Users/mistlight/.local/bin/uv', 'UV_RUN_RECURSION_DEPTH': '1', 'VIRTUAL_ENV': '/Volumes/Crucial X9/ext-Projects/Ralph-Site/tmp/docs_build/ralph-workflow/.venv', 'WINDOWID': '97', 'XPC_FLAGS': '0x0', 'XPC_SERVICE_NAME': '0', 'ZSH': '/Users/mistlight/.oh-my-zsh', '__CFBundleIdentifier': 'net.kovidgoyal.kitty', '__CF_USER_TEXT_ENCODING': '0x1F5:0x0:0x0', 'DOCUTILSCONFIG': '/Volumes/Crucial X9/ext-Projects/Ralph-Site/tmp/docs_build/ralph-workflow/docs/sphinx/docutils.conf'}))[source]

Read an environment variable by name.

Parameters:
  • session (CoordinationSessionLike) – Agent session; must declare env.read.

  • _workspace (WorkspaceLike) – Unused; kept for tool-handler signature parity.

  • params (dict[str, object]) – Mapping with required name (string).

  • env (dict[str, str] | _Environ[str]) – Optional injected environment mapping. Defaults to os.environ (read-only os._Environ[str]).

Returns:

A ToolResult whose text content is <name>=<value> or <name>=[not found] when the variable is absent.

Raises:

CapabilityDeniedError – When the session does not declare env.read.

Return type:

ToolResult

Side effects:

Pure read of the injected env mapping. No subprocess, no network call, no workspace writes.

ralph.mcp.tools.coordination.handle_report_progress(session, _workspace, params, *, now_fn=<function _timestamp>)[source]

Report agent progress to the Ralph pipeline.

Parameters:
  • session (CoordinationSessionLike) – Agent session; must declare run.report_progress.

  • _workspace (WorkspaceLike) – Unused; kept for tool-handler signature parity.

  • params (dict[str, object]) – Mapping with required status (string) and optional note (string, defaults to empty).

  • now_fn (Callable[[], int]) – Optional injected wall-clock provider for the unix_ts suffix in the formatted text. Defaults to _timestamp.

Returns:

A ToolResult whose text content is the formatted progress line (suffixed with PROGRESS_PIPELINE_MARKER so the idle watchdog can key on it).

Raises:

CapabilityDeniedError – When the session does not declare run.report_progress.

Return type:

ToolResult

Side effects:

Pure with respect to the workspace. Emits a run.report_progress event to the pipeline. No subprocess, no network call.

ralph.mcp.tools.coordination.require_capability(session, capability, action)[source]

Require a capability, raising a capability-denied error when missing.

Parameters:
Return type:

None

ralph.mcp.tools.exec

MCP exec tool handler.

Executes bounded subprocesses directly in the workspace after capability checks and blacklist policy filtering.

Exported surface:

  • handle_exec_command — the public MCP tool handler. Validates the ProcessExecBounded capability on the session, parses and policy-checks the command, runs the bounded subprocess, and returns the result or a timeout-shaped error.

  • parse_exec_params / run_command / apply_exec_policy — parameter parsing, subprocess execution, and blacklist enforcement helpers (exposed for tests; the public tool contract is the handler above).

  • ExecParams / ExecRunDeps / ExecutionError — typed parameter bundle, dependency-injection bundle, and the typed error raised on timeout / launch failure.

  • check_command / format_exec_result / resolve_spill_dir — lower-level helpers used by the handler.

  • PROCESS_EXEC_BOUNDED_CAPABILITY / DEFAULT_TIMEOUT_MS — the capability string and the per-call default timeout (90 000 ms; the hard cap is EXEC_MAX_TIMEOUT_MS in ralph.timeout_defaults).

Trust boundary: this tool is the only public path that lets a hosted agent spawn an arbitrary subprocess. It enforces:

  • A mandatory capability check (default-deny if the session does not declare ProcessExecBounded).

  • A static blacklist covering privilege escalation (sudo, su, doas, pkexec, runuser), destructive system commands (shutdown, reboot, halt, poweroff, killall), network tunnel and remote-network tools (nc, ncat, netcat, socat, ssh, scp, rsync), and container / namespace escapes (docker, podman, chroot, nsenter, unshare).

  • A bounded per-call timeout (timeout_ms capped at EXEC_MAX_TIMEOUT_MS); a non-positive or missing value is clamped to the default so a direct caller can never produce an unbounded blocking call.

  • A bounded output spill (anything above SPILL_OUTPUT_LIMIT_BYTES is written to .agent/tmp/ rather than returned to the model).

Side effects: spawns a subprocess under ralph.process.manager (registered with the global ProcessManager), executes it in the workspace root, captures stdout/stderr, and may write a spill file to <workspace>/.agent/tmp/ when the output exceeds the spill limit. The subprocess is killed on timeout. The capability check is the trust boundary — everything else is a hard-coded defence-in-depth layer.

class ralph.mcp.tools.exec.ExecParams(command, args, timeout_ms)[source]

Bases: object

Parsed parameters for the MCP exec tool.

Parameters:
  • command (str)

  • args (list[str])

  • timeout_ms (int)

class ralph.mcp.tools.exec.ExecRunDeps(runner=None, cwd_provider=None, process_manager=None, on_output_chunk=None, spill_dir=None)[source]

Bases: object

Injectable dependencies for exec tool command execution.

Parameters:
  • runner (CommandRunner | None)

  • cwd_provider (CwdProvider | None)

  • process_manager (ProcessManager | None)

  • on_output_chunk (OutputChunkCallback | None)

  • spill_dir (Path | None)

spill_dir: Path | None = None

Directory for oversized-output spill files. Defaults to the OS temp dir (tempfile.gettempdir()) so the OS reclaims them; injectable for tests.

exception ralph.mcp.tools.exec.ExecutionError(message='', *, current_bytes=None, cap_bytes=None, removed_paths=None, removed_bytes=None, remaining_bytes=None, workspace_bytes=None, max_workspace_bytes=None, timed_out=False, timeout_ms=None, suggested_timeout_ms=None, diagnostics=None)[source]

Bases: ToolError

Raised when the exec subprocess cannot be started or times out.

Optional keyword fields enable structured, agent-actionable error messages for cache-full and workspace-limit scenarios.

Parameters:
  • message (str)

  • current_bytes (int | None)

  • cap_bytes (int | None)

  • removed_paths (int | None)

  • removed_bytes (int | None)

  • remaining_bytes (int | None)

  • workspace_bytes (int | None)

  • max_workspace_bytes (int | None)

  • timed_out (bool)

  • timeout_ms (int | None)

  • suggested_timeout_ms (int | None)

  • diagnostics (str | None)

Return type:

None

class ralph.mcp.tools.exec.WorkspaceWithRoot(*args, **kwargs)[source]

Bases: Protocol

Workspace surface required for command execution.

property root: Path

Return the absolute workspace root path.

ralph.mcp.tools.exec.apply_exec_policy(command, args)[source]

Apply command policy and raise if the command is denied.

Parameters:
  • command (str)

  • args (list[str])

Return type:

None

ralph.mcp.tools.exec.check_command(command, args)[source]

Return a denial reason when a command matches the blacklist policy.

Parameters:
  • command (str)

  • args (list[str])

Return type:

str | None

ralph.mcp.tools.exec.format_exec_result(command, args, output, timeout_ms)[source]

Format subprocess output to match the Rust tool response.

Parameters:
  • command (str)

  • args (list[str])

  • output (_CompletedProcessAdapter)

  • timeout_ms (int)

Return type:

str

ralph.mcp.tools.exec.handle_exec_command(session, workspace, params, deps=None)[source]

Execute a bounded subprocess in the workspace after blacklist checks.

Public MCP tool handler. Validates the ProcessExecBounded capability on the session, parses and policy-checks the command, runs the bounded subprocess under the workspace root, and returns the formatted result or a timeout-shaped error.

Parameters:
  • session (CoordinationSessionLike) – Agent session carrying the capability set, run id, and chunk callback used to compose output for live streaming.

  • workspace (object) – Workspace surface whose workspace_root is the cwd for the spawned subprocess. Path-like is required.

  • params (Mapping[str, object]) – Mapping with command (string) and optional args (list of strings), timeout_ms (int, bounded by EXEC_MAX_TIMEOUT_MS).

  • deps (ExecRunDeps | None) – Optional dependency-injection bundle (custom runner, cwd_provider, process_manager, on_output_chunk, spill_dir). When None, DEFAULT_EXEC_RUN_DEPS is used.

Returns:

A ToolResult whose text content is the formatted command output (returncode + stdout/stderr). Output above SPILL_OUTPUT_LIMIT_BYTES is written to <workspace>/.agent/tmp/ instead of returned to the model.

Raises:
  • CapabilityDeniedError – When the session does not declare ProcessExecBounded. The handler enforces default-deny.

  • InvalidParamsError – When params fails the ExecParams parser (missing command, wrong types, etc.).

  • ExecutionError – When the subprocess fails to launch (not on non-zero return; non-zero return is preserved as text).

Return type:

ToolResult

Side effects:

Spawns a subprocess registered with the global ProcessManager and executes it in the workspace root. Captures stdout/stderr, kills the subprocess on timeout, and may write a spill file to <workspace>/.agent/tmp/ when the output exceeds the spill limit. A timeout is converted into an actionable, non-retryable is_error ToolResult (not a -32603 protocol error).

ralph.mcp.tools.exec.parse_exec_params(params)[source]

Parse and validate exec tool parameters.

Parameters:

params (Mapping[str, object])

Return type:

ExecParams

ralph.mcp.tools.exec.resolve_spill_dir(workspace, deps)[source]

Resolve where oversized exec output spills, INSIDE the workspace by default.

The agent reads spill files through the workspace-scoped read/exec tools, which reject any path resolving outside the workspace root. Spilling to the OS temp dir produces a path the agent is told to read but cannot reach — it goes blind on exactly the large outputs (a full pytest run) where the failing summary lives, and loops re-running the command until the watchdog kills it. Default to <workspace>/.agent/tmp (Ralph’s own readable scratch dir); an explicitly injected deps.spill_dir (tests, custom deployments) wins.

Parameters:
Return type:

Path

ralph.mcp.tools.exec.run_command(command, args, workspace, timeout_ms, deps=None)[source]

Execute a subprocess directly in the workspace root after blacklist checks.

Parameters:
  • command (str)

  • args (list[str])

  • workspace (object)

  • timeout_ms (int)

  • deps (ExecRunDeps | None)

Return type:

_CompletedProcessAdapter

ralph.mcp.tools.unsafe_exec

MCP unsafe_exec tool handler.

Executes unrestricted shell commands in the real workspace directory. Only version control commands (git, hg, svn) are blocked.

Execution goes through the SAME bounded process-manager path as exec (run_command): output is capped (and spilled to a file when oversized rather than buffered unbounded in memory) and the process tree is killed on timeout. The sync handler is dispatched off the asyncio event loop by the production _FallbackHttpHandler via the saturated-dispatch seam (ralph.mcp.server._saturated_dispatch), so a long shell command cannot freeze the server.

The raw_exec tool is an alias for unsafe_exec — it uses the same handler and exposes the same functionality under a different name.

ralph.mcp.tools.unsafe_exec.handle_unsafe_exec(session, workspace, params, deps=None)[source]

Execute an unrestricted shell command in the real workspace directory.

Parameters:
Return type:

ToolResult

ralph.mcp.tools.git_read

MCP Git read tool handlers.

Ports the Rust MCP Git read tools so agents can inspect repository state through bounded read-only git commands from the workspace root.

Exported surface:

  • handle_git_status — runs git status in the workspace root. Capability: GitStatusRead.

  • handle_git_diff — runs git diff [args] in the workspace root. The handler uses the lenient runner so a non-zero exit (which can happen when there is nothing to diff, or when a path filter matches no files) is surfaced as stdout/stderr rather than an exception. Capability: GitDiffRead.

  • handle_git_log — runs git log -<count> --oneline (default count = DEFAULT_LOG_COUNT = 10). Capability: GitStatusRead.

  • handle_git_show — runs git show <ref> for a single object. Capability: GitStatusRead.

  • parse_git_diff_params / parse_git_log_params / parse_git_show_params — the parameter parsers used by the handlers above (string-only args, bounded count, ref validation).

  • run_git_command / run_git_command_lenient — the two subprocess runners. Both require a successful git exit code unless the lenient variant is used. They are the only call sites of the internal _run_git_subprocess helper, which always carries the fixed _GIT_READ_TIMEOUT_SECONDS = 30.0 bound.

Trust boundary: every handler is gated on a McpCapability and runs through a process spawned by ralph.process.manager. The 30-second hard timeout is the bounded-subprocess contract — a hung git status over a large vendor/ submodule or a held .git lock cannot hang the MCP server thread.

Side effects: spawns a git subprocess under the workspace root (registered with the global ProcessManager) and reads its stdout/stderr. No write to the workspace, no network call. Timeouts are converted into a non-retryable is_error result that names the likely cause (vendor/ submodule or held lock) and tells the agent not to retry unchanged.

exception ralph.mcp.tools.git_read.ExecutionError(message='', *, timed_out=False)[source]

Bases: ToolError

Raised when a git subprocess cannot be started or fails.

timed_out marks the bounded-timeout case so the git read handlers can convert it into an actionable, non-retryable is_error result instead of letting it surface as a -32603 protocol error the agent retries forever.

Parameters:
  • message (str)

  • timed_out (bool)

Return type:

None

class ralph.mcp.tools.git_read.GitDiffParams(args)[source]

Bases: object

Parsed parameters for the git diff tool.

Parameters:

args (list[str])

class ralph.mcp.tools.git_read.GitLogParams(count)[source]

Bases: object

Parsed parameters for the git log tool.

Parameters:

count (int)

class ralph.mcp.tools.git_read.GitShowParams(git_ref)[source]

Bases: object

Parsed parameters for the git show tool.

Parameters:

git_ref (str)

class ralph.mcp.tools.git_read.WorkspaceWithRoot(*args, **kwargs)[source]

Bases: Protocol

Workspace surface required for git command execution.

property root: Path

Return the absolute workspace root path.

ralph.mcp.tools.git_read.handle_git_diff(session, workspace, params)[source]

Read the git diff of the workspace.

Parameters:
  • session (CoordinationSessionLike) – Agent session; must declare GitDiffRead.

  • workspace (object) – Workspace surface whose root is the cwd for git diff.

  • params (Mapping[str, object]) – Mapping with optional args (list of strings passed verbatim to git diff). Empty mapping returns the full-tree diff.

Returns:

A ToolResult whose text content is the git diff output. The lenient runner is used so a non-zero exit (no diff to show, unmatched path filter) surfaces as stdout/stderr rather than an exception.

Raises:
Return type:

ToolResult

Side effects:

Spawns a git diff subprocess registered with the global ProcessManager. Bounded by _GIT_READ_TIMEOUT_SECONDS = 30. No workspace writes, no network calls.

ralph.mcp.tools.git_read.handle_git_log(session, workspace, params)[source]

Read the git commit log.

Parameters:
  • session (CoordinationSessionLike) – Agent session; must declare GitStatusRead.

  • workspace (object) – Workspace surface whose root is the cwd for git log.

  • params (Mapping[str, object]) – Mapping with optional count (positive integer, defaults to DEFAULT_LOG_COUNT = 10).

Returns:

A ToolResult whose text content is the git log -<count> --oneline output.

Raises:
Return type:

ToolResult

Side effects:

Spawns a git log subprocess registered with the global ProcessManager. Bounded by _GIT_READ_TIMEOUT_SECONDS = 30. No workspace writes, no network calls.

ralph.mcp.tools.git_read.handle_git_show(session, workspace, params)[source]

Show a git object by ref.

Parameters:
  • session (CoordinationSessionLike) – Agent session; must declare GitStatusRead.

  • workspace (object) – Workspace surface whose root is the cwd for git show.

  • params (Mapping[str, object]) – Mapping with required ref (string; commit-ish, tag, or branch) per parse_git_show_params.

Returns:

A ToolResult whose text content is the git show <ref> output.

Raises:
Return type:

ToolResult

Side effects:

Spawns a git show subprocess registered with the global ProcessManager. Bounded by _GIT_READ_TIMEOUT_SECONDS = 30. No workspace writes, no network calls.

ralph.mcp.tools.git_read.handle_git_status(session, workspace, _params)[source]

Read the git status of the workspace.

Parameters:
  • session (CoordinationSessionLike) – Agent session; must declare GitStatusRead.

  • workspace (object) – Workspace surface whose root is the cwd for git status.

  • _params (Mapping[str, object]) – Unused; kept for tool-handler signature parity.

Returns:

A ToolResult whose text content is the git status output.

Raises:

CapabilityDeniedError – When the session does not declare GitStatusRead.

Return type:

ToolResult

Side effects:

Spawns a git status subprocess registered with the global ProcessManager. Bounded by _GIT_READ_TIMEOUT_SECONDS = 30. No workspace writes, no network calls.

ralph.mcp.tools.git_read.parse_git_diff_params(params)[source]

Parse git diff params, keeping only string arguments.

Parameters:

params (Mapping[str, object])

Return type:

GitDiffParams

ralph.mcp.tools.git_read.parse_git_log_params(params)[source]

Parse git log params with the Rust default count.

Parameters:

params (Mapping[str, object])

Return type:

GitLogParams

ralph.mcp.tools.git_read.parse_git_show_params(params)[source]

Parse git show params.

Parameters:

params (Mapping[str, object])

Return type:

GitShowParams

ralph.mcp.tools.git_read.run_git_command(workspace, args, *, runner=None, cwd_provider=<bound method Path.cwd of <class 'pathlib.Path'>>)[source]

Execute git and require a successful exit status.

Parameters:
  • workspace (object)

  • args (list[str])

  • runner (GitRunner | None)

  • cwd_provider (CwdProvider)

Return type:

str

ralph.mcp.tools.git_read.run_git_command_lenient(workspace, args, *, runner=None, cwd_provider=<bound method Path.cwd of <class 'pathlib.Path'>>)[source]

Execute git and return combined stdout/stderr regardless of exit code.

Parameters:
  • workspace (object)

  • args (list[str])

  • runner (GitRunner | None)

  • cwd_provider (CwdProvider)

Return type:

str

ralph.mcp.tools.names

Canonical Ralph MCP tool naming helpers.

class ralph.mcp.tools.names.RalphToolName(*values)[source]

Bases: StrEnum

Canonical names for all Ralph MCP tools.

as_claude_alias(*, server_name='ralph')[source]

Return the Claude MCP tool alias in the form mcp__<server>__<tool>.

Parameters:

server_name (str)

Return type:

str

prompt_aliases(*, tool_name_prefix='')[source]

Return the full set of prompt-facing alias names for this tool.

Parameters:

tool_name_prefix (str)

Return type:

tuple[str, …]

prompt_reference(*, tool_name_prefix='')[source]

Return a human-readable reference string for prompts.

Parameters:

tool_name_prefix (str)

Return type:

str

with_prefix(*, tool_name_prefix='')[source]

Return the tool name with an optional prefix applied.

Parameters:

tool_name_prefix (str)

Return type:

str

ralph.mcp.tools.names.claude_tool_name(tool_name, *, server_name='ralph')[source]

Return the Claude MCP alias for a tool name (mcp__<server>__<tool>).

Parameters:
Return type:

str

ralph.mcp.tools.names.claude_tool_name_prefix(*, server_name='ralph')[source]

Return the mcp__<server>__ prefix string used by Claude for MCP tools.

Parameters:

server_name (str)

Return type:

str

ralph.mcp.tools.names.custom_proxy_tool_name(server_name, tool_name)[source]

Return the stable proxy alias for a Ralph custom MCP server tool.

Parameters:
  • server_name (str)

  • tool_name (str)

Return type:

str

ralph.mcp.tools.names.opencode_tool_name(tool_name, *, server_name='ralph')[source]

Return the OpenCode alias for a tool name (<server>_<tool>).

OpenCode namespaces every remote MCP tool as <serverName>_<tool> (e.g. an angular server exposes angular_ai_tutor). Ralph’s server is registered as ralph (see transport/opencode.py and its ralph_* permission), so its tools are exposed as ralph_<tool>. This is the canonical builder so prompts match the names OpenCode actually exposes.

Parameters:
Return type:

str

ralph.mcp.tools.names.opencode_tool_name_prefix(*, server_name='ralph')[source]

Return the <server>_ prefix OpenCode uses for remote MCP tools.

Parameters:

server_name (str)

Return type:

str

ralph.mcp.tools.names.prefix_tool_name(tool_name, *, tool_name_prefix='')[source]

Return the tool name with an optional prefix applied.

Parameters:
Return type:

str

ralph.mcp.tools.names.prefix_tool_names(tool_names, *, tool_name_prefix='')[source]

Apply the given prefix to each tool name in the sequence.

Parameters:
  • tool_names (Sequence[str | RalphToolName])

  • tool_name_prefix (str)

Return type:

list[str]

ralph.mcp.tools.names.proxied_mcp_tool_name(server_name, tool_name, *, origin)[source]

Return the stable proxy alias for a proxied MCP server tool.

Parameters:
  • server_name (str)

  • tool_name (str)

  • origin (str)

Return type:

str

ralph.mcp.tools.names.upstream_proxy_tool_name(server_name, tool_name)[source]

Return the stable proxy alias for an agent-native upstream MCP server tool.

Parameters:
  • server_name (str)

  • tool_name (str)

Return type:

str

ralph.mcp.tool_contract

Shared MCP tool-surface contract helpers.

This module owns the canonical Ralph MCP tool naming contract so startup, runtime, prompts, and provider integrations all derive from the same rules.

ralph.mcp.tool_contract.canonical_tool_name(tool_name)[source]

Collapse a raw or aliased tool name to its canonical raw Ralph name.

Parameters:

tool_name (str)

Return type:

str

ralph.mcp.tool_contract.canonicalize_tool_names(tool_names)[source]

Return deduped canonical raw Ralph tool names in input order.

Parameters:

tool_names (Iterable[str])

Return type:

tuple[str, …]

ralph.mcp.tool_contract.claude_alias_for_tool_name(tool_name)[source]

Return the strict-MCP alias for a canonical Ralph tool name.

Parameters:

tool_name (str)

Return type:

str | None

ralph.mcp.tool_contract.expand_tool_names_with_aliases(tool_names)[source]

Return each canonical tool name plus its strict-MCP alias, deduped.

Parameters:

tool_names (Iterable[str])

Return type:

list[str]

ralph.mcp.tool_contract.visible_owned_tool_names(session, workspace, *, upstream_registry=None, include_aliases=True)[source]

Return the live visible Ralph-owned tool surface for a session.

Parameters:
  • session (object)

  • workspace (object)

  • upstream_registry (UpstreamRegistry | None)

  • include_aliases (bool)

Return type:

list[str]

ralph.mcp.tool_contract.visible_tool_names_for_capabilities(capability_ids, *, drain)[source]

Project capabilities onto the real runtime registry tool surface.

Parameters:
  • capability_ids (Iterable[str])

  • drain (str)

Return type:

list[str]

ralph.mcp.tools.websearch

MCP tool handler for web search across pluggable backends.

Exposes handle_web_search, which dispatches a search query through the configured backend (and optional fallbacks) and returns a ToolResult. Backends are loaded lazily; the dispatch order is taken from WebSearchConfig.

Exported surface:

  • handle_web_search — the public MCP tool handler. Requires the WebSearch capability on the session, parses a string query (and optional bounded limit clamped to [MIN_LIMIT, MAX_LIMIT] = [1, 25]), and dispatches through the configured backend order (default backend followed by the configured fallbacks).

  • build_backend / _build_backend — the public / private factory that returns a WebSearchBackend instance for a given backend name and config. The factory always uses a resolved timeout_seconds (per-backend override falls back to the global default).

  • WEB_SEARCH_CAPABILITY / MIN_LIMIT / MAX_LIMIT — the capability string and the request-size bounds.

Trust boundary: every handler is gated on the WebSearch McpCapability. The backend is selected from a closed allowlist (ddgs, searxng, tavily, brave, exa); an unsupported backend name or a missing configuration returns WebSearchError.

Side effects (network contract): every backend implementation uses an injected timeout_seconds on the network call, so a misbehaving upstream cannot hang the MCP server thread. The dispatch loop falls back through the configured backend order and only returns an is_error result after every backend has failed. loguru warnings are emitted on every backend failure so an operator can correlate upstream outages with retries.

Dispatch a web search query through the configured backend and return results.

Parameters:
  • session (CoordinationSessionLike) – Agent session; must declare WebSearch.

  • _workspace (Workspace) – Unused; kept for tool-handler signature parity.

  • params (dict[str, object]) – Mapping with required query (string) and optional limit (int, clamped to [MIN_LIMIT, MAX_LIMIT] = [1, 25]).

  • web_search_config (WebSearchConfig | None) – Optional injected WebSearchConfig for the dispatch order and per-backend overrides. Defaults to WebSearchConfig().

Returns:

A ToolResult whose text content is the formatted backend result list (Title / URL / Snippet blocks joined by blank lines). Falls back through the configured backend order and only returns is_error=True after every backend has failed.

Raises:
Return type:

ToolResult

Side effects (network contract):

Every backend implementation uses an injected timeout_seconds on the network call so a misbehaving upstream cannot hang the MCP server thread. loguru warnings are emitted on every backend failure so an operator can correlate upstream outages with retries. No workspace writes.

ralph.mcp.tools.webvisit

MCP tool handler for visit_url: fetch one URL and return readable text.

Exported surface:

  • handle_visit_url — the public MCP tool handler. Requires the WebVisit capability on the session, fetches the URL through ralph.mcp.webvisit.fetcher.fetch_url (bounded timeout_ms, max_bytes, and an opt-in private-network toggle), and runs the response body through the readability-lxml / selectolax extractor. Returns a JSON payload with status, title, effective_url, content_type, the readable text (clamped to max_bytes // 4 characters), and the extracted links when with_links is requested.

  • handle_download_url — the public download handler. Requires the WebDownload capability, fetches the URL with the same bounded network contract, and writes the response body to a workspace output_path (UTF-8 with errors="replace"). Returns a JSON payload with status, effective_url, content_type, output_path, and bytes_written. A write failure is converted into a non-retryable is_error result so a model that sees the error does not loop re-issuing the call.

  • _error_result / _MAX_TEXT_CHARS_DIVISOR — internal helper for the FetchOutcome -> ToolResult translation, and the factor used to clamp the extracted text length.

  • WEB_VISIT_CAPABILITY / WEB_DOWNLOAD_CAPABILITY — the capability strings required by the two public handlers.

Trust boundary: every public handler is gated on a McpCapability declared by the agent session. The fetch is performed by ralph.mcp.webvisit.fetcher.fetch_url which carries the bounded timeout_ms and max_bytes from WebVisitConfig; private network ranges are opt-in via allow_private_networks.

Side effects (network contract): handle_visit_url performs an HTTP/HTTPS fetch bounded by WebVisitConfig.timeout_ms and max_bytes. handle_download_url additionally writes the downloaded body to a workspace path (any OSError is captured and returned as a non-retryable is_error result rather than re-raised as a -32603 protocol error). The extractor is best-effort: an extraction exception is converted to an is_error JSON payload with status="unsupported_content" and the exception text.

ralph.mcp.tools.webvisit.handle_download_url(session, workspace, params, *, web_visit_config=None)[source]

Download a URL and save its content to a workspace file.

Parameters:
  • session (CoordinationSessionLike) – Agent session; must declare WebDownload.

  • workspace (Workspace) – Workspace surface whose root resolves output_path.

  • params (dict[str, object]) – Mapping with required url (string) and output_path (relative path inside the workspace).

  • web_visit_config (WebVisitConfig | None) – Optional injected WebVisitConfig providing timeout_ms, max_bytes, user_agent, and allow_private_networks. Defaults to WebVisitConfig().

Returns:

A ToolResult whose text content is a JSON payload with status, effective_url, content_type, output_path, and bytes_written.

Raises:
Return type:

ToolResult

Side effects (network + filesystem contract):

Performs an HTTP/HTTPS fetch bounded by WebVisitConfig.timeout_ms and max_bytes. Writes the response body to output_path as UTF-8 with errors="replace". A write failure (OSError) is captured and returned as a non-retryable is_error result rather than re-raised as a -32603 protocol error.

ralph.mcp.tools.webvisit.handle_visit_url(session, _workspace, params, *, web_visit_config=None)[source]

Fetch a URL and return readable extracted text.

Parameters:
  • session (CoordinationSessionLike) – Agent session; must declare WebVisit.

  • _workspace (Workspace) – Unused; kept for tool-handler signature parity.

  • params (dict[str, object]) – Mapping with required url (string, http/https). The handler also reads the optional with_links boolean (overrides WebVisitConfig.extract_links).

  • web_visit_config (WebVisitConfig | None) – Optional injected WebVisitConfig providing timeout_ms, max_bytes, user_agent, and allow_private_networks. Defaults to WebVisitConfig().

Returns:

A ToolResult whose text content is a JSON payload with status, title, effective_url, content_type, text (clamped to max_bytes // 4 chars), and the links array when with_links was requested.

Raises:
Return type:

ToolResult

Side effects (network contract):

Performs an HTTP/HTTPS fetch bounded by WebVisitConfig.timeout_ms and max_bytes. Private network ranges are opt-in via allow_private_networks. Readability extraction is best-effort; an extraction exception is converted to is_error=True with status="unsupported_content". No workspace writes.

ralph.mcp.tools.workspace

Workspace tool handlers for MCP interactions.

Ports the Rust mcp_server::tool_workspace helpers into Python so MCP handlers can read, list, search, and write workspace files while enforcing session capabilities and edit area policies.

ralph.mcp.tools.workspace.check_edit_area_restriction(session, path)[source]

Enforce the parallel-worker edit-area restriction for path.

Raises:

CapabilityDeniedError – If the session is a parallel worker and the configured edit-area policy does not approve the path.

Parameters:
  • session (object)

  • path (str)

Return type:

None

ralph.mcp.tools.workspace.handle_append_file(session, workspace, params)[source]

Append content to a workspace file.

Parameters:
Return type:

ToolResult

ralph.mcp.tools.workspace.handle_copy_file(session, workspace, params)[source]

Copy a workspace file or directory to a new location.

Parameters:
Return type:

ToolResult

ralph.mcp.tools.workspace.handle_create_directory(session, workspace, params)[source]

Create a directory (and parents) within the workspace.

Parameters:
Return type:

ToolResult

ralph.mcp.tools.workspace.handle_delete_path(session, workspace, params)[source]

Delete a workspace file or directory.

Parameters:
Return type:

ToolResult

ralph.mcp.tools.workspace.handle_directory_tree(session, workspace, params)[source]

Return a nested JSON directory tree for a workspace path.

Parameters:
Return type:

ToolResult

ralph.mcp.tools.workspace.handle_edit_file(session, workspace, params)[source]

Apply structured oldText/newText replacements to a workspace file.

Parameters:
Return type:

ToolResult

ralph.mcp.tools.workspace.handle_grep_files(session, workspace, params)[source]

Search file contents for a pattern and return line-level matches.

Parameters:
Return type:

ToolResult

ralph.mcp.tools.workspace.handle_list_allowed_roots(session, workspace, params)[source]

Return the list of workspace paths the session is permitted to access.

Parameters:
Return type:

ToolResult

ralph.mcp.tools.workspace.handle_list_directory(session, workspace, params)[source]

List entries in a workspace directory, optionally recursive.

Parameters:
Return type:

ToolResult

ralph.mcp.tools.workspace.handle_list_directory_recursive(session, workspace, params)[source]

Return a flat listing of all entries under a workspace directory.

Parameters:
Return type:

ToolResult

ralph.mcp.tools.workspace.handle_move_file(session, workspace, params)[source]

Move or rename a workspace file or directory.

Parameters:
Return type:

ToolResult

ralph.mcp.tools.workspace.handle_read_file(session, workspace, params)[source]

Read a UTF-8 file from the workspace.

Full-file reads (no partial params) return a plain text block for UTF-8 files at or below max_bytes (default 5_000_000). The JSON envelope only appears when truncated is True OR when an error occurs (binary_or_invalid_utf8).

Partial-read parameter groups (line_start/line_end, offset/limit, head, tail) are mutually exclusive; combining any two raises InvalidParams.

Optional param max_bytes overrides the default ceiling for full-file reads.

Parameters:
Return type:

ToolResult

ralph.mcp.tools.workspace.handle_read_image(session, workspace, params, *, max_inline_bytes=5242880)[source]

Read an image file and return it as a capability-aware content block.

Requires MediaRead capability. Validates that the file is a supported image format, then delegates to the shared workspace media handler for delivery decision (inline image, typed block, or explicit unsupported/error).

This is a compatibility alias over _handle_workspace_media that restricts inputs to image formats only while preserving the same truthful delivery contract as read_media.

Parameters:
Return type:

ToolResult

ralph.mcp.tools.workspace.handle_read_media(session, workspace, params, *, max_inline_bytes=5242880)[source]

Read a media file or replay a stored artifact handle.

Accepts either: - a workspace file path (e.g., screenshots/shot.png) - a ralph://media/{artifact_id} replay handle from a prior session

When given a replay handle, rehydrates the artifact from the live session manifest and returns the same typed block that was originally emitted. Invalid or unrecognised handles return an explicit structured failure.

For workspace paths, delivery mode is determined by the session’s model identity via the capability matrix: INLINE_IMAGE, TYPED_BLOCK, RESOURCE_REFERENCE_REPLAY, or UNSUPPORTED.

Parameters:
Return type:

ToolResult

ralph.mcp.tools.workspace.handle_read_multiple_files(session, workspace, params)[source]

Read multiple workspace files in one call and return per-file results.

Parameters:
Return type:

ToolResult

ralph.mcp.tools.workspace.handle_search_files(session, workspace, params)[source]

Search for files matching a glob pattern within a workspace directory.

Parameters:
Return type:

ToolResult

ralph.mcp.tools.workspace.handle_stat(session, workspace, params)[source]

Return file metadata (type, size, timestamps) for a workspace path.

Parameters:
Return type:

ToolResult

ralph.mcp.tools.workspace.handle_write_file(session, workspace, params)[source]

Write UTF-8 content to a workspace file, creating it if necessary.

Parameters:
Return type:

ToolResult

ralph.mcp.tools.workspace.infer_image_mime_type(path)[source]

Return the MIME type for supported image paths based on extension.

Parameters:

path (str)

Return type:

str | None

ralph.mcp.tools.workspace.is_parallel_worker(session)[source]

Return True when the active session is a parallel fan-out worker.

Parameters:

session (object)

Return type:

bool

ralph.mcp.tools.workspace.is_path_git_tracked(workspace, path)[source]

Return True when path should be treated as git-tracked output.

The path must exist in the workspace and must not live in ephemeral directories such as .agent/, target/, or node_modules/.

Parameters:
Return type:

bool

ralph.mcp.tools.workspace.is_policy_approved(outcome)[source]

Return True if the given policy outcome represents an approval decision.

Parameters:

outcome (object | None)

Return type:

bool

ralph.mcp.tools.workspace.join_path(base, entry)[source]

Join a base relative path with an entry and normalize the result.

Parameters:
  • base (str) – Existing relative path, or "" for the workspace root.

  • entry (str) – Path fragment to append.

Returns:

A normalized POSIX-style relative path.

Return type:

str

ralph.mcp.tools.workspace.list_dir_entries(workspace, path)[source]

List the entries in path and surface workspace errors as ToolError.

Parameters:
Return type:

list[str]

ralph.mcp.tools.workspace.match_glob(rel_path, pattern)[source]

Match a path against a glob pattern supporting , *, and ? segments.

Parameters:
  • rel_path (str)

  • pattern (str)

Return type:

bool

ralph.mcp.tools.workspace.normalize_relative_path(path)[source]

Return a normalized POSIX-style relative path string.

Collapses redundant separators and parent references. An empty or dot-only path resolves to "" so callers can treat it as the workspace root.

Parameters:

path (str)

Return type:

str

ralph.mcp.tools.workspace.persist_upstream_media_artifacts(result, session, workspace)[source]

Persist upstream embedded media artifacts to the durable cache and session index.

Called after normalize_upstream_content_blocks so that:

  • resource_reference_replay blocks (backed by ralph://media/… URIs stored in the session manifest) are written to the durable cache and session index, enabling cross-session replay of artifacts from upstream embedded-data blocks.

  • URI-backed resource_reference blocks (delivery=’resource_reference’) reference external URIs and cannot be replayed across sessions. These are synthesized as unsupported_runtime_seam entries so the failure is explicit at invoke time.

Parameters:
  • result (object)

  • session (object)

  • workspace (Workspace)

Return type:

None

ralph.mcp.tools.workspace.required_string_param(params, name)[source]

Return a required string parameter, raising if it is missing.

Parameters:
  • params (dict[str, object])

  • name (str)

Return type:

str

ralph.mcp.tools.capability_denied_error

Capability-denied MCP tool error.

exception ralph.mcp.tools.capability_denied_error.CapabilityDeniedError[source]

Bases: ToolError

Raised when a required session capability is not available.

ralph.mcp.tools.coordination_session_like

Protocol for coordination tool session access.

class ralph.mcp.tools.coordination_session_like.CoordinationSessionLike(*args, **kwargs)[source]

Bases: Protocol

Minimum session surface required by coordination handlers.

Sessions may optionally carry a broker_secret (RFC-013 P3) — a broker-owned string the agent never sees directly, used to HMAC the run-scoped receipt and completion sentinel so a model with workspace write capabilities cannot forge either. None means no HMAC enforcement at write or read time (the pre-P3 contract); the handlers downcast gracefully when the attribute is absent.

property broker_secret: str | None

RFC-013 P3 broker-owned HMAC secret. Read-only at the protocol surface; the implementation decides whether the value is supplied by a constructor arg, a property backed by one, or a dataclass field with a None default.

check_capability(capability)[source]

Return a policy outcome for the requested capability.

Parameters:

capability (str)

Return type:

object

ralph.mcp.tools.invalid_params_error

Invalid MCP tool parameter error.

exception ralph.mcp.tools.invalid_params_error.InvalidParamsError[source]

Bases: ToolError

Raised when tool parameters are missing or invalid.

ralph.mcp.tools.json_repair

Import-safe JSON container repair for MCP tool arguments.

ralph.mcp.tools.json_repair.repair_json_containers(value)[source]

Decode JSON-looking strings into containers without touching scalars.

Parameters:

value (object)

Return type:

object

ralph.mcp.tools.json_repair.repair_params_for_schema(params, schema)[source]

Repair JSON container strings for structured schema fields.

Parameters:
  • params (dict[str, object])

  • schema (dict[str, object])

Return type:

dict[str, object]

ralph.mcp.tools.tool_content

MCP tool text content block.

class ralph.mcp.tools.tool_content.ToolContent(type, text)[source]

Bases: object

Single text tool response content block.

Parameters:
  • type (str)

  • text (str)

classmethod json_content(payload)[source]

Create a text content block carrying a JSON-serialized payload.

The payload is json.dumps-serialized with sort_keys=False so dict insertion order is preserved (helps the agent read the response in the same order the handler built it). The text content type is reused so downstream consumers that only know how to parse text fields keep working; agents that need a typed object can json.loads(content[0].text).

Parameters:

payload (Mapping[str, object] | list[object])

Return type:

ToolContent

classmethod text_content(text)[source]

Create a text content block.

Parameters:

text (str)

Return type:

ToolContent

to_dict()[source]

Serialize the content block to a dictionary.

Return type:

dict[str, str]

ralph.mcp.tools.tool_error

Base error raised by MCP tool handlers.

exception ralph.mcp.tools.tool_error.ToolError[source]

Bases: Exception

Base error raised by MCP tool handlers.

ralph.mcp.tools.tool_result

Serializable MCP tool result.

class ralph.mcp.tools.tool_result.ToolResult(content, is_error=None)[source]

Bases: object

Serializable MCP tool result.

Parameters:
  • content (list[ContentBlock])

  • is_error (bool | None)

to_dict()[source]

Serialize the result to an MCP-compatible dictionary.

Return type:

dict[str, object]

ralph.mcp.tools.workspace_like

Protocol for workspace access used by coordination tools.

class ralph.mcp.tools.workspace_like.WorkspaceLike(*args, **kwargs)[source]

Bases: Protocol

Placeholder workspace protocol for handler parity.

absolute_path(path)[source]

Return an absolute workspace path for the provided relative path.

Parameters:

path (str)

Return type:

str

ralph.mcp.transport

Public transport helpers for per-agent MCP wiring.

Grouped by agent: claude, codex, opencode, nanocoder, agy. Shared helpers (mcp.toml merging, env serialization) live in common.

ralph.mcp.transport.agy_mcp_config(endpoint)[source]

Return the AGY MCP JSON config string pointing to the given endpoint.

Parameters:

endpoint (str) – The MCP server HTTP endpoint URL.

Returns:

JSON string with mcpServers containing the Ralph entry with serverUrl key.

Return type:

str

ralph.mcp.transport.build_nanocoder_mcp_config(existing, endpoint, *, always_allow=(), unsafe_mode=False, workspace_path=None, env=None)[source]

Build a Nanocoder MCP payload with Ralph injected as the managed server.

Parameters:
  • existing (str | None)

  • endpoint (str)

  • always_allow (tuple[str, ...])

  • unsafe_mode (bool)

  • workspace_path (Path | None)

  • env (Mapping[str, str] | None)

Return type:

tuple[str, tuple[UpstreamMcpServer, …]]

ralph.mcp.transport.build_opencode_provider_config(existing, endpoint, *, unsafe_mode=False)[source]

Build a full OpenCode config JSON with Ralph MCP and return it with upstream servers.

Parameters:
  • existing (str | None)

  • endpoint (str)

  • unsafe_mode (bool)

Return type:

tuple[str, tuple[UpstreamMcpServer, …]]

ralph.mcp.transport.claude_mcp_config(endpoint, *, workspace_path=None, unsafe_mode=False)[source]

Return the Claude MCP JSON config string pointing to the given endpoint.

Parameters:
  • endpoint (str)

  • workspace_path (Path | None)

  • unsafe_mode (bool)

Return type:

str

ralph.mcp.transport.load_existing_agy_upstream_servers(workspace_path=None)[source]

Read AGY’s MCP config files and return any upstream MCP servers found.

Parameters:

workspace_path (Path | None) – Optional workspace path for workspace-level AGY config.

Returns:

Tuple of UpstreamMcpServer objects found in AGY config files.

Return type:

tuple[UpstreamMcpServer, …]

ralph.mcp.transport.load_existing_claude_upstream_servers(workspace_path=None)[source]

Read Claude’s MCP config files and return any upstream MCP servers found.

Parameters:

workspace_path (Path | None)

Return type:

tuple[UpstreamMcpServer, …]

ralph.mcp.transport.load_existing_nanocoder_upstream_servers(workspace_path, *, env=None)[source]

Load Nanocoder MCP servers from documented config locations.

Parameters:
  • workspace_path (Path | None)

  • env (dict[str, str] | None)

Return type:

tuple[UpstreamMcpServer, …]

ralph.mcp.transport.mcp_toml_as_upstreams(workspace_path)[source]

Load .agent/mcp.toml and return the configured upstream MCP servers.

Parameters:

workspace_path (Path | None)

Return type:

tuple[UpstreamMcpServer, …]

ralph.mcp.transport.merge_mcp_toml_into_upstreams(agent_native, mcp_toml_servers)[source]

Merge mcp.toml servers into agent-native upstreams, preferring mcp.toml on conflict.

Parameters:
Return type:

tuple[UpstreamMcpServer, …]

ralph.mcp.transport.merge_opencode_config_content(existing, endpoint)[source]

Merge Ralph MCP endpoint into an existing OpenCode config and return JSON.

Parameters:
  • existing (str | None)

  • endpoint (str)

Return type:

str

ralph.mcp.transport.prepare_codex_home(endpoint, *, workspace_path, existing_home, system_prompt_file, unsafe_mode=False)[source]

Prepare an isolated Codex home directory and return its path.

Parameters:
  • endpoint (str | None)

  • workspace_path (Path | None)

  • existing_home (str | None)

  • system_prompt_file (str | None)

  • unsafe_mode (bool)

Return type:

str

ralph.mcp.transport.prepare_codex_home_with_upstreams(endpoint, *, workspace_path, existing_home, system_prompt_file, unsafe_mode=False)[source]

Prepare an isolated Codex home directory and return its path with upstream servers.

Parameters:
  • endpoint (str | None)

  • workspace_path (Path | None)

  • existing_home (str | None)

  • system_prompt_file (str | None)

  • unsafe_mode (bool)

Return type:

tuple[str, tuple[UpstreamMcpServer, …]]

ralph.mcp.transport.set_upstream_mcp_config(runtime_env, upstreams)[source]

Inject upstream MCP config into the runtime environment dict.

Parameters:
Return type:

None

ralph.mcp.transport.claude

Claude-specific MCP transport helpers.

ralph.mcp.transport.claude.claude_mcp_config(endpoint, *, workspace_path=None, unsafe_mode=False)[source]

Return the Claude MCP JSON config string pointing to the given endpoint.

Parameters:
  • endpoint (str)

  • workspace_path (Path | None)

  • unsafe_mode (bool)

Return type:

str

ralph.mcp.transport.claude.load_existing_claude_upstream_servers(workspace_path=None)[source]

Read Claude’s MCP config files and return any upstream MCP servers found.

Parameters:

workspace_path (Path | None)

Return type:

tuple[UpstreamMcpServer, …]

ralph.mcp.transport.codex

Codex-specific MCP transport helpers.

ralph.mcp.transport.codex.cleanup_codex_homes()[source]

Remove every Codex home dir this process ever allocated.

Iterates _all_allocated_codex_homes (NOT the bounded _allocated_codex_homes deque) so FIFO-evicted homes are also reaped on interpreter shutdown. Earlier versions of this function only iterated _allocated_codex_homes, which (after the analysis-feedback wt-024 round 2 active-home fix changed _allocate_codex_home_dir to NOT rmtree on FIFO eviction) meant homes evicted from the bookkeeping deque could leak past atexit. The fix is to maintain a separate _all_allocated_codex_homes set that tracks every allocation regardless of deque membership; release_codex_home discards from BOTH collections.

Standalone importable function so tests can invoke cleanup directly without depending on atexit timing. ignore_errors makes the function robust to partial interpreter shutdown and already-removed dirs.

Return type:

None

ralph.mcp.transport.codex.prepare_codex_home(endpoint, *, workspace_path, existing_home, system_prompt_file, unsafe_mode=False)[source]

Prepare an isolated Codex home directory and return its path.

Parameters:
  • endpoint (str | None)

  • workspace_path (Path | None)

  • existing_home (str | None)

  • system_prompt_file (str | None)

  • unsafe_mode (bool)

Return type:

str

ralph.mcp.transport.codex.prepare_codex_home_with_upstreams(endpoint, *, workspace_path, existing_home, system_prompt_file, unsafe_mode=False)[source]

Prepare an isolated Codex home directory and return its path with upstream servers.

Parameters:
  • endpoint (str | None)

  • workspace_path (Path | None)

  • existing_home (str | None)

  • system_prompt_file (str | None)

  • unsafe_mode (bool)

Return type:

tuple[str, tuple[UpstreamMcpServer, …]]

ralph.mcp.transport.codex.release_codex_home(codex_home)[source]

Release a single Codex home during the normal runtime lifecycle.

Removes the path from both _all_allocated_codex_homes (so the atexit net will not re-rrmtree it) AND _allocated_codex_homes (the bounded deque). Rmtree’s the on-disk directory with ignore_errors=True so the operation is idempotent.

Returns True if the home was in the bounded deque (active bookkeeping member); False if the home was not in the deque (already released, never registered, or FIFO-evicted before this call). The returned boolean preserves the documented contract used by callers that want to know whether they were the first releaser; an evicted-but-unreleased home returns False even though the on-disk rmtree still happens.

This is the production release path: callers that allocate a Codex home, use it for a bounded operation (e.g. the MCP probe synthesizes a config + runs a handshake, then has no further use for the home), and want to release it BEFORE interpreter shutdown MUST call this function. atexit-only cleanup leaves every allocated home on disk for the entire interpreter lifetime and grows the registry unboundedly across a long run.

Parameters:

codex_home (str)

Return type:

bool

ralph.mcp.transport.common

Shared MCP transport helpers: mcp.toml loading, upstream merging, env serialization.

ralph.mcp.transport.common.mcp_config_as_upstreams(mcp_config)[source]

Convert loaded MCP config into Ralph custom upstream server records.

Parameters:

mcp_config (McpConfig)

Return type:

tuple[UpstreamMcpServer, …]

ralph.mcp.transport.common.mcp_toml_as_upstreams(workspace_path)[source]

Load .agent/mcp.toml and return the configured upstream MCP servers.

Parameters:

workspace_path (Path | None)

Return type:

tuple[UpstreamMcpServer, …]

ralph.mcp.transport.common.merge_existing_upstreams(agent_name, current_config, *, unsafe_mode, workspace_path=None)[source]

Merge existing upstream servers into current_config based on agent and unsafe_mode.

This helper consolidates the unsafe_mode merge logic across the 4 JSON-based transport files (claude, agy, nanocoder, opencode) into one dispatcher.

When unsafe_mode=False: returns only the ralph entry (existing upstreams dropped). When unsafe_mode=True: merges existing agent-native servers with the ralph entry.

Codex uses TOML-style mcp_servers.X keys and is handled separately to preserve the native TOML structure and all per-entry fields.

Parameters:
  • agent_name (str) – One of “claude”, “agy”, “nanocoder”, “opencode”, “codex”.

  • current_config (dict[str, object]) – Agent-native config dict. - claude/agy/nanocoder: {“mcpServers”: {“<name>”: {…}}} - opencode: {“mcp”: {“<name>”: {“type”, “url”, “enabled”, “timeout”, …}}} - codex: {“mcp_servers.X”: {…}, …} (TOML-style keys)

  • unsafe_mode (bool) – Whether to preserve existing upstream servers.

  • workspace_path (object) – Optional workspace path for workspace-level config files.

Returns:

Merged config dict with ralph entry and optionally existing upstreams.

Return type:

dict[str, object]

ralph.mcp.transport.common.merge_mcp_toml_into_upstreams(agent_native, mcp_toml_servers)[source]

Merge mcp.toml servers into agent-native upstreams, preferring mcp.toml on conflict.

Parameters:
Return type:

tuple[UpstreamMcpServer, …]

ralph.mcp.transport.common.set_upstream_mcp_config(runtime_env, upstreams)[source]

Inject upstream MCP config into the runtime environment dict.

Parameters:
Return type:

None

ralph.mcp.transport.cursor

Cursor Agent CLI transport helpers.

This module provides Cursor-specific MCP transport helpers.

Research-confirmed facts (Cursor Agent CLI agent):

  • Executable: agent (binary name on PATH)

  • Headless flag: --print with --output-format stream-json

  • Autonomy flag: --yolo (or --auto-review when configured)

  • MCP config path: workspace .cursor/mcp.json AND user-global ~/.cursor/mcp.json (Cursor may prefer one over the other depending on cwd; writing both ensures MCP is wired for any invocation pattern)

  • HTTP JSON key: url (Cursor’s documented MCP server shape)

  • Output format: NDJSON stream-json (parsed by CursorParser)

Cursor’s MCP server configuration uses the standard MCP convention:

{
    "mcpServers": {
        "ralph": {
            "url": "http://127.0.0.1:<port>/mcp"
        }
    }
}

Ralph reads existing Cursor upstream servers from the workspace-local .cursor/mcp.json and the user-global ~/.cursor/mcp.json files, merges the run-scoped ralph entry through the existing upstream merge flow (merge_existing_upstreams), and writes the merged config to BOTH paths so the agent picks up MCP regardless of the cwd it was launched from.

The write/restore protocol mirrors the AGY pattern in ralph/mcp/transport/agy.py: a process-local threading.Lock serialises concurrent cursors, an atomic Path.replace keeps the write torn-write-safe, and the original-bytes restore happens INSIDE the critical section so a parallel sibling cannot interleave its own write/restore between our read and our restore.

ralph.mcp.transport.cursor.cursor_mcp_config(endpoint)[source]

Return the Cursor MCP JSON config string pointing to the given endpoint.

Parameters:

endpoint (str) – The MCP server HTTP endpoint URL.

Returns:

JSON string with mcpServers containing the Ralph entry with the url key (Cursor’s documented MCP server shape).

Return type:

str

ralph.mcp.transport.cursor.cursor_workspace_mcp_endpoint(workspace_path, endpoint, *, unsafe_mode=False)[source]

Write a run-scoped Ralph MCP config to Cursor’s paths and restore them on exit.

Writes the merged config (Ralph entry + merged upstream servers in unsafe_mode) to BOTH the workspace-local .cursor/mcp.json and the user-global ~/.cursor/mcp.json so a Cursor invocation launched from inside or outside the workspace picks up the run-scoped Ralph MCP endpoint. On exit the original bytes are restored on each path that was modified.

Concurrency safety: this context manager serialises concurrent callers with a single threading.Lock (process-local) and writes the merged config atomically (via Path.replace) so a parallel Cursor session cannot observe a torn write or clobber a sibling session’s restore step. The original-bytes read happens INSIDE the critical section so a parallel sibling cannot interleave its own write/restore between our read and our restore.

Parameters:
  • workspace_path (Path)

  • endpoint (str)

  • unsafe_mode (bool)

Return type:

Iterator[None]

ralph.mcp.transport.cursor.load_existing_cursor_upstream_servers(workspace_path=None)[source]

Read Cursor’s MCP config files and return any upstream MCP servers found.

Parameters:

workspace_path (Path | None) – Optional workspace path for the workspace-local .cursor/mcp.json.

Returns:

Tuple of UpstreamMcpServer objects found in Cursor config files. The Ralph entry is filtered out so it does not collide with the run-scoped ralph injection.

Return type:

tuple[UpstreamMcpServer, …]

ralph.mcp.transport.opencode

OpenCode-specific MCP transport helpers.

ralph.mcp.transport.opencode.build_opencode_provider_config(existing, endpoint, *, unsafe_mode=False)[source]

Build a full OpenCode config JSON with Ralph MCP and return it with upstream servers.

Parameters:
  • existing (str | None)

  • endpoint (str)

  • unsafe_mode (bool)

Return type:

tuple[str, tuple[UpstreamMcpServer, …]]

ralph.mcp.transport.opencode.merge_opencode_config_content(existing, endpoint)[source]

Merge Ralph MCP endpoint into an existing OpenCode config and return JSON.

Parameters:
  • existing (str | None)

  • endpoint (str)

Return type:

str

ralph.mcp.transport.nanocoder

Nanocoder-specific MCP transport helpers.

ralph.mcp.transport.nanocoder.build_nanocoder_mcp_config(existing, endpoint, *, always_allow=(), unsafe_mode=False, workspace_path=None, env=None)[source]

Build a Nanocoder MCP payload with Ralph injected as the managed server.

Parameters:
  • existing (str | None)

  • endpoint (str)

  • always_allow (tuple[str, ...])

  • unsafe_mode (bool)

  • workspace_path (Path | None)

  • env (Mapping[str, str] | None)

Return type:

tuple[str, tuple[UpstreamMcpServer, …]]

ralph.mcp.transport.nanocoder.load_existing_nanocoder_upstream_servers(workspace_path, *, env=None)[source]

Load Nanocoder MCP servers from documented config locations.

Parameters:
  • workspace_path (Path | None)

  • env (dict[str, str] | None)

Return type:

tuple[UpstreamMcpServer, …]

ralph.mcp.transport.pi

Pi-specific MCP transport helpers.

ralph.mcp.transport.pi.pi_mcp_extension_path(workspace_path)[source]

Return the deterministic per-workspace Pi MCP extension path.

Parameters:

workspace_path (Path)

Return type:

Path

ralph.mcp.transport.pi.write_pi_mcp_extension(endpoint, *, workspace_path)[source]

Write a Pi extension that bridges Pi custom tools to Ralph’s MCP server.

Parameters:
  • endpoint (str)

  • workspace_path (Path | None)

Return type:

tuple[Path, Callable[[], None] | None]

ralph.mcp.transport.agy

Google Anti Gravity (AGY) transport helpers.

This module provides AGY-specific MCP transport helpers.

Research-confirmed facts: - Executable: agy - Print flag: –print - Yolo flag: –dangerously-skip-permissions - MCP config path: ~/.gemini/antigravity-cli/mcp_config.json - HTTP JSON key: serverUrl - Output format: plain text (not NDJSON) - uses JsonParserType.GENERIC

Ralph reads existing AGY upstream servers from the user config files at ~/.gemini/antigravity-cli/mcp_config.json and workspace .agents/mcp_config.json. The agy_mcp_config() helper builds the AGY-native JSON payload for Ralph’s MCP endpoint using AGY’s serverUrl field.

ralph.mcp.transport.agy.agy_mcp_config(endpoint)[source]

Return the AGY MCP JSON config string pointing to the given endpoint.

Parameters:

endpoint (str) – The MCP server HTTP endpoint URL.

Returns:

JSON string with mcpServers containing the Ralph entry with serverUrl key.

Return type:

str

ralph.mcp.transport.agy.agy_workspace_mcp_endpoint(workspace_path, endpoint, *, unsafe_mode=False)[source]

Write a run-scoped Ralph MCP config to AGY’s global path and restore it after exit.

Concurrency safety: this context manager serialises concurrent callers with a single threading.Lock and writes the merged config atomically (via os.replace) so a parallel AGY session cannot observe a torn write or clobber a sibling session’s restore step. The lock is process-local: it serialises within one Ralph process but does not block a separate AGY launch invoked by another process. Cross-process safety relies on the atomic replace below and on the original-bytes read happening INSIDE the critical section (so a parallel sibling cannot interleave its own write/restore between our read and our restore).

Parameters:
  • workspace_path (Path)

  • endpoint (str)

  • unsafe_mode (bool)

Return type:

Iterator[None]

ralph.mcp.transport.agy.load_existing_agy_upstream_servers(workspace_path=None)[source]

Read AGY’s MCP config files and return any upstream MCP servers found.

Parameters:

workspace_path (Path | None) – Optional workspace path for workspace-level AGY config.

Returns:

Tuple of UpstreamMcpServer objects found in AGY config files.

Return type:

tuple[UpstreamMcpServer, …]

ralph.mcp.upstream

Upstream MCP client and validation.

This sub-package handles Ralph’s outbound MCP traffic: Ralph acts as an MCP client when talking to user-defined upstream servers in mcp.toml. Contains the HTTP/stdio client, server registry, validation handshake, and per-agent transport probe.

ralph.mcp.upstream.agent_probe

Probe per-agent MCP wiring against validated upstream servers.

After ralph.mcp.upstream.validation has confirmed that each upstream MCP server is reachable from Ralph, this module synthesizes the agent-specific config payload Ralph would emit for Claude/Codex/OpenCode/AGY and re-runs the same MCP handshake to confirm the wire is shaped correctly.

The probe is self-contained: it never spawns the agent binaries themselves. The MCP JSON-RPC handshake is identical across the supported agents so Ralph’s own client is a faithful reference implementation.

class ralph.mcp.upstream.agent_probe.AgentProbeReport(transport, server_name, ok, error=None, note=None)[source]

Bases: object

Result of probing one (transport, upstream server) combination.

Parameters:
  • transport (AgentTransport)

  • server_name (str)

  • ok (bool)

  • error (str | None)

  • note (str | None)

exception ralph.mcp.upstream.agent_probe.AgentTransportProbeError[source]

Bases: RuntimeError

Raised when the synthesized agent config payload is malformed.

ralph.mcp.upstream.agent_probe.probe_agent_transports(servers, *, transports=(AgentTransport.CLAUDE, AgentTransport.CLAUDE_INTERACTIVE, AgentTransport.CODEX, AgentTransport.OPENCODE, AgentTransport.AGY), workspace_path=None, timeout=None)[source]

Confirm Ralph’s per-agent MCP wiring reaches each server.

Parameters:
  • servers (Iterable[UpstreamMcpServer]) – Iterable of validated upstream servers.

  • transports (Iterable[AgentTransport]) – Agent transports to probe. Defaults to all supported.

  • workspace_path (Path | None) – Optional workspace path used by Codex prep helpers.

  • timeout (timedelta | None) – Reserved; subprocess and HTTP probes use the per-call timeout configured via RALPH_MCP_PREFLIGHT_TIMEOUT_MS.

Returns:

One report per (transport, server) pair.

Return type:

tuple[AgentProbeReport, …]

ralph.mcp.upstream.client

HTTP and stdio clients for proxying calls to upstream MCP servers.

Provides HttpUpstreamClient and StdioUpstreamClient, both implementing UpstreamMcpClient. make_upstream_client selects the right implementation from the server’s transport field. Internal helpers handle JSON-RPC framing, legacy SSE endpoints, and multimodal content-block normalization.

Multimodal normalization is done at the registry level via normalize_upstream_content_blocks(), not inside individual clients.

class ralph.mcp.upstream.client.HasMediaManifest(*args, **kwargs)[source]

Bases: Protocol

Protocol for upstream clients that expose a media artifact manifest.

class ralph.mcp.upstream.client.HttpUpstreamClient(server, *, caller=None)[source]

Bases: object

Upstream MCP client that communicates over HTTP JSON-RPC.

Parameters:
class ralph.mcp.upstream.client.StdioUpstreamClient(server, *, caller=None)[source]

Bases: object

Upstream MCP client that communicates over stdio with a subprocess.

Parameters:
exception ralph.mcp.upstream.client.UpstreamCallError[source]

Bases: Exception

Raised when a remote tool call or upstream server reachability check fails.

class ralph.mcp.upstream.client.UpstreamMcpClient(*args, **kwargs)[source]

Bases: Protocol

Protocol satisfied by both HTTP and stdio upstream MCP client implementations.

ralph.mcp.upstream.client.make_upstream_client(server, *, caller=None)[source]

Instantiate the appropriate upstream client for the server’s transport.

Parameters:
Return type:

HttpUpstreamClient | StdioUpstreamClient

ralph.mcp.upstream.client.normalize_upstream_content_blocks(result, server_name, tool_name, session=None, workspace=None)[source]

Normalize upstream tool result content blocks into the multimodal contract.

  • text blocks: pass through unchanged.

  • resource_reference blocks: pass through unchanged.

  • image/audio/video/pdf/document blocks: normalized to resource_reference. URI-backed blocks preserve the upstream URI; embedded-data blocks store bytes in the session manifest (ralph://media/… URI) when available.

  • Other types: raise UpstreamCallError with a clear explanation.

Modifies the result dict in place.

Parameters:
Return type:

None

ralph.mcp.upstream.config

Transport-neutral upstream MCP config normalization helpers.

class ralph.mcp.upstream.config.UpstreamMcpServer(name, transport, url=None, command=None, args=(), env=<factory>, origin='agent_upstream')[source]

Bases: object

Normalized upstream MCP server definition for Ralph runtime use.

Parameters:
  • name (str)

  • transport (Literal['http', 'stdio'])

  • url (str | None)

  • command (str | None)

  • args (tuple[str, ...])

  • env (dict[str, str])

  • origin (Literal['custom', 'agent_upstream'])

ralph.mcp.upstream.config.load_upstream_mcp_servers(raw)[source]

Decode upstream MCP servers from their serialized environment payload.

Parameters:

raw (str | None)

Return type:

tuple[UpstreamMcpServer, …]

ralph.mcp.upstream.config.load_upstream_tool_catalog(raw)[source]

Decode upstream tool metadata from its serialized environment payload.

Parameters:

raw (str | None)

Return type:

dict[str, list[UpstreamTool]]

ralph.mcp.upstream.config.normalize_upstream_mcp_servers(server_entries)[source]

Normalize provider-specific MCP server maps into Ralph runtime definitions.

Parameters:

server_entries (Mapping[str, object])

Return type:

tuple[UpstreamMcpServer, …]

ralph.mcp.upstream.config.serialize_upstream_mcp_servers(servers)[source]

Serialize normalized upstream servers for process environment transport.

Parameters:

servers (Iterable[UpstreamMcpServer])

Return type:

str

ralph.mcp.upstream.config.serialize_upstream_tool_catalog(tool_catalog)[source]

Serialize discovered upstream tool metadata for process environment transport.

Parameters:

tool_catalog (Mapping[str, Iterable[UpstreamTool]])

Return type:

str

ralph.mcp.upstream.models

Data models shared across the upstream MCP client subsystem.

Contains UpstreamTool (description of a single tool advertised by an upstream server) and UpstreamCallError (raised when a remote tool call or server reachability check fails).

exception ralph.mcp.upstream.models.UpstreamCallError[source]

Bases: Exception

Raised when a remote tool call or upstream server reachability check fails.

class ralph.mcp.upstream.models.UpstreamTool(name, description, input_schema=<factory>)[source]

Bases: object

A tool advertised by an upstream MCP server.

Parameters:
  • name (str)

  • description (str)

  • input_schema (dict[str, object])

ralph.mcp.upstream.registry

Registry that aggregates tools from multiple upstream MCP servers.

UpstreamRegistry is built from a list of configured UpstreamMcpServer entries; it contacts each server, collects its tool list, assigns stable alias names via upstream_proxy_tool_name, and exposes tool_definitions and call_tool for use by the MCP bridge. Alias collisions raise RegistryCollisionError immediately.

class ralph.mcp.upstream.registry.ProxiedTool(alias, server_name, tool)[source]

Bases: object

A single upstream tool mapped to a stable proxy alias.

Parameters:
exception ralph.mcp.upstream.registry.RegistryCollisionError[source]

Bases: ValueError

Raised when two upstream servers produce the same proxy alias for a tool.

class ralph.mcp.upstream.registry.UpstreamRegistry(proxied_tools, clients)[source]

Bases: object

Aggregates tools from multiple upstream MCP servers under stable proxy aliases.

Parameters:
  • proxied_tools (list[ProxiedTool])

  • clients (dict[str, _AnyUpstreamClient])

classmethod build_from_tool_catalog(servers, tool_catalog, *, client_factory=None)[source]

Build a registry from pre-discovered tools without probing upstreams.

Parameters:
Return type:

UpstreamRegistry

ralph.mcp.upstream.tool_catalog_cache

Workspace-scoped cache of validated upstream tool catalogs.

ralph.mcp.upstream.tool_catalog_cache.apply_tool_catalog_env(runtime_env, catalog)[source]

Materialize the upstream tool catalog into runtime environment variables.

Parameters:
  • runtime_env (dict[str, str])

  • catalog (dict[str, list[UpstreamTool]])

Return type:

None

ralph.mcp.upstream.tool_catalog_cache.cache_tool_catalog(workspace_root, catalog)[source]

Store a copy of the validated upstream tool catalog for one workspace.

Parameters:
  • workspace_root (Path | None)

  • catalog (dict[str, list[UpstreamTool]])

Return type:

None

ralph.mcp.upstream.tool_catalog_cache.clear_tool_catalog(workspace_root)[source]

Drop any cached upstream tool catalog associated with one workspace.

Parameters:

workspace_root (Path | None)

Return type:

None

ralph.mcp.upstream.tool_catalog_cache.collect_tool_catalog(servers)[source]

Probe configured upstream servers and return their advertised tool catalogs.

Parameters:

servers (Iterable[UpstreamMcpServer])

Return type:

dict[str, list[UpstreamTool]]

ralph.mcp.upstream.tool_catalog_cache.get_tool_catalog(workspace_root)[source]

Return a defensive copy of the cached tool catalog for one workspace.

Parameters:

workspace_root (Path | None)

Return type:

dict[str, list[UpstreamTool]]

ralph.mcp.upstream.validation

Startup validation for user-defined upstream MCP servers.

Ralph fails fast if any custom MCP server cannot complete the standard initializenotifications/initializedtools/list handshake. Set RALPH_MCP_STRICT=0 to fall back to the legacy warn-and-skip behaviour for CI smoke runs.

class ralph.mcp.upstream.validation.UpstreamServerReport(name, transport, ok, tool_count=0, error=None, secret_keys=<factory>)[source]

Bases: object

Validation result for a single upstream MCP server.

Parameters:
  • name (str)

  • transport (Literal['http', 'stdio'])

  • ok (bool)

  • tool_count (int)

  • error (str | None)

  • secret_keys (tuple[str, ...])

exception ralph.mcp.upstream.validation.UpstreamValidationError[source]

Bases: RuntimeError

Raised in strict mode when one or more upstream MCP servers fail validation.

class ralph.mcp.upstream.validation.UpstreamValidationReport(servers)[source]

Bases: object

Aggregated validation results for all configured upstream MCP servers.

Parameters:

servers (tuple[UpstreamServerReport, ...])

ralph.mcp.upstream.validation.strict_mode_from_env(env=None)[source]

Return True when strict mode is active (the default).

Parameters:

env (Mapping[str, str] | None)

Return type:

bool

ralph.mcp.upstream.validation.validate_upstream_mcp_servers(servers, *, timeout=None, strict=None, preflight_http=<function preflight_http_mcp_server_tools>, list_stdio_tools=None)[source]

Validate every configured upstream MCP server at startup.

Parameters:
  • servers (Iterable[UpstreamMcpServer]) – Iterable of normalized upstream MCP server definitions.

  • timeout (timedelta | None) – Optional preflight timeout. Defaults to mcp_preflight_timeout_from_env() (30s, tunable via RALPH_MCP_PREFLIGHT_TIMEOUT_MS).

  • strict (bool | None) – Override strict-mode autodetection. If unset, reads RALPH_MCP_STRICT from the environment.

  • preflight_http (HttpPreflightFn) – Injection point for the HTTP preflight helper. Tests override this to drive the validator without touching the network.

  • list_stdio_tools (Callable[[UpstreamMcpServer, timedelta], list[str]] | None) – Injection point for the stdio probe. Defaults to _list_stdio_tools(), which spawns the configured command through StdioUpstreamClient.

Returns:

UpstreamValidationReport with one entry per server. In soft mode failures are reported with ok=False and a warning is logged per failure. In strict mode an UpstreamValidationError is raised after all servers are inspected so the diagnostic listing names every problem at once.

Return type:

UpstreamValidationReport

ralph.mcp.webvisit

Web visit capability: single-page URL fetch and readable text extraction.

This package implements the visit_url MCP tool that agents use to fetch and read web pages. It fetches a single URL over HTTP/HTTPS, extracts readable text from the HTML, and returns the content as Markdown-like plain text.

Main entry points:

  • ralph.mcp.webvisit.fetcher — HTTP fetch layer; sends the request, follows redirects, and enforces the allowed-scheme/SSRF guard.

  • ralph.mcp.webvisit.extractor — HTML-to-text extraction; strips scripts, styles, and navigation boilerplate and returns readable body text.

This package is a pure back-end implementation; the MCP tool registration lives in ralph.mcp.tools.webvisit. It does not support multi-page crawling; for that, see the upstream Crawl4AI/Firecrawl configuration described in the Local Web Access docs.

ralph.mcp.webvisit.extractor

HTML text extraction for the visit_url tool.

Uses readability-lxml for main-content isolation and selectolax for fast plain-text rendering. Both dependencies are included in the default ralph-workflow installation.

class ralph.mcp.webvisit.extractor.ExtractedPage(title, text, links)[source]

Bases: object

Result of extracting readable content from an HTML page.

Parameters:
  • title (str | None)

  • text (str)

  • links (tuple[str, ...])

ralph.mcp.webvisit.extractor.extract_readable(html, *, base_url, with_links)[source]

Extract readable text and optional links from raw HTML.

Parameters:
  • html (str)

  • base_url (str | None)

  • with_links (bool)

Return type:

ExtractedPage

ralph.mcp.webvisit.fetcher

HTTP fetch layer for the visit_url tool.

Performs a single HTTP GET with SSRF-guard, size cap, and timeout enforcement. No network IO should escape this module in production code paths.

class ralph.mcp.webvisit.fetcher.FetchOutcome(status, effective_url=None, http_status=None, content_type=None, body=None, error=None)[source]

Bases: object

Result of a single HTTP fetch attempt.

Parameters:
  • status (Literal['ok', 'timeout', 'unreachable', 'http_error', 'unsupported_content', 'too_large', 'blocked_by_policy', 'invalid_url'])

  • effective_url (str | None)

  • http_status (int | None)

  • content_type (str | None)

  • body (bytes | None)

  • error (str | None)

ralph.mcp.webvisit.fetcher.fetch_url(url, *, timeout_ms, max_bytes, user_agent, allow_private_networks)[source]

Fetch a single URL and return a FetchOutcome.

Never raises on network failures  always returns a FetchOutcome.

Parameters:
  • url (str)

  • timeout_ms (int)

  • max_bytes (int)

  • user_agent (str)

  • allow_private_networks (bool)

Return type:

FetchOutcome

ralph.mcp.websearch

Web-search backends and helpers for the MCP web_search tool.

ralph.mcp.websearch.backends

Concrete web-search backend implementations.

ralph.mcp.websearch.backends.base

Shared web-search backend abstractions.

class ralph.mcp.websearch.backends.base.SearchResult(title, url, snippet)[source]

Bases: object

Normalized search result shape shared by all backends.

Parameters:
  • title (str)

  • url (str)

  • snippet (str)

class ralph.mcp.websearch.backends.base.WebSearchBackend(*args, **kwargs)[source]

Bases: Protocol

Protocol implemented by concrete web-search backends.

exception ralph.mcp.websearch.backends.base.WebSearchError[source]

Bases: RuntimeError

Raised when a web-search backend fails.

ralph.mcp.websearch.backends.brave

Brave Search web-search backend.

class ralph.mcp.websearch.backends.brave.BraveBackend(api_key=None, api_key_env=None, url='https://api.search.brave.com/res/v1/web/search', timeout_seconds=None)[source]

Bases: object

Backend powered by Brave Search’s HTTP API.

Parameters:
  • api_key (str | None)

  • api_key_env (str | None)

  • url (str)

  • timeout_seconds (float | None)

ralph.mcp.websearch.backends.ddgs

DuckDuckGo Search backend implementation.

class ralph.mcp.websearch.backends.ddgs.DdgsBackend(*, timeout_seconds=None)[source]

Bases: object

In-process default backend backed by the ddgs package.

Parameters:

timeout_seconds (float | None)

property timeout_seconds: float | None

Return the per-call timeout, or None to inherit the central default.

ralph.mcp.websearch.backends.exa

Exa web-search backend.

Implements the ExaBackend dataclass that wraps the exa-py Python SDK to deliver web-search results via the Exa API. Requires pip install ralph-workflow[web-search] (or pip install exa-py) at runtime; importing this module without the SDK installed is safe, but calling search raises WebSearchError.

API key resolution:

  • Pass api_key directly, or

  • set api_key_env to an environment variable name that holds the key (resolved via ralph.mcp.websearch.secrets.resolve_secret).

Typical usage (from ralph.config.mcp_models backend selection):

backend = ExaBackend(api_key_env="EXA_API_KEY")
results = backend.search("async Python tutorial", limit=5)
class ralph.mcp.websearch.backends.exa.ExaBackend(api_key=None, api_key_env=None, timeout_seconds=None)[source]

Bases: object

Backend powered by the Exa Python SDK.

Parameters:
  • api_key (str | None)

  • api_key_env (str | None)

  • timeout_seconds (float | None)

ralph.mcp.websearch.backends.searxng

SearXNG web-search backend.

Implements the SearxngBackend dataclass that queries a self-hosted SearXNG instance over HTTP. Unlike the API-key backends (Exa, Tavily, Brave), this backend requires no credentials — only the base URL of a running SearXNG server.

The backend POSTs to {url}/search?format=json with a 10-second timeout and normalises the JSON response into a list of SearchResult objects. Network errors and non-200 responses raise WebSearchError.

Typical usage (from ralph.config.mcp_models backend selection):

backend = SearxngBackend(url="http://localhost:8080")
results = backend.search("Python type hints", limit=5)
class ralph.mcp.websearch.backends.searxng.SearxngBackend(url, timeout_seconds=None)[source]

Bases: object

Backend that queries a user-managed SearXNG instance.

Parameters:
  • url (str)

  • timeout_seconds (float | None)

ralph.mcp.websearch.backends.tavily

Tavily web-search backend.

Implements the TavilyBackend dataclass that wraps the tavily-python SDK to deliver web-search results via the Tavily API. Requires pip install ralph-workflow[web-search] (or pip install tavily-python) at runtime; importing without the SDK is safe, but calling search raises WebSearchError.

API key resolution:

  • Pass api_key directly, or

  • set api_key_env to an environment variable name that holds the key (resolved via ralph.mcp.websearch.secrets.resolve_secret).

Typical usage (from ralph.config.mcp_models backend selection):

backend = TavilyBackend(api_key_env="TAVILY_API_KEY")
results = backend.search("FastAPI dependency injection", limit=5)
class ralph.mcp.websearch.backends.tavily.TavilyBackend(api_key=None, api_key_env=None, timeout_seconds=None)[source]

Bases: object

Backend powered by the Tavily Python SDK.

Parameters:
  • api_key (str | None)

  • api_key_env (str | None)

  • timeout_seconds (float | None)

ralph.mcp.websearch.secrets

Secret resolution helpers for web-search backends.

ralph.mcp.websearch.secrets.resolve_secret(api_key, api_key_env, *, getenv=<function getenv>)[source]

Resolve a backend secret from either an inline value or an env var name.

Parameters:
  • api_key (str | None)

  • api_key_env (str | None)

  • getenv (EnvGetter)

Return type:

str

Git

ralph.git

Public Git helpers for Ralph.

This package exposes the repository operations used by the Python orchestrator, including staging/commit helpers, managed hook installation, and rebase support. Implementation uses GitPython rather than the retired Rust/libgit2 stack.

class ralph.git.GitHelpers[source]

Bases: object

State carrier for one agent-phase git-protection sequence.

GitHelpers is the lightweight bundle start_agent_phase() and end_agent_phase() populate as they enable and later roll back the agent-phase git protections. The two functions use it to share a view of the runtime state (which directory is the Ralph-managed hooks dir, which repository root the protections belong to, and which git binary is the genuine system one) without recomputing it on every call.

real_git

Path to the genuine git executable on the host. Set by callers when the Ralph wrapper redirects git to a sandbox binary; None when the host has no wrapper in front of git. The wrapper scripts use this to invoke the real binary once the agent-phase protections are restored.

Type:

pathlib.Path | None

wrapper_dir

Path to the per-repository .git/ralph directory that holds the agent-phase marker, HEAD OID snapshot, and the Ralph-managed hooks directory. Set by start_agent_phase() and read by end_agent_phase() during teardown. None before start_agent_phase() populates it.

Type:

pathlib.Path | None

wrapper_repo_root

Path to the repository root the protections were enabled for. None before start_agent_phase() populates it; end_agent_phase() asserts the value matches the repo being torn down so a mismatched call fails fast instead of editing the wrong repository’s .git.

Type:

pathlib.Path | None

Lifecycle:
  1. Construct (or reuse) a GitHelpers. Optional — the phase helpers create one for you.

  2. Call start_agent_phase(repo_root, helpers)(); it sets wrapper_repo_root and wrapper_dir and writes the marker / HEAD OID / hooks-path snapshot.

  3. Run the agent phase (commit/push attempts are blocked by the marker file the hook scripts check).

  4. Call end_agent_phase(repo_root, helpers)(); it restores core.hooksPath, deletes the marker / snapshot / track files, and leaves wrapper_dir populated only until the next start/end cycle.

Invariants:
  • The class is a plain data carrier with no locking; threads that enable protections concurrently must serialize around the instance externally.

  • All attributes are intentionally typed Path | None because none of them carry meaning outside of a paired start_agent_phase / end_agent_phase cycle.

exception ralph.git.GitOperationError(operation, message)[source]

Bases: Exception

Raised when a git operation fails.

Parameters:
  • operation (str)

  • message (str)

Return type:

None

operation

Name of the operation that failed.

message

Error message describing the failure.

class ralph.git.GitRunResult(args, returncode, stdout, stderr)[source]

Bases: object

Result of a git subprocess invocation.

Parameters:
  • args (tuple[str, ...])

  • returncode (int)

  • stdout (str)

  • stderr (str)

ralph.git.append_to_gitignore(repo_root, patterns)[source]

Append patterns to .gitignore.

Parameters:
  • repo_root (Path | str) – Path to the repository root.

  • patterns (list[str]) – List of patterns to add.

Return type:

None

ralph.git.create_commit(repo_root, message, author_name=None, author_email=None)[source]

Create a git commit.

Parameters:
  • repo_root (Path | str) – Path to the repository root.

  • message (str) – Commit message.

  • author_name (str | None) – Optional author name override.

  • author_email (str | None) – Optional author email override.

Returns:

SHA of the created commit.

Raises:

GitOperationError – If commit fails.

Return type:

str

ralph.git.detect_unauthorized_commit(repo_root)[source]

Return True if the HEAD OID no longer matches the stored baseline.

Compares the repository’s current HEAD against the OID start_agent_phase() snapshotted into <repo>/.git/ralph/head-oid.txt. A mismatch indicates that an agent phase wrote a commit despite the protection hooks — a condition the supervisor should treat as a security violation and surface to the user before any further work continues.

The function is read-only: it does not modify the repository, delete the snapshot file, or invoke the hooks. Callers that want a single boolean answer for a check-and-act flow should call this function and decide the policy themselves; a follow-up end_agent_phase() will still roll back the protections regardless of the return value.

Parameters:

repo_root (Path | str) – Path to the repository root to inspect. Accepts a pathlib.Path or a string; the value is resolved against the GitPython Repo constructor.

Returns:

True when a stored snapshot exists and the current HEAD OID differs from it (unauthorized commit detected); False when no snapshot exists, the snapshot is empty, the current HEAD cannot be read, or HEAD still matches the snapshot.

Return type:

bool

Side effects:
  • Closes the GitPython Repo it opened for the duration of the call.

  • Does NOT mutate any file under <repo>/.git/ralph/ and does NOT invoke the hooks scripts.

Raises:

git.exc.GitCommandError – Re-raised only when the underlying git invocation fails for a reason other than a missing HEAD (detached/unborn HEAD is reported as False, not raised).

Parameters:

repo_root (Path | str)

Return type:

bool

See also

start_agent_phase() writes the snapshot this function compares against. end_agent_phase() removes it.

ralph.git.end_agent_phase(repo_root, helpers=None)[source]

Remove agent-phase protections and restore git state.

Reverses every change made by start_agent_phase(): restores the previous core.hooksPath value, deletes the Ralph-managed marker / HEAD-OID / track files under <repo>/.git/ralph/, and closes the GitPython Repo opened for the duration of the call. After this function returns, the repository is in the same git state it was in before the matching start_agent_phase() call.

Parameters:
  • repo_root (Path | str) – Path to the repository root whose protections should be rolled back. Must match the repo_root passed to the matching start_agent_phase() call so teardown targets the same repository. Accepts a pathlib.Path or a string.

  • helpers (GitHelpers | None) – Optional GitHelpers carrier populated by the matching start_agent_phase() call. When None, a fresh carrier is constructed and wrapper_repo_root is set to Path(repo_root) so teardown can locate the .git/ralph directory written during setup.

Returns:

None. The function mutates the repository’s local core.hooksPath and deletes the marker / snapshot files written during setup.

Return type:

None

Side effects:
  • Restores the previous core.hooksPath from the snapshot file (or clears it if none was set).

  • Deletes the marker, HEAD OID, and track files inside <repo>/.git/ralph/.

  • Closes the GitPython Repo it opened for the duration of the call.

Raises:

git.exc.GitCommandError – If any underlying git invocation fails (filesystem permission, missing snapshot file, or a held lock). Each subprocess is bounded by GIT_SUBPROCESS_TIMEOUT_SECONDS so a stuck lock cannot hang an agent-phase teardown.

Parameters:
  • repo_root (Path | str)

  • helpers (GitHelpers | None)

Return type:

None

See also

start_agent_phase() installs the protections this function rolls back. detect_unauthorized_commit() reports whether HEAD advanced during a protected phase, which this function neither inspects nor clears.

ralph.git.find_repo_root(start=PosixPath('.'))[source]

Locate git repo root from start path.

Parameters:

start (Path | str) – Starting path for the search.

Returns:

Path to the repository root.

Raises:

GitOperationError – If not inside a git repository.

Return type:

Path

ralph.git.get_head_sha(repo_root)[source]

Return current HEAD commit SHA.

Parameters:

repo_root (Path | str) – Path to the repository root.

Returns:

SHA of the current HEAD commit.

Return type:

str

ralph.git.get_hooks_dir(repo_root=None)[source]

Return the git hooks directory for a repository root.

Parameters:

repo_root (Path | str | None)

Return type:

Path

ralph.git.get_staged_files(repo_root)[source]

Get list of staged files.

Parameters:

repo_root (Path | str) – Path to the repository root.

Returns:

List of staged file paths. Untracked-only worktrees return [].

Return type:

list[str]

ralph.git.has_staged_changes(repo_root)[source]

Check if repository has staged changes.

Parameters:

repo_root (Path | str) – Path to the repository root.

Returns:

True if there are staged changes. Untracked-only worktrees return False – this helper is the staged-only contract used by commit to decide whether a commit can proceed.

Return type:

bool

ralph.git.install_hooks(repo_root=None)

Install every Ralph-managed git hook into repo_root.

The function walks RALPH_HOOK_NAMES, ensures the repository’s .git/hooks directory and the .git/ralph marker directory exist, and writes a generated hook script for each managed hook. The generated scripts first block any commit/push/merge when an agent-phase marker file is present, then delegate to the original hook script (backed up alongside as <hook_name>.ralph.orig) when the marker is absent.

The function is the writer half of the install_hooks_in_repo() / uninstall_hooks() pair; together they give Ralph a way to gate host-driven agent actions without permanently modifying the repository.

Parameters:

repo_root (Path | str | None) – Filesystem path or string path of the git repository root whose hooks should be installed. When None the repository is discovered via ralph.git.operations.find_repo_root() from the current working directory. A relative path is resolved as-is.

Returns:

The absolute path to the repository’s .git/hooks directory, so callers can inspect the installed hooks or attach additional bookkeeping.

Return type:

Path

Raises:

OSError – If the host filesystem refuses to create the hook files (e.g. permission errors on a read-only mount). The error surfaces unchanged so callers can decide whether to abort the bootstrap or fall back to operator-supplied hooks.

Side Effects:
  • Creates <repo>/.git/hooks and <repo>/.git/ralph if they do not already exist.

  • Touches the no_agent_commit and git-wrapper-dir.txt marker files under <repo>/.git/ralph so subsequent agent phases can detect Ralph-managed installations.

  • Backs up any existing non-Ralph hook script as <hook_name>.ralph.orig before replacing it, so uninstall restores the user’s original hook behavior.

  • Writes a Ralph-generated hook script for every name in RALPH_HOOK_NAMES. The marker HOOK_MARKER is embedded in each script so reinstall_hooks_if_tampered() can later detect drift.

Trust Boundary:

The function executes only filesystem writes inside the target repository’s .git tree; it does not invoke any external commands or read user-supplied data. Callers must ensure repo_root resolves to a trusted repository before passing it in (a hostile caller could otherwise induce the script to write marker files in an attacker-chosen directory).

ralph.git.install_hooks_in_repo(repo_root=None)[source]

Install every Ralph-managed git hook into repo_root.

The function walks RALPH_HOOK_NAMES, ensures the repository’s .git/hooks directory and the .git/ralph marker directory exist, and writes a generated hook script for each managed hook. The generated scripts first block any commit/push/merge when an agent-phase marker file is present, then delegate to the original hook script (backed up alongside as <hook_name>.ralph.orig) when the marker is absent.

The function is the writer half of the install_hooks_in_repo() / uninstall_hooks() pair; together they give Ralph a way to gate host-driven agent actions without permanently modifying the repository.

Parameters:

repo_root (Path | str | None) – Filesystem path or string path of the git repository root whose hooks should be installed. When None the repository is discovered via ralph.git.operations.find_repo_root() from the current working directory. A relative path is resolved as-is.

Returns:

The absolute path to the repository’s .git/hooks directory, so callers can inspect the installed hooks or attach additional bookkeeping.

Return type:

Path

Raises:

OSError – If the host filesystem refuses to create the hook files (e.g. permission errors on a read-only mount). The error surfaces unchanged so callers can decide whether to abort the bootstrap or fall back to operator-supplied hooks.

Side Effects:
  • Creates <repo>/.git/hooks and <repo>/.git/ralph if they do not already exist.

  • Touches the no_agent_commit and git-wrapper-dir.txt marker files under <repo>/.git/ralph so subsequent agent phases can detect Ralph-managed installations.

  • Backs up any existing non-Ralph hook script as <hook_name>.ralph.orig before replacing it, so uninstall restores the user’s original hook behavior.

  • Writes a Ralph-generated hook script for every name in RALPH_HOOK_NAMES. The marker HOOK_MARKER is embedded in each script so reinstall_hooks_if_tampered() can later detect drift.

Trust Boundary:

The function executes only filesystem writes inside the target repository’s .git tree; it does not invoke any external commands or read user-supplied data. Callers must ensure repo_root resolves to a trusted repository before passing it in (a hostile caller could otherwise induce the script to write marker files in an attacker-chosen directory).

ralph.git.is_repo_clean(repo_root)[source]

Check if repository has uncommitted changes.

Parameters:

repo_root (Path | str) – Path to the repository root.

Returns:

True if repository is clean (no uncommitted changes).

Return type:

bool

ralph.git.merge_base(repo_root, ref_a, ref_b)[source]

Return merge-base SHA between two refs.

Parameters:
  • repo_root (Path | str) – Path to the repository root.

  • ref_a (str) – First ref (branch, tag, SHA).

  • ref_b (str) – Second ref (branch, tag, SHA).

Returns:

SHA of the merge base commit.

Raises:

GitOperationError – If merge base cannot be determined.

Return type:

str

ralph.git.push(repo_root, remote='origin', branch=None)[source]

Push current branch to remote.

Parameters:
  • repo_root (Path | str) – Path to the repository root.

  • remote (str) – Remote name to push to.

  • branch (str | None) – Optional branch name override.

Raises:

GitOperationError – If push fails.

Return type:

None

ralph.git.reinstall_hooks_if_tampered(*, logger=None, repo_root=None)[source]

Reinstall hooks when they are missing or do not contain the marker.

Parameters:
  • logger (Logger | None)

  • repo_root (Path | str | None)

Return type:

bool

ralph.git.run_git(args, *, cwd, label, options=None)[source]

Spawn a git subprocess through ProcessManager and return the result.

When options.phase is provided the process label becomes phase:<phase>:git:<label> so process_phase_scope can terminate it.

Raises subprocess.TimeoutExpired if timeout is exceeded. Raises subprocess.CalledProcessError if options.check is True and returncode != 0.

Parameters:
  • args (Sequence[str])

  • cwd (Path | None)

  • label (str)

  • options (GitRunOptions | None)

Return type:

GitRunResult

ralph.git.stage_all(repo_root)[source]

Stage all changes (git add -A).

Parameters:

repo_root (Path | str) – Path to the repository root.

Return type:

None

ralph.git.start_agent_phase(repo_root, helpers=None)[source]

Enable git protections for an agent phase.

Installs the Ralph-managed git hooks that block agent commits for the remainder of the current phase. The function writes a marker file and the current HEAD OID under <repo>/.git/ralph/, snapshots the previous core.hooksPath value, and repoints core.hooksPath at the Ralph-managed hooks directory. Subsequent agent invocations that attempt to commit or push are rejected by the hooks.

This function is the public entry point for enabling the protection scheme and is paired with end_agent_phase(), which rolls back the changes. Together they bracket one agent phase; callers that skip end_agent_phase() leave the repository in a state that still blocks commits.

Parameters:
  • repo_root (Path | str) – Path to the repository root whose protections should be enabled. Accepts a pathlib.Path or a string; the value is resolved against the GitPython Repo constructor.

  • helpers (GitHelpers | None) – Optional pre-built GitHelpers carrier. When None, a fresh carrier is constructed and populated with wrapper_repo_root and wrapper_dir. The same carrier (or one with identical population) must be passed to the matching end_agent_phase() call so teardown targets the right repository.

Returns:

None. The function mutates the repository’s .git/ralph directory, the local core.hooksPath config, and the supplied GitHelpers carrier.

Return type:

None

Side effects:
  • Creates <repo>/.git/ralph/ if absent.

  • Writes the marker, HEAD OID, and track files inside it.

  • Snapshots and overwrites the local core.hooksPath.

  • Closes the GitPython Repo it opened for the duration of the call (callers must not reuse the original handle).

Raises:

git.exc.GitCommandError – If any underlying git invocation fails (filesystem permission, missing .git, or a held lock). Each subprocess is bounded by GIT_SUBPROCESS_TIMEOUT_SECONDS so a stuck lock cannot hang an agent-phase setup.

Parameters:
  • repo_root (Path | str)

  • helpers (GitHelpers | None)

Return type:

None

See also

end_agent_phase() rolls back the protections this function installs. detect_unauthorized_commit() reports whether HEAD advanced during a protected phase.

ralph.git.uninstall_hooks(*, logger=None, repo_root=None)[source]

Remove Ralph-managed hooks from repo_root and restore backups.

Walks RALPH_HOOK_NAMES and, for any hook that still carries the HOOK_MARKER written by install_hooks_in_repo(), restores the original script from the <hook_name>.ralph.orig backup (when one exists) and removes the backup. When no backup exists the hook file is deleted outright, leaving the slot empty so subsequent pre-commit / pre-push invocations no longer fire at all.

The function is the reverse half of the install_hooks_in_repo() / uninstall_hooks() pair. It only removes scripts that carry the Ralph marker, so it is safe to call on a repository whose hooks have already been replaced by another tool: foreign hooks are detected by the absence of the marker and left untouched.

Keyword Arguments:
  • logger – Optional loguru.Logger instance used for the “Uninstalled N Ralph hook(s)” / “No Ralph-managed hooks were found to uninstall” summary lines. Defaults to the process logger when None, which is the right choice for normal CLI invocations.

  • repo_root – Filesystem path or string path of the git repository root whose hooks should be removed. When None the repository is discovered via ralph.git.operations.find_repo_root() from the current working directory.

Returns:

True when at least one Ralph-managed hook was removed or restored; False when no managed hooks were found and the repository was already clean. The return value is intended for CLI-level reporting and is not load-bearing for the rest of the Ralph pipeline.

Return type:

bool

Raises:

OSError – If the host filesystem refuses to move or delete the hook files. The error surfaces unchanged so the caller can decide whether to retry with elevated permissions or surface the failure to the operator.

Parameters:
  • logger (Logger | None)

  • repo_root (Path | str | None)

Side Effects:
  • For every managed hook with a .ralph.orig backup, the backup is moved back into place as the active hook script and the backup file is removed.

  • For every managed hook without a backup, the hook file is deleted (leaving the slot empty rather than leaving a stub Ralph script).

  • Emits one logger.info line summarizing how many hooks were removed.

Trust Boundary:

The function only writes inside the target repository’s .git tree and never invokes external commands. Callers must ensure repo_root resolves to a trusted repository so that an attacker cannot trick the uninstall routine into deleting unrelated git hooks on a different host.

ralph.git.hooks

Manage Ralph git hooks for the Python CLI.

ralph.git.hooks.HOOK_MARKER = 'RALPH_RUST_MANAGED_HOOK'

Marker string embedded in every Ralph-managed hook.

ralph.git.hooks.RALPH_HOOK_NAMES = ('pre-commit', 'pre-push', 'pre-merge-commit', 'commit-msg')

Hook names managed by Ralph workflows.

ralph.git.hooks.get_hooks_dir(repo_root=None)[source]

Return the git hooks directory for a repository root.

Parameters:

repo_root (Path | str | None)

Return type:

Path

ralph.git.hooks.install_hooks(repo_root=None)

Install every Ralph-managed git hook into repo_root.

The function walks RALPH_HOOK_NAMES, ensures the repository’s .git/hooks directory and the .git/ralph marker directory exist, and writes a generated hook script for each managed hook. The generated scripts first block any commit/push/merge when an agent-phase marker file is present, then delegate to the original hook script (backed up alongside as <hook_name>.ralph.orig) when the marker is absent.

The function is the writer half of the install_hooks_in_repo() / uninstall_hooks() pair; together they give Ralph a way to gate host-driven agent actions without permanently modifying the repository.

Parameters:

repo_root (Path | str | None) – Filesystem path or string path of the git repository root whose hooks should be installed. When None the repository is discovered via ralph.git.operations.find_repo_root() from the current working directory. A relative path is resolved as-is.

Returns:

The absolute path to the repository’s .git/hooks directory, so callers can inspect the installed hooks or attach additional bookkeeping.

Return type:

Path

Raises:

OSError – If the host filesystem refuses to create the hook files (e.g. permission errors on a read-only mount). The error surfaces unchanged so callers can decide whether to abort the bootstrap or fall back to operator-supplied hooks.

Side Effects:
  • Creates <repo>/.git/hooks and <repo>/.git/ralph if they do not already exist.

  • Touches the no_agent_commit and git-wrapper-dir.txt marker files under <repo>/.git/ralph so subsequent agent phases can detect Ralph-managed installations.

  • Backs up any existing non-Ralph hook script as <hook_name>.ralph.orig before replacing it, so uninstall restores the user’s original hook behavior.

  • Writes a Ralph-generated hook script for every name in RALPH_HOOK_NAMES. The marker HOOK_MARKER is embedded in each script so reinstall_hooks_if_tampered() can later detect drift.

Trust Boundary:

The function executes only filesystem writes inside the target repository’s .git tree; it does not invoke any external commands or read user-supplied data. Callers must ensure repo_root resolves to a trusted repository before passing it in (a hostile caller could otherwise induce the script to write marker files in an attacker-chosen directory).

ralph.git.hooks.install_hooks_in_repo(repo_root=None)[source]

Install every Ralph-managed git hook into repo_root.

The function walks RALPH_HOOK_NAMES, ensures the repository’s .git/hooks directory and the .git/ralph marker directory exist, and writes a generated hook script for each managed hook. The generated scripts first block any commit/push/merge when an agent-phase marker file is present, then delegate to the original hook script (backed up alongside as <hook_name>.ralph.orig) when the marker is absent.

The function is the writer half of the install_hooks_in_repo() / uninstall_hooks() pair; together they give Ralph a way to gate host-driven agent actions without permanently modifying the repository.

Parameters:

repo_root (Path | str | None) – Filesystem path or string path of the git repository root whose hooks should be installed. When None the repository is discovered via ralph.git.operations.find_repo_root() from the current working directory. A relative path is resolved as-is.

Returns:

The absolute path to the repository’s .git/hooks directory, so callers can inspect the installed hooks or attach additional bookkeeping.

Return type:

Path

Raises:

OSError – If the host filesystem refuses to create the hook files (e.g. permission errors on a read-only mount). The error surfaces unchanged so callers can decide whether to abort the bootstrap or fall back to operator-supplied hooks.

Side Effects:
  • Creates <repo>/.git/hooks and <repo>/.git/ralph if they do not already exist.

  • Touches the no_agent_commit and git-wrapper-dir.txt marker files under <repo>/.git/ralph so subsequent agent phases can detect Ralph-managed installations.

  • Backs up any existing non-Ralph hook script as <hook_name>.ralph.orig before replacing it, so uninstall restores the user’s original hook behavior.

  • Writes a Ralph-generated hook script for every name in RALPH_HOOK_NAMES. The marker HOOK_MARKER is embedded in each script so reinstall_hooks_if_tampered() can later detect drift.

Trust Boundary:

The function executes only filesystem writes inside the target repository’s .git tree; it does not invoke any external commands or read user-supplied data. Callers must ensure repo_root resolves to a trusted repository before passing it in (a hostile caller could otherwise induce the script to write marker files in an attacker-chosen directory).

ralph.git.hooks.reinstall_hooks_if_tampered(*, logger=None, repo_root=None)[source]

Reinstall hooks when they are missing or do not contain the marker.

Parameters:
  • logger (Logger | None)

  • repo_root (Path | str | None)

Return type:

bool

ralph.git.hooks.uninstall_hooks(*, logger=None, repo_root=None)[source]

Remove Ralph-managed hooks from repo_root and restore backups.

Walks RALPH_HOOK_NAMES and, for any hook that still carries the HOOK_MARKER written by install_hooks_in_repo(), restores the original script from the <hook_name>.ralph.orig backup (when one exists) and removes the backup. When no backup exists the hook file is deleted outright, leaving the slot empty so subsequent pre-commit / pre-push invocations no longer fire at all.

The function is the reverse half of the install_hooks_in_repo() / uninstall_hooks() pair. It only removes scripts that carry the Ralph marker, so it is safe to call on a repository whose hooks have already been replaced by another tool: foreign hooks are detected by the absence of the marker and left untouched.

Keyword Arguments:
  • logger – Optional loguru.Logger instance used for the “Uninstalled N Ralph hook(s)” / “No Ralph-managed hooks were found to uninstall” summary lines. Defaults to the process logger when None, which is the right choice for normal CLI invocations.

  • repo_root – Filesystem path or string path of the git repository root whose hooks should be removed. When None the repository is discovered via ralph.git.operations.find_repo_root() from the current working directory.

Returns:

True when at least one Ralph-managed hook was removed or restored; False when no managed hooks were found and the repository was already clean. The return value is intended for CLI-level reporting and is not load-bearing for the rest of the Ralph pipeline.

Return type:

bool

Raises:

OSError – If the host filesystem refuses to move or delete the hook files. The error surfaces unchanged so the caller can decide whether to retry with elevated permissions or surface the failure to the operator.

Parameters:
  • logger (Logger | None)

  • repo_root (Path | str | None)

Side Effects:
  • For every managed hook with a .ralph.orig backup, the backup is moved back into place as the active hook script and the backup file is removed.

  • For every managed hook without a backup, the hook file is deleted (leaving the slot empty rather than leaving a stub Ralph script).

  • Emits one logger.info line summarizing how many hooks were removed.

Trust Boundary:

The function only writes inside the target repository’s .git tree and never invokes external commands. Callers must ensure repo_root resolves to a trusted repository so that an attacker cannot trick the uninstall routine into deleting unrelated git hooks on a different host.

ralph.git.operations

Git operations for ralph pipeline via GitPython.

This module provides a high-level interface for git operations, wrapping GitPython to provide the functionality needed by the pipeline.

exception ralph.git.operations.GitOperationError(operation, message)[source]

Bases: Exception

Raised when a git operation fails.

Parameters:
  • operation (str)

  • message (str)

Return type:

None

operation

Name of the operation that failed.

message

Error message describing the failure.

ralph.git.operations.append_to_gitignore(repo_root, patterns)[source]

Append patterns to .gitignore.

Parameters:
  • repo_root (Path | str) – Path to the repository root.

  • patterns (list[str]) – List of patterns to add.

Return type:

None

ralph.git.operations.create_commit(repo_root, message, author_name=None, author_email=None)[source]

Create a git commit.

Parameters:
  • repo_root (Path | str) – Path to the repository root.

  • message (str) – Commit message.

  • author_name (str | None) – Optional author name override.

  • author_email (str | None) – Optional author email override.

Returns:

SHA of the created commit.

Raises:

GitOperationError – If commit fails.

Return type:

str

ralph.git.operations.find_main_worktree_root(start=PosixPath('.'))[source]

Find the primary worktree root for the current repository.

For linked worktrees, this resolves to the main checkout that owns the shared git common directory. For ordinary repositories, it matches the active repository root.

This helper detects linked git worktrees only as a workspace-root resolver and is NEVER used by the same-workspace parallel worker path. Parallel v1 workers always share the canonical repo_root; this function MUST NOT be invoked by ralph.pipeline.parallel.* modules. Callers in that package violate the same-workspace isolation contract.

Parameters:

start (Path | str)

Return type:

Path

ralph.git.operations.find_repo_root(start=PosixPath('.'))[source]

Locate git repo root from start path.

Parameters:

start (Path | str) – Starting path for the search.

Returns:

Path to the repository root.

Raises:

GitOperationError – If not inside a git repository.

Return type:

Path

ralph.git.operations.get_commits_between(repo_root, from_ref, to_ref)[source]

Get list of commit SHAs between two refs.

Parameters:
  • repo_root (Path | str) – Path to the repository root.

  • from_ref (str) – Starting ref (exclusive).

  • to_ref (str) – Ending ref (inclusive).

Returns:

List of commit SHAs in reverse chronological order.

Return type:

list[str]

ralph.git.operations.get_current_branch(repo_root)[source]

Get the current branch name.

Parameters:

repo_root (Path | str) – Path to the repository root.

Returns:

Name of the current branch.

Return type:

str

ralph.git.operations.get_head_sha(repo_root)[source]

Return current HEAD commit SHA.

Parameters:

repo_root (Path | str) – Path to the repository root.

Returns:

SHA of the current HEAD commit.

Return type:

str

ralph.git.operations.get_staged_files(repo_root)[source]

Get list of staged files.

Parameters:

repo_root (Path | str) – Path to the repository root.

Returns:

List of staged file paths. Untracked-only worktrees return [].

Return type:

list[str]

ralph.git.operations.has_commits_since(repo_root, baseline_sha)[source]

Return True when HEAD is ahead of baseline_sha.

When baseline_sha is None the caller has no prior baseline (first run), so we conservatively return True to allow the caller to proceed.

Parameters:
  • repo_root (Path | str)

  • baseline_sha (str | None)

Return type:

bool

ralph.git.operations.has_staged_changes(repo_root)[source]

Check if repository has staged changes.

Parameters:

repo_root (Path | str) – Path to the repository root.

Returns:

True if there are staged changes. Untracked-only worktrees return False – this helper is the staged-only contract used by commit to decide whether a commit can proceed.

Return type:

bool

ralph.git.operations.has_uncommitted_changes(repo_root)[source]

Return True when the working tree has uncommitted work.

Includes staged diff, unstaged diff, and untracked files. This is the authoritative skip check for commit phases: if this returns False, there is literally nothing for a commit agent to package up.

Parameters:

repo_root (Path | str)

Return type:

bool

ralph.git.operations.is_repo_clean(repo_root)[source]

Check if repository has uncommitted changes.

Parameters:

repo_root (Path | str) – Path to the repository root.

Returns:

True if repository is clean (no uncommitted changes).

Return type:

bool

ralph.git.operations.list_changed_paths(repo_root)[source]

Return unique changed paths from git status --porcelain in output order.

Parameters:

repo_root (Path | str)

Return type:

list[str]

ralph.git.operations.merge_base(repo_root, ref_a, ref_b)[source]

Return merge-base SHA between two refs.

Parameters:
  • repo_root (Path | str) – Path to the repository root.

  • ref_a (str) – First ref (branch, tag, SHA).

  • ref_b (str) – Second ref (branch, tag, SHA).

Returns:

SHA of the merge base commit.

Raises:

GitOperationError – If merge base cannot be determined.

Return type:

str

ralph.git.operations.push(repo_root, remote='origin', branch=None)[source]

Push current branch to remote.

Parameters:
  • repo_root (Path | str) – Path to the repository root.

  • remote (str) – Remote name to push to.

  • branch (str | None) – Optional branch name override.

Raises:

GitOperationError – If push fails.

Return type:

None

ralph.git.operations.stage_all(repo_root)[source]

Stage all changes (git add -A).

Parameters:

repo_root (Path | str) – Path to the repository root.

Return type:

None

ralph.git.operations.stage_files(repo_root, files)[source]

Stage only the provided repository-relative paths.

Uses git add --all -- <paths> so modified, untracked, and deleted files are all handled consistently for the selected scope.

Parameters:
  • repo_root (Path | str)

  • files (list[str])

Return type:

None

ralph.git.rebase

Rebase-specific helpers for git operations.

ralph.git.rebase.rebase

Core git rebase helpers (abort/continue/rebase).

class ralph.git.rebase.rebase.ProcessExecutor(*args, **kwargs)[source]

Bases: Protocol

Executor that runs external processes.

class ralph.git.rebase.rebase.ProcessResult(returncode, stdout, stderr)[source]

Bases: object

Represents the result of running a git subprocess.

Parameters:
  • returncode (int)

  • stdout (str)

  • stderr (str)

class ralph.git.rebase.rebase.RebaseConflicts(files)[source]

Bases: object

Rebase stopped because conflicts remain.

Parameters:

files (list[str])

class ralph.git.rebase.rebase.RebaseFailed(kind)[source]

Bases: object

Rebase failed with a specific error kind.

Parameters:

kind (RebaseErrorKind)

class ralph.git.rebase.rebase.RebaseNoOp(reason)[source]

Bases: object

Rebase was not applicable (already up-to-date or invalid state).

Parameters:

reason (str)

exception ralph.git.rebase.rebase.RebaseOperationError[source]

Bases: Exception

Raised when a rebase operation fails.

class ralph.git.rebase.rebase.RebaseSuccess[source]

Bases: object

Rebase completed successfully.

class ralph.git.rebase.rebase.SubprocessExecutor[source]

Bases: object

Default executor powered by run_git.

ralph.git.rebase.rebase.abort_rebase(*, repo_root=None, executor=None)[source]

Abort an in-progress rebase.

Parameters:
Return type:

None

ralph.git.rebase.rebase.continue_rebase(*, repo_root=None, executor=None)[source]

Continue an in-progress rebase after conflicts have been resolved.

Parameters:
Return type:

None

ralph.git.rebase.rebase.get_conflicted_files(*, repo_root=None, executor=None)[source]

List files that are currently marked as conflicted in the index.

Parameters:
Return type:

list[str]

ralph.git.rebase.rebase.rebase_in_progress(repo_root=None)[source]

Return True when a rebase directory exists in the git repo.

Parameters:

repo_root (Path | str | None)

Return type:

bool

ralph.git.rebase.rebase.rebase_onto(upstream_branch, *, repo_root=None, executor=None)[source]

Rebase the current branch on top of the provided upstream branch.

Parameters:
  • upstream_branch (str)

  • repo_root (Path | str | None)

  • executor (ProcessExecutor | None)

Return type:

RebaseSuccess | RebaseConflicts | RebaseNoOp | RebaseFailed

ralph.git.rebase.rebase_checkpoint

Rebase checkpoint persistence and locking utilities.

class ralph.git.rebase.rebase_checkpoint.RebaseCheckpoint(phase=<factory>, upstream_branch='', conflicted_files=<factory>, resolved_files=<factory>, error_count=0, last_error=None, timestamp=<factory>, phase_error_count=0)[source]

Bases: object

Persisted state for a rebase operation, written to .agent/rebase_checkpoint.json.

Parameters:
  • phase (RebasePhase)

  • upstream_branch (str)

  • conflicted_files (list[str])

  • resolved_files (list[str])

  • error_count (int)

  • last_error (str | None)

  • timestamp (str)

  • phase_error_count (int)

ralph.git.rebase.rebase_checkpoint.acquire_rebase_lock()[source]

Acquire the rebase lock file, raising OSError if another process holds it.

Return type:

None

ralph.git.rebase.rebase_checkpoint.clear_rebase_checkpoint()[source]

Delete the rebase checkpoint file if it exists.

Return type:

None

ralph.git.rebase.rebase_checkpoint.load_rebase_checkpoint()[source]

Load and validate the rebase checkpoint, falling back to backup on error.

Return type:

RebaseCheckpoint | None

ralph.git.rebase.rebase_checkpoint.rebase_checkpoint_exists()[source]

Return True if a rebase checkpoint file exists on disk.

Return type:

bool

ralph.git.rebase.rebase_checkpoint.release_rebase_lock()[source]

Release the rebase lock file if it exists.

Return type:

None

ralph.git.rebase.rebase_checkpoint.restore_from_backup()[source]

Attempt to restore a valid checkpoint from the backup file.

Return type:

RebaseCheckpoint | None

ralph.git.rebase.rebase_checkpoint.save_rebase_checkpoint(checkpoint)[source]

Atomically persist checkpoint to the agent rebase checkpoint file.

Parameters:

checkpoint (RebaseCheckpoint)

Return type:

None

ralph.git.rebase.rebase_continuation

Helpers for continuing paused Git rebases.

exception ralph.git.rebase.rebase_continuation.ConflictRemainingError[source]

Bases: RebaseContinuationError

Raised when conflicts remain while attempting to continue.

exception ralph.git.rebase.rebase_continuation.NoRebaseInProgressError[source]

Bases: RebaseContinuationError

Raised when no rebase is active but continuation was requested.

exception ralph.git.rebase.rebase_continuation.RebaseContinuationError[source]

Bases: Exception

Base exception for rebase continuation helpers.

exception ralph.git.rebase.rebase_continuation.RebaseVerificationError[source]

Bases: Exception

Raised when verifying rebase completion fails.

ralph.git.rebase.rebase_continuation.continue_rebase(repo_root=None)[source]

Resume a paused rebase, auto-detecting the repo root.

Parameters:

repo_root (Path | str | None)

Return type:

None

ralph.git.rebase.rebase_continuation.continue_rebase_at(repo_root)[source]

Resume a paused rebase at repo_root, raising if conflicts remain.

Parameters:

repo_root (Path | str)

Return type:

None

ralph.git.rebase.rebase_continuation.rebase_in_progress(repo_root=None)[source]

Return True if a git rebase is in progress, auto-detecting the repo root.

Parameters:

repo_root (Path | str | None)

Return type:

bool

ralph.git.rebase.rebase_continuation.rebase_in_progress_at(repo_root)[source]

Return True if a git rebase is currently in progress at repo_root.

Parameters:

repo_root (Path | str)

Return type:

bool

ralph.git.rebase.rebase_continuation.verify_rebase_completed(upstream_branch, repo_root=None)[source]

Verify rebase completion, auto-detecting the repo root.

Parameters:
  • upstream_branch (str)

  • repo_root (Path | str | None)

Return type:

bool

ralph.git.rebase.rebase_continuation.verify_rebase_completed_at(repo_root, upstream_branch)[source]

Return True if the rebase is complete and HEAD is a descendant of upstream_branch.

Parameters:
  • repo_root (Path | str)

  • upstream_branch (str)

Return type:

bool

ralph.git.rebase.rebase_kinds

Classification helpers for Git rebase outcomes.

class ralph.git.rebase.rebase_kinds.RebaseErrorKind(kind, metadata=<factory>)[source]

Bases: object

Payload for a classified rebase failure.

Parameters:
  • kind (RebaseKind)

  • metadata (Mapping[str, object])

class ralph.git.rebase.rebase_kinds.RebaseKind(*values)[source]

Bases: Enum

Enum describing every supported rebase failure mode.

ralph.git.rebase.rebase_kinds.classify_rebase_error(stderr, stdout)[source]

Translate git rebase stderr/stdout into a concrete rebase failure kind.

Parameters:
  • stderr (str)

  • stdout (str)

Return type:

RebaseErrorKind

ralph.git.rebase.rebase_preconditions

Precondition validation before performing a git rebase.

exception ralph.git.rebase.rebase_preconditions.RebasePreconditionError[source]

Bases: Exception

Raised when a rebase cannot start because a precondition failed.

ralph.git.rebase.rebase_preconditions.check_rebase_preconditions(repo_root)[source]

Ensure the git repository is ready to start a rebase.

Parameters:

repo_root (Path | str) – Path to the git repository.

Raises:

RebasePreconditionError – When the repository is not ready to rebase.

Return type:

None

ralph.git.rebase.rebase_state_machine

High-level rebase state machine for Python agents.

exception ralph.git.rebase.rebase_state_machine.InvalidTransitionError[source]

Bases: Exception

Raised when an event is invalid in the current state.

class ralph.git.rebase.rebase_state_machine.RebaseCheckpoint(phase=<factory>, upstream_branch='', conflicted_files=<factory>, resolved_files=<factory>, error_count=0, last_error=None, timestamp=<factory>, phase_error_count=0)[source]

Bases: object

Persisted state for a rebase operation, written to .agent/rebase_checkpoint.json.

Parameters:
  • phase (RebasePhase)

  • upstream_branch (str)

  • conflicted_files (list[str])

  • resolved_files (list[str])

  • error_count (int)

  • last_error (str | None)

  • timestamp (str)

  • phase_error_count (int)

class ralph.git.rebase.rebase_state_machine.RebaseEvent(*values)[source]

Bases: Enum

Events that drive transitions in the RebaseStateMachine.

class ralph.git.rebase.rebase_state_machine.RebaseLock[source]

Bases: object

Context manager that acquires and releases the rebase lock.

class ralph.git.rebase.rebase_state_machine.RebasePhase(*values)[source]

Bases: StrEnum

Lifecycle phase of an in-progress rebase operation.

class ralph.git.rebase.rebase_state_machine.RebaseStateMachine(checkpoint, *, persist=True, max_recovery_attempts=3)[source]

Bases: object

State machine that coordinates rebase lifecycle via RebaseCheckpoint.

Parameters:
class ralph.git.rebase.rebase_state_machine.RecoveryAction(*values)[source]

Bases: Enum

Decision returned by decide to guide error recovery in a rebase.

ralph.git.rebase.rebase_state_machine.acquire_rebase_lock()[source]

Acquire the rebase lock file, raising OSError if another process holds it.

Return type:

None

ralph.git.rebase.rebase_state_machine.clear_rebase_checkpoint()[source]

Delete the rebase checkpoint file if it exists.

Return type:

None

ralph.git.rebase.rebase_state_machine.load_rebase_checkpoint()[source]

Load and validate the rebase checkpoint, falling back to backup on error.

Return type:

RebaseCheckpoint | None

ralph.git.rebase.rebase_state_machine.rebase_checkpoint_exists()[source]

Return True if a rebase checkpoint file exists on disk.

Return type:

bool

ralph.git.rebase.rebase_state_machine.release_rebase_lock()[source]

Release the rebase lock file if it exists.

Return type:

None

ralph.git.rebase.rebase_state_machine.restore_from_backup()[source]

Attempt to restore a valid checkpoint from the backup file.

Return type:

RebaseCheckpoint | None

ralph.git.rebase.rebase_state_machine.save_rebase_checkpoint(checkpoint)[source]

Atomically persist checkpoint to the agent rebase checkpoint file.

Parameters:

checkpoint (RebaseCheckpoint)

Return type:

None

ralph.git.subprocess_runner

Synchronous git helper backed by ProcessManager.

class ralph.git.subprocess_runner.GitRunOptions(phase=None, timeout=None, env=None, check=False, capture_output=True, text=True, output_limit_bytes=None)[source]

Bases: object

Options for run_git beyond the required args, cwd, and label.

output_limit_bytes: cap on stdout/stderr captured from the git subprocess. None (default) preserves the legacy unbounded behavior. The default when callers do pass a non-None value is the module-level GIT_OUTPUT_LIMIT_BYTES (10 MiB) — matching the existing SPILL_OUTPUT_LIMIT_BYTES precedent at ralph/mcp/tools/_exec_output_spill.py:33 and well above any realistic single-file diff. Outputs exceeding the cap are truncated with a marker (the ManagedProcessOutputLimitExceededError semantics in _communicate_with_output_limit).

Parameters:
  • phase (str | None)

  • timeout (float | None)

  • env (Mapping[str, str] | None)

  • check (bool)

  • capture_output (bool)

  • text (bool)

  • output_limit_bytes (int | None)

ralph.git.subprocess_runner.run_git(args, *, cwd, label, options=None)[source]

Spawn a git subprocess through ProcessManager and return the result.

When options.phase is provided the process label becomes phase:<phase>:git:<label> so process_phase_scope can terminate it.

Raises subprocess.TimeoutExpired if timeout is exceeded. Raises subprocess.CalledProcessError if options.check is True and returncode != 0.

Parameters:
  • args (Sequence[str])

  • cwd (Path | None)

  • label (str)

  • options (GitRunOptions | None)

Return type:

GitRunResult

ralph.git.wrapper

Git wrapper helpers for blocking commits during agent phases.

class ralph.git.wrapper.GitHelpers[source]

Bases: object

State carrier for one agent-phase git-protection sequence.

GitHelpers is the lightweight bundle start_agent_phase() and end_agent_phase() populate as they enable and later roll back the agent-phase git protections. The two functions use it to share a view of the runtime state (which directory is the Ralph-managed hooks dir, which repository root the protections belong to, and which git binary is the genuine system one) without recomputing it on every call.

real_git

Path to the genuine git executable on the host. Set by callers when the Ralph wrapper redirects git to a sandbox binary; None when the host has no wrapper in front of git. The wrapper scripts use this to invoke the real binary once the agent-phase protections are restored.

Type:

pathlib.Path | None

wrapper_dir

Path to the per-repository .git/ralph directory that holds the agent-phase marker, HEAD OID snapshot, and the Ralph-managed hooks directory. Set by start_agent_phase() and read by end_agent_phase() during teardown. None before start_agent_phase() populates it.

Type:

pathlib.Path | None

wrapper_repo_root

Path to the repository root the protections were enabled for. None before start_agent_phase() populates it; end_agent_phase() asserts the value matches the repo being torn down so a mismatched call fails fast instead of editing the wrong repository’s .git.

Type:

pathlib.Path | None

Lifecycle:
  1. Construct (or reuse) a GitHelpers. Optional — the phase helpers create one for you.

  2. Call start_agent_phase(repo_root, helpers)(); it sets wrapper_repo_root and wrapper_dir and writes the marker / HEAD OID / hooks-path snapshot.

  3. Run the agent phase (commit/push attempts are blocked by the marker file the hook scripts check).

  4. Call end_agent_phase(repo_root, helpers)(); it restores core.hooksPath, deletes the marker / snapshot / track files, and leaves wrapper_dir populated only until the next start/end cycle.

Invariants:
  • The class is a plain data carrier with no locking; threads that enable protections concurrently must serialize around the instance externally.

  • All attributes are intentionally typed Path | None because none of them carry meaning outside of a paired start_agent_phase / end_agent_phase cycle.

ralph.git.wrapper.detect_unauthorized_commit(repo_root)[source]

Return True if the HEAD OID no longer matches the stored baseline.

Compares the repository’s current HEAD against the OID start_agent_phase() snapshotted into <repo>/.git/ralph/head-oid.txt. A mismatch indicates that an agent phase wrote a commit despite the protection hooks — a condition the supervisor should treat as a security violation and surface to the user before any further work continues.

The function is read-only: it does not modify the repository, delete the snapshot file, or invoke the hooks. Callers that want a single boolean answer for a check-and-act flow should call this function and decide the policy themselves; a follow-up end_agent_phase() will still roll back the protections regardless of the return value.

Parameters:

repo_root (Path | str) – Path to the repository root to inspect. Accepts a pathlib.Path or a string; the value is resolved against the GitPython Repo constructor.

Returns:

True when a stored snapshot exists and the current HEAD OID differs from it (unauthorized commit detected); False when no snapshot exists, the snapshot is empty, the current HEAD cannot be read, or HEAD still matches the snapshot.

Return type:

bool

Side effects:
  • Closes the GitPython Repo it opened for the duration of the call.

  • Does NOT mutate any file under <repo>/.git/ralph/ and does NOT invoke the hooks scripts.

Raises:

git.exc.GitCommandError – Re-raised only when the underlying git invocation fails for a reason other than a missing HEAD (detached/unborn HEAD is reported as False, not raised).

Parameters:

repo_root (Path | str)

Return type:

bool

See also

start_agent_phase() writes the snapshot this function compares against. end_agent_phase() removes it.

ralph.git.wrapper.end_agent_phase(repo_root, helpers=None)[source]

Remove agent-phase protections and restore git state.

Reverses every change made by start_agent_phase(): restores the previous core.hooksPath value, deletes the Ralph-managed marker / HEAD-OID / track files under <repo>/.git/ralph/, and closes the GitPython Repo opened for the duration of the call. After this function returns, the repository is in the same git state it was in before the matching start_agent_phase() call.

Parameters:
  • repo_root (Path | str) – Path to the repository root whose protections should be rolled back. Must match the repo_root passed to the matching start_agent_phase() call so teardown targets the same repository. Accepts a pathlib.Path or a string.

  • helpers (GitHelpers | None) – Optional GitHelpers carrier populated by the matching start_agent_phase() call. When None, a fresh carrier is constructed and wrapper_repo_root is set to Path(repo_root) so teardown can locate the .git/ralph directory written during setup.

Returns:

None. The function mutates the repository’s local core.hooksPath and deletes the marker / snapshot files written during setup.

Return type:

None

Side effects:
  • Restores the previous core.hooksPath from the snapshot file (or clears it if none was set).

  • Deletes the marker, HEAD OID, and track files inside <repo>/.git/ralph/.

  • Closes the GitPython Repo it opened for the duration of the call.

Raises:

git.exc.GitCommandError – If any underlying git invocation fails (filesystem permission, missing snapshot file, or a held lock). Each subprocess is bounded by GIT_SUBPROCESS_TIMEOUT_SECONDS so a stuck lock cannot hang an agent-phase teardown.

Parameters:
  • repo_root (Path | str)

  • helpers (GitHelpers | None)

Return type:

None

See also

start_agent_phase() installs the protections this function rolls back. detect_unauthorized_commit() reports whether HEAD advanced during a protected phase, which this function neither inspects nor clears.

ralph.git.wrapper.start_agent_phase(repo_root, helpers=None)[source]

Enable git protections for an agent phase.

Installs the Ralph-managed git hooks that block agent commits for the remainder of the current phase. The function writes a marker file and the current HEAD OID under <repo>/.git/ralph/, snapshots the previous core.hooksPath value, and repoints core.hooksPath at the Ralph-managed hooks directory. Subsequent agent invocations that attempt to commit or push are rejected by the hooks.

This function is the public entry point for enabling the protection scheme and is paired with end_agent_phase(), which rolls back the changes. Together they bracket one agent phase; callers that skip end_agent_phase() leave the repository in a state that still blocks commits.

Parameters:
  • repo_root (Path | str) – Path to the repository root whose protections should be enabled. Accepts a pathlib.Path or a string; the value is resolved against the GitPython Repo constructor.

  • helpers (GitHelpers | None) – Optional pre-built GitHelpers carrier. When None, a fresh carrier is constructed and populated with wrapper_repo_root and wrapper_dir. The same carrier (or one with identical population) must be passed to the matching end_agent_phase() call so teardown targets the right repository.

Returns:

None. The function mutates the repository’s .git/ralph directory, the local core.hooksPath config, and the supplied GitHelpers carrier.

Return type:

None

Side effects:
  • Creates <repo>/.git/ralph/ if absent.

  • Writes the marker, HEAD OID, and track files inside it.

  • Snapshots and overwrites the local core.hooksPath.

  • Closes the GitPython Repo it opened for the duration of the call (callers must not reuse the original handle).

Raises:

git.exc.GitCommandError – If any underlying git invocation fails (filesystem permission, missing .git, or a held lock). Each subprocess is bounded by GIT_SUBPROCESS_TIMEOUT_SECONDS so a stuck lock cannot hang an agent-phase setup.

Parameters:
  • repo_root (Path | str)

  • helpers (GitHelpers | None)

Return type:

None

See also

end_agent_phase() rolls back the protections this function installs. detect_unauthorized_commit() reports whether HEAD advanced during a protected phase.

Workspace

ralph.workspace

Filesystem abstraction exports.

Use Workspace as the protocol shared by production and test code, FsWorkspace for real filesystem access, and MemoryWorkspace for tests that need an in-memory implementation.

ralph.workspace.fs

Production filesystem workspace.

This module provides the FsWorkspace implementation that wraps pathlib.Path operations for real filesystem access.

ralph.workspace.memory

In-memory workspace for testing.

This module provides the MemoryWorkspace implementation that stores file contents in memory for test isolation.

class ralph.workspace.memory.MemoryWorkspace(root='/workspace')[source]

Bases: object

In-memory workspace for test isolation.

This workspace stores all file contents in a dictionary, making it suitable for unit testing without filesystem operations.

All paths are normalized to POSIX-style relative paths.

Parameters:

root (str)

absolute_path(path)[source]

Return an absolute-like path string for the workspace.

Parameters:

path (str)

Return type:

str

allowed_roots()[source]

Return the list of allowed workspace root paths.

Returns:

List of string paths from configured allowed roots.

Return type:

list[str]

append(path, content)[source]

Append content to file.

Parameters:
  • path (str) – Relative path to the file.

  • content (str) – Content to append.

Return type:

None

clear()[source]

Clear all stored contents.

Return type:

None

copy(src, dest, *, overwrite=False)[source]

Copy a file or directory.

Parameters:
  • src (str) – Source path.

  • dest (str) – Destination path.

  • overwrite (bool) – Whether to overwrite existing destination.

Raises:

FileExistsError – If dest exists and overwrite is False.

Return type:

None

create_dir(path)[source]

Create a directory.

Parameters:

path (str) – Relative path to the directory.

Return type:

None

delete(path, *, recursive=False)[source]

Delete a file or directory.

Parameters:
  • path (str) – Relative path to delete.

  • recursive (bool) – If True, delete directories recursively.

Raises:

IsADirectoryError – If path is a directory and recursive is False.

Return type:

None

exists(path)[source]

Check if file exists.

Parameters:

path (str) – Relative path to check.

Returns:

True if file exists.

Return type:

bool

is_dir(path)[source]

Check if path is a directory.

Parameters:

path (str) – Relative path to check.

Returns:

True if path is a directory.

Return type:

bool

is_file(path)[source]

Check if path is a file.

Parameters:

path (str) – Relative path to check.

Returns:

True if path is a file.

Return type:

bool

iter_files(base)[source]

Iterate over file paths under a base directory.

Parameters:

base (str) – Base directory path to search under.

Yields:

File paths relative to workspace root, honoring skip patterns.

Return type:

tuple[str, …]

list_dir(path)[source]

List directory contents.

Parameters:

path (str) – Relative path to the directory.

Returns:

List of file/directory names.

Return type:

list[str]

mkdirs(path)[source]

Create a directory and all parent directories.

Parameters:

path (str) – Relative path to the directory to create.

Return type:

None

move(src, dest, *, overwrite=False)[source]

Move a file or directory.

Parameters:
  • src (str) – Source path.

  • dest (str) – Destination path.

  • overwrite (bool) – Whether to overwrite existing destination.

Raises:

FileExistsError – If dest exists and overwrite is False.

Return type:

None

read(path)[source]

Read file contents.

Parameters:

path (str) – Relative path to the file.

Returns:

File contents as string.

Raises:

FileNotFoundError – If file doesn’t exist.

Return type:

str

read_bytes(path, *, offset=0, limit=None)[source]

Read a byte window from a file, decoded as UTF-8.

Parameters:
  • path (str)

  • offset (int)

  • limit (int | None)

Return type:

tuple[str, dict[str, object]]

read_lines(path, *, start=None, end=None, head=None, tail=None)[source]

Read lines from a file with slicing support.

Parameters:
  • path (str) – Relative path to the file.

  • start (int | None) – 1-based line number to start from (inclusive).

  • end (int | None) – 1-based line number to end at (inclusive).

  • head (int | None) – Return only the first N lines.

  • tail (int | None) – Return only the last N lines.

Returns:

Tuple of (text content, metadata dict) where metadata has total_lines, returned_lines, truncated keys.

Raises:
  • ValueError – If conflicting params are supplied.

  • FileNotFoundError – If file doesn’t exist.

Return type:

tuple[str, dict[str, object]]

remove(path)[source]

Remove a file.

Parameters:

path (str) – Relative path to the file.

Return type:

None

stat(path)[source]

Get file metadata/stat data.

Parameters:

path (str) – Relative path to the file.

Returns:

Dict with type (‘file’|’dir’|’missing’), size_bytes, created_unix, modified_unix, mode.

Return type:

dict[str, object]

write(path, content)[source]

Write content to file.

Parameters:
  • path (str) – Relative path to the file.

  • content (str) – Content to write.

Return type:

None

ralph.workspace.protocol

Workspace Protocol for file I/O abstraction.

This module defines the Workspace protocol that enables test doubles and in-memory implementations for testing.

class ralph.workspace.protocol.Workspace(*args, **kwargs)[source]

Bases: Protocol

File I/O abstraction enabling test doubles.

This protocol defines the interface for file system operations. Implementations can be production (FsWorkspace) or test doubles (MemoryWorkspace).

All paths are relative to the workspace root.

absolute_path(path)[source]

Resolve a relative path to its absolute workspace path.

Parameters:

path (str)

Return type:

str

allowed_roots()[source]

Return the list of allowed workspace root paths.

Returns:

List of string paths from configured allowed roots.

Return type:

list[str]

append(path, content)[source]

Append content to file.

Parameters:
  • path (str) – Relative path to the file.

  • content (str) – Content to append.

Return type:

None

copy(src, dest, *, overwrite=False)[source]

Copy a file or directory.

Parameters:
  • src (str) – Source path.

  • dest (str) – Destination path.

  • overwrite (bool) – Whether to overwrite existing destination.

Return type:

None

delete(path, *, recursive=False)[source]

Delete a file or directory.

Parameters:
  • path (str) – Relative path to delete.

  • recursive (bool) – If True, delete directories recursively.

Raises:

IsADirectoryError – If path is a directory and recursive is False.

Return type:

None

exists(path)[source]

Check if file exists.

Parameters:

path (str) – Relative path to check.

Returns:

True if file exists.

Return type:

bool

is_dir(path)[source]

Check if path is a directory.

Parameters:

path (str) – Relative path to check.

Returns:

True if path is a directory.

Return type:

bool

is_file(path)[source]

Check if path is a file.

Parameters:

path (str) – Relative path to check.

Returns:

True if path is a file.

Return type:

bool

iter_files(base)[source]

Iterate over file paths under a base directory.

Parameters:

base (str) – Base directory path to search under.

Yields:

File paths relative to workspace root, honoring skip patterns.

Return type:

tuple[str, …]

list_dir(path)[source]

List directory contents.

Parameters:

path (str) – Relative path to the directory.

Returns:

List of file/directory names in the directory.

Return type:

list[str]

mkdirs(path)[source]

Create a directory and all parent directories.

Parameters:

path (str) – Relative path to the directory to create.

Return type:

None

move(src, dest, *, overwrite=False)[source]

Move a file or directory.

Parameters:
  • src (str) – Source path.

  • dest (str) – Destination path.

  • overwrite (bool) – Whether to overwrite existing destination.

Return type:

None

read(path)[source]

Read file contents.

Parameters:

path (str) – Relative path to the file.

Returns:

File contents as string.

Raises:

FileNotFoundError – If file doesn’t exist.

Return type:

str

read_bytes(path, *, offset=0, limit=None)[source]

Read a byte window from a file, decoded as UTF-8.

Parameters:
  • path (str) – Relative path to the file.

  • offset (int) – 0-based byte offset to start reading from.

  • limit (int | None) – Maximum number of bytes to read (None means read to end).

Returns:

Tuple of (text content, metadata dict) where metadata has total_bytes, returned_bytes, truncated keys.

Raises:
  • FileNotFoundError – If file doesn’t exist.

  • UnicodeDecodeError – If the byte range cannot be decoded as UTF-8.

Return type:

tuple[str, dict[str, object]]

read_lines(path, *, start=None, end=None, head=None, tail=None)[source]

Read lines from a file with slicing support.

Parameters:
  • path (str) – Relative path to the file.

  • start (int | None) – 1-based line number to start from (inclusive).

  • end (int | None) – 1-based line number to end at (inclusive).

  • head (int | None) – Return only the first N lines.

  • tail (int | None) – Return only the last N lines.

Returns:

Tuple of (text content, metadata dict) where metadata has total_lines, returned_lines, truncated keys.

Raises:
  • ValueError – If conflicting params are supplied.

  • FileNotFoundError – If file doesn’t exist.

Return type:

tuple[str, dict[str, object]]

remove(path)[source]

Remove a file.

Parameters:

path (str) – Relative path to the file.

Return type:

None

stat(path)[source]

Get file metadata/stat data.

Parameters:

path (str) – Relative path to the file.

Returns:

Dict with type (‘file’|’dir’|’missing’), size_bytes, created_unix, modified_unix, mode.

Return type:

dict[str, object]

write(path, content)[source]

Write content to file.

Parameters:
  • path (str) – Relative path to the file.

  • content (str) – Content to write.

Return type:

None

ralph.workspace.scope

Canonical workspace scope for the active Ralph run.

Provides WorkspaceScope, the frozen dataclass that centralises all workspace-root and allowed-directory decisions made at process startup. Every component that needs to know where files live or which paths an agent may write should read its values from a WorkspaceScope instance rather than calling Path.cwd() directly.

Key API:

  • resolve_workspace_scope(start) - detect the active workspace from the filesystem. Walks upward from start (default: cwd()) looking for a ralph-workflow.toml config file or a git repo root. Linked worktrees automatically inherit config from the main worktree unless the linked worktree has its own override.

  • WorkspaceScope - frozen dataclass with root, allowed_roots, local_config_path, and propagated_config_paths. Use scope.resolve_agent_file(filename) to locate .agent/ files with correct inheritance between linked and main worktrees.

  • WorkspaceScope.for_same_workspace_worker(...) - builds a restricted scope for parallel workers that share a single checkout; the repo root is NOT added to allowed roots, enforcing that workers only write to their declared directories and their own worker namespace.

Config files searched (in order):

ralph-workflow.toml, agents.toml, pipeline.toml, artifacts.toml, mcp.toml (all under .agent/ in the workspace root).

class ralph.workspace.scope.WorkspaceScope(root, allowed_roots=None, *, local_config_path=None, propagated_config_paths=None)[source]

Bases: object

Single source of truth for workspace root and config inheritance.

Frozen dataclass that centralises every workspace-root and allowed-directory decision made at process startup. Every component that needs to know where files live, which paths an agent may write, or where local and inherited configuration files are read from should consume a WorkspaceScope instance rather than calling pathlib.Path.cwd() directly. The dataclass is hashable and frozen so it can be cached, passed between threads, and used as a dictionary key.

Construction canonicalises every path through _canonicalize() (expanduser + resolve) and deduplicates allowed_roots so callers can pass user-supplied paths and still rely on a stable canonical form.

Parameters:
  • root (Path)

  • allowed_roots (tuple[Path, ...])

  • local_config_path (Path)

  • propagated_config_paths (tuple[Path, ...])

root

Canonical absolute path to the workspace root. All .agent/-relative paths are resolved against this value, and it is the canonical entry point for repository-relative lookups.

Type:

pathlib.Path

allowed_roots

Tuple of canonical absolute paths that agents may read or write to during the run. root is always the first entry. Additional entries are added by the workspace resolver for linked worktrees, parallel worker namespaces, and any directory the active pipeline phase has been granted access to.

Type:

tuple[pathlib.Path, …]

local_config_path

Canonical absolute path to the workspace-local ralph-workflow.toml file. Defaults to <root>/.agent/ralph-workflow.toml when no override is supplied; can be overridden for tests and for workspaces that store configuration outside .agent/.

Type:

pathlib.Path

propagated_config_paths

Tuple of canonical absolute paths to inherited configuration files. The values come from the workspace resolver walking upward from root and collecting any .agent/ralph-workflow.toml, agents.toml, pipeline.toml, artifacts.toml, or mcp.toml it finds. Order is parent-first so the most-specific entry wins on conflict.

Type:

tuple[pathlib.Path, …]

Lifecycle:
  1. Construct (or receive from resolve_workspace_scope()) a WorkspaceScope.

  2. Pass it to every component that needs to locate files (scope.root, scope.local_config_path) or check path containment (root in scope.allowed_roots).

  3. For parallel workers, build a restricted scope with for_same_workspace_worker() instead of mutating the original instance — the dataclass is frozen.

classmethod for_same_workspace_worker(repo_root, allowed_directories, worker_namespace)[source]

Build a worker-scoped view of the shared checkout.

The root stays at repo_root (no per-worker root reassignment). Each allowed directory is resolved relative to repo_root. The worker_namespace is always added so the worker can write its own artifacts, logs, and temporary outputs even when allowed_directories is narrow. A ValueError is raised when any entry escapes repo_root via .. or an absolute path.

This method bypasses the standard __init__ to avoid unconditionally adding repo_root to allowed_roots. Same-workspace workers must NOT have the repo root as an allowed root — they are restricted to only their declared edit areas plus their own worker namespace.

Parameters:
  • repo_root (Path) – Shared repository root (same for all parallel workers).

  • allowed_directories (tuple[str, ...]) – Relative subpaths the worker may edit.

  • worker_namespace (Path) – Per-worker scratch directory (always allowed).

Returns:

WorkspaceScope with root=repo_root, allowed_roots restricted to the declared directories plus the worker namespace (repo root is NOT included).

Return type:

WorkspaceScope

has_any_local_agent_override()[source]

Return True when the current workspace has any explicit .agent override.

Return type:

bool

resolve_agent_file(filename)[source]

Resolve the effective .agent file for this workspace.

Linked worktrees inherit defaults from the main worktree unless the current workspace has an explicit local override for that filename.

Parameters:

filename (str)

Return type:

Path

ralph.workspace.scope.resolve_workspace_scope(start=None)[source]

Resolve the active workspace scope.

The workspace root remains the active checkout, but linked worktrees inherit default .agent config from the main checkout unless the linked worktree has an explicit local override file.

Parameters:

start (Path | str | None)

Return type:

WorkspaceScope

ralph.workspace.skip

Skip patterns for recursive workspace traversal.

Defines RECURSIVE_SKIP_DIRECTORY_NAMES, the canonical frozenset of directory names that must never be recursed into during workspace file discovery or context-window content gathering. Applying this set keeps scans fast and prevents noise from VCS internals, build caches, and vendored package trees.

Currently skipped: .git, .hg, .mypy_cache, .pytest_cache, .ruff_cache, .svn, .venv, __pycache__, node_modules, target.

ralph.workspace.agent_dir_retention

Run-start retention sweep for machine-only .agent bookkeeping.

Long-lived workspaces accumulate one completion_seen_<run_id>.json per agent session, one receipts/<run_id>/ directory per run, and agent_retry_* scratch per retry — hundreds of files over multi-day runs. Nothing reads them after their run ends. The sweep deletes entries older than max_age_seconds (default 7 days), always keeping the current run’s entries regardless of age.

Everything here is best-effort: a failed unlink is skipped, never raised, so a permission quirk cannot break run startup. The DB prune (RFC-013 P3) is invoked with the same best-effort contract.

ralph.workspace.agent_dir_retention.sweep_agent_dir(workspace_root, *, keep_run_id, max_age_seconds=604800.0, now=<built-in function time>)[source]

Delete aged machine-only bookkeeping under <workspace>/.agent.

The file-glob sweep covers completion_seen_*.json, receipts/, and tmp/agent_retry_*.md. When .agent/state.db is present (RFC-013 P3) the sweep also calls RunStateDB.prune_older_than so aged DB rows do not accumulate either. Both passes are best-effort.

Parameters:
  • workspace_root (Path) – Workspace root containing .agent.

  • keep_run_id (str | None) – Current run id whose sentinel/receipts are always kept.

  • max_age_seconds (float) – Entries younger than this are kept.

  • now (Callable[[], float]) – Clock injection for tests.

Returns:

Number of filesystem entries removed (file count + DB row count).

Return type:

int

Recovery

ralph.recovery

Pipeline recovery: failure classification, budgets, connectivity, and retry control.

This package coordinates the recovery cycle that runs after a phase fails. It decides whether to retry the phase, escalate, or abort based on failure classification and remaining budget.

Main entry points:

  • RecoveryController — top-level controller; evaluates a failure and returns a recovery action (retry, fallover, abort). Injected with a FailureClassifier and an AgentBudgetRegistry.

  • FailureClassifier, ClassifiedFailure, FailureCategory — classify a raw failure string into a category (agent_error, environment, connectivity, ambiguous, …). is_retryable_without_budget() identifies failures that bypass the budget counter.

  • AgentBudgetRegistry, FailureBudget, BudgetState, seed_budget_registry — per-agent retry budgets; prevent infinite retry loops.

  • ConnectivityMonitor, ConnectivityState — background connectivity probe that signals the runner to pause when the host loses network access.

  • CycleCap — enforces the pipeline-level cycle_cap limit from recovery policy.

  • FailureEvent, FailureEventBus, FalloverEvent — event types emitted when the recovery controller fires; consumed by the display and logging subsystems.

  • compute_backoff_ms — computes the exponential backoff delay for the next retry.

ralph.recovery.budget

Failure budget tracking per agent in the pipeline.

class ralph.recovery.budget.AgentBudgetRegistry(budgets=None)[source]

Bases: object

Registry mapping (phase, agent_name) -> BudgetState.

Immutable-value-returning: debit returns a new registry instance. The previous reset method was removed in wt-024 memory-perf AC-01: it had zero callers (repo-wide grep) and violated the AGENTS.md “Absolutely Zero Dead code” rule.

Parameters:

budgets (dict[tuple[str, str], BudgetState] | None)

debit(phase, agent, failure)[source]

Return a new registry with the failure debited for (phase, agent).

The previous failures=(*current.failures, failure) accumulator was removed in wt-024 memory-perf AC-01: the failures tuple was appended on every debit and never read for any decision, while retaining heavyweight ClassifiedFailure objects (original_exception + traceback frames) for the lifetime of the registry. Only consumed is needed to drive the exhausted / remaining decisions.

Parameters:
Return type:

AgentBudgetRegistry

is_exhausted(phase, agent)[source]

Check if the budget for (phase, agent) is exhausted.

Parameters:
  • phase (str)

  • agent (str)

Return type:

bool

items()[source]

Iterate over ((phase, agent), state) pairs without exposing the internal dict.

Return type:

Iterable[tuple[tuple[str, str], BudgetState]]

set_budget(phase, agent, max_retries)[source]

Return a new registry with this budget initialized.

Parameters:
  • phase (str)

  • agent (str)

  • max_retries (int)

Return type:

AgentBudgetRegistry

class ralph.recovery.budget.BudgetState(max_retries, consumed=0)[source]

Bases: object

Immutable budget state for a single (phase, agent) pair.

max_retries and consumed are the only counters needed to drive every budget decision (exhausted / remaining). A previous failures: tuple[ClassifiedFailure, ...] accumulator was removed in wt-024 memory-perf AC-01: it was appended on every debit, never read for any decision, and retained heavyweight ClassifiedFailure objects (original_exception + traceback frames) across an entire run. Repo-wide grep confirmed zero readers.

Parameters:
  • max_retries (int)

  • consumed (int)

class ralph.recovery.budget.FailureBudget(state)[source]

Bases: object

Per-agent failure budget wrapper.

Parameters:

state (BudgetState)

debit(failure)[source]

Return a new budget with the failure counted (only if it counts).

The previous failures=(*self.state.failures, failure) accumulator was removed in wt-024 memory-perf AC-01: the failures tuple was appended on every debit and never read for any decision, while retaining heavyweight ClassifiedFailure objects (original_exception + traceback frames) for the lifetime of the budget. Only consumed is needed to drive the exhausted / remaining decisions.

Parameters:

failure (ClassifiedFailure)

Return type:

FailureBudget

ralph.recovery.budget.seed_budget_registry(bundle)[source]

Seed the budget registry from policy bundle configuration.

Parameters:

bundle (PolicyBundle)

Return type:

AgentBudgetRegistry

ralph.recovery.classifier

Failure classification: categorize exceptions for intelligent attribution.

class ralph.recovery.classifier.ClassifiedFailure(category, reason, attributed_agent, attributed_phase, counts_against_budget, original_exception, raw_message, reset_session=False, reset_tool_registry=False, is_unavailable=False, watchdog_reason=None, unavailability_reason=None, resumable_session_id=None)[source]

Bases: object

A failure with its category, attribution, and budget-counting decision.

Parameters:
  • category (FailureCategory)

  • reason (str)

  • attributed_agent (str | None)

  • attributed_phase (str)

  • counts_against_budget (bool)

  • original_exception (BaseException | None)

  • raw_message (str)

  • reset_session (bool)

  • reset_tool_registry (bool)

  • is_unavailable (bool)

  • watchdog_reason (str | None)

  • unavailability_reason (UnavailabilityReason | None)

  • resumable_session_id (str | None)

class ralph.recovery.classifier.FailureCategory(*values)[source]

Bases: StrEnum

Categories of pipeline failures for attribution and routing.

class ralph.recovery.classifier.FailureClassifier[source]

Bases: object

Classify failures into categories for intelligent recovery routing.

This is a pure, stateless classifier. All classification rules are encapsulated here so new failure modes are added once, not at call sites.

classify(exc, *, phase, agent, connectivity_state=None)[source]

Classify a failure and return a ClassifiedFailure.

Parameters:
  • exc (BaseException | str)

  • phase (str)

  • agent (str | None)

  • connectivity_state (str | None)

Return type:

ClassifiedFailure

class ralph.recovery.classifier.FailureContext(phase, agent=None, retry_in_session=False, classified_failure=None)[source]

Bases: object

Context for a failure event passed to RecoveryController.handle.

Parameters:
  • phase (str)

  • agent (str | None)

  • retry_in_session (bool)

  • classified_failure (ClassifiedFailure | None)

ralph.recovery.classifier.is_missing_artifact_message(raw_message)[source]

Return True if the message indicates a missing required artifact.

Parameters:

raw_message (str)

Return type:

bool

ralph.recovery.classifier.is_retryable_without_budget(failure)[source]

Return True if this failure should retry without debiting the agent budget.

Environmental, artifact-validation, and ambiguous failures retry without counting. Agent and user_config failures consume budget.

Parameters:

failure (ClassifiedFailure)

Return type:

bool

ralph.recovery.connectivity

Proactive connectivity detection with auto-resume.

class ralph.recovery.connectivity.ConnectivityEvent(state, since, reason)[source]

Bases: object

A snapshot of a connectivity state transition.

Parameters:
class ralph.recovery.connectivity.ConnectivityMonitor(*, probe_targets=[('1.1.1.1', 53), ('8.8.8.8', 53)], probe_interval_s=10.0, probe_timeout_s=2.0, probe=None)[source]

Bases: object

Proactively detect connectivity loss and surface state transitions.

All timing and network I/O is injectable so tests run deterministically without real sockets.

Parameters:
  • probe_targets (list[tuple[str, int]])

  • probe_interval_s (float)

  • probe_timeout_s (float)

  • probe (ProbeCallable | None)

add_listener(cb)[source]

Register a listener for connectivity events. Returns an unsubscribe callable.

Parameters:

cb (Callable[[ConnectivityEvent], None])

Return type:

Callable[[], None]

async start()[source]

Start the background connectivity probe loop.

Return type:

None

async stop()[source]

Stop the background probe loop and unblock any waiters.

Return type:

None

async wait_online()[source]

Suspend until connectivity is restored (or monitor is stopped).

Return type:

None

class ralph.recovery.connectivity.ConnectivityState(*values)[source]

Bases: StrEnum

Enumeration of observed network connectivity states.

ralph.recovery.controller

RecoveryController: single owner of failure classification, budget, and fallover.

Never-Exit Invariant

The recovery controller is the second half of the recovery contract documented in ralph.agents.idle_watchdog. The pipeline NEVER exits because of agent unavailability. This is enforced by the all-agents-unavailable wait branch (in _handle_retry_progression) and the wrap=True re-arming in _next_available_agent_index:

  • All-agents-unavailable wait branch: when every agent in the chain is on cooldown, the controller returns state.copy_with(last_retry_delay_ms=<earliest cooldown>, is_waiting_state=True) and does NOT call _enter_phase_failed. The run loop sleeps on last_retry_delay_ms and re-enters the same phase. The is_waiting_state flag is the structured contract the run loop keys off; last_error text is operator-readable context only and is not parsed by the run loop.

  • wrap=True re-arming: when the chain advances, the _next_available_agent_index search is cyclic. Earlier agents whose cooldown has expired are reconsidered; the recovered agent is selected for the next attempt (it is not the agent that was on cooldown longest).

The pipeline has exactly two recovery states: exponential backoff to the next agent (AgentUnavailabilityTracker.mark_unavailable) and retry with the same agent (AgentChain.record_retry). The all-agents-unavailable wait branch is a third observable effect (is_waiting_state=True) but it is NOT a third state – it is a transient holding pattern that the run loop interprets as “continue the same phase after the cooldown expires”. The controller never reaches failed_terminal via this path.

class ralph.recovery.controller.RecoveryController(*, options=None)[source]

Bases: object

Single conceptual owner of recovery logic.

Handles classification, budget debiting, chain fallover, and cycle cap. Delegates nothing to the reducer’s internal retry counter when active.

Parameters:

options (RecoveryControllerOptions | None)

agents_now_available(phase, agents)[source]

Return the subset of agents that are currently available.

Convenience wrapper around the public store surface for the run loop’s RESUMED log. Callers MUST use this method instead of reaching through to the private _unavailability_tracker.

Parameters:
  • phase (str) – The pipeline phase.

  • agents (list[str]) – The agent chain in policy order.

Returns:

A list of agent names that are currently available, preserving the input order.

Return type:

list[str]

handle(state, raw_failure, context)[source]

Classify a failure and compute the recovery transition.

Parameters:
  • state (PipelineState) – Current pipeline state.

  • raw_failure (BaseException | str) – The raw exception or string error message.

  • context (FailureContext) – Phase/agent context and optional pre-classified failure.

Returns:

Tuple of (new_state, effects, failure_event).

Return type:

tuple[PipelineState, list[Effect], FailureEvent]

reset_backoff(phase, agent)[source]

Reset backoff counter for a phase/agent after successful invocation.

Parameters:
  • phase (str)

  • agent (str | None)

Return type:

None

snapshot()[source]

Return a runtime observability snapshot of recovery state.

Return type:

dict[str, object]

property unavailability_store: UnavailabilityStore

Public access to the unavailability store (Protocol-typed).

Callers MUST consume the store through this property, not through the private _unavailability_tracker attribute. The Protocol is the seam for a future persistent (sqlite, redis, file) implementation; the in-memory AgentUnavailabilityTracker is the default when RecoveryControllerOptions.unavailability_store is not provided.

waiting_state_payload(phase, agents)[source]

Return the per-agent cooldown payload for the all-agents-unavailable WAITING / RESUMED structured logs.

Each tuple is (agent, attempt, cooldown_ms_remaining) where cooldown_ms_remaining is the time in milliseconds until the agent becomes available (0 if the agent is already available). This is the single public surface for the run loop’s WAITING log; the run loop MUST NOT reach through to the private _unavailability_tracker or the tracker’s _clock.

Parameters:
  • phase (str) – The pipeline phase (e.g. “development”).

  • agents (list[str]) – The agent chain in policy order.

Returns:

A list of (agent, attempt, cooldown_ms_remaining) tuples, one per agent in the chain. Order matches agents input order. Each cooldown is a non-negative int.

Return type:

list[tuple[str, int, int]]

class ralph.recovery.controller.RecoveryControllerOptions(cycle_cap=200, classifier=None, event_bus=None, budget_registry=None, policy_bundle=None, backoff_attempts=None, technical_retry_cap=10, unavailable_timeouts=None, unavailability_backoff_policy=None, unavailability_entries=None, clock=None, unavailability_store=None)[source]

Bases: object

Options for constructing a RecoveryController.

Parameters:
  • cycle_cap (int)

  • classifier (FailureClassifier | None)

  • event_bus (FailureEventBus | None)

  • budget_registry (AgentBudgetRegistry | None)

  • policy_bundle (PolicyBundle | None)

  • backoff_attempts (dict[str, int] | None)

  • technical_retry_cap (int)

  • unavailable_timeouts (dict[str, int] | None)

  • unavailability_backoff_policy (dict[UnavailabilityReason, ReasonBackoffPolicy] | None)

  • unavailability_entries (dict[str, UnavailabilityEntry] | None)

  • clock (Clock | None)

  • unavailability_store (UnavailabilityStore | None)

ralph.recovery.controller.compute_backoff_ms(base_ms, attempt, max_ms=30000)[source]

Compute exponential backoff delay with cap.

Parameters:
  • base_ms (int) – Base delay in milliseconds.

  • attempt (int) – Current retry attempt (0-indexed).

  • max_ms (int) – Maximum delay cap in milliseconds.

Returns:

Delay in milliseconds, capped at max_ms.

Return type:

int

ralph.recovery.cycle_cap

Recovery cycle cap: bounded cap on total recovery cycles.

class ralph.recovery.cycle_cap.CycleCap(cap)[source]

Bases: object

Tracks and enforces the maximum number of recovery cycles.

A recovery cycle increments when the entire agent chain for a phase is exhausted. The cap prevents a persistently-failing handler from looping silently forever.

Parameters:

cap (int)

exit_reason(count, last_category, last_reason)[source]

Build a descriptive exit reason for when the cap is exceeded.

Parameters:
  • count (int)

  • last_category (str)

  • last_reason (str)

Return type:

str

is_exceeded(count)[source]

Return True if count >= cap.

Parameters:

count (int)

Return type:

bool

ralph.recovery.events

Structured failure events and event bus for recovery observability.

class ralph.recovery.events.FailureEvent(timestamp, phase, agent, category, reason, counted_against_budget, chain_capacity_remaining, recovery_cycle, retry_delay_ms=0, watchdog_reason=None, unavailability_reason=None)[source]

Bases: object

Structured failure event emitted for every classified failure.

Parameters:
  • timestamp (datetime)

  • phase (str)

  • agent (str | None)

  • category (str)

  • reason (str)

  • counted_against_budget (bool)

  • chain_capacity_remaining (int)

  • recovery_cycle (int)

  • retry_delay_ms (int)

  • watchdog_reason (str | None)

  • unavailability_reason (str | None)

class ralph.recovery.events.FailureEventBus[source]

Bases: object

Simple publish/subscribe bus for failure and fallover events.

subscribe(cb)[source]

Register a listener. Returns a callable that unsubscribes it.

Parameters:

cb (Callable[[FailureEvent | FalloverEvent], None])

Return type:

Callable[[], None]

class ralph.recovery.events.FalloverEvent(timestamp, phase, from_agent, to_agent, reason, watchdog_reason=None, unavailability_reason=None)[source]

Bases: object

Emitted when an agent is exhausted and the chain falls over to the next.

Parameters:
  • timestamp (datetime)

  • phase (str)

  • from_agent (str)

  • to_agent (str)

  • reason (str)

  • watchdog_reason (str | None)

  • unavailability_reason (str | None)

ralph.recovery.testing

Test helpers for recovery package: fake monitors and fakes for black-box tests.

class ralph.recovery.testing.FakeConnectivityMonitor(initial_state=ConnectivityState.ONLINE)[source]

Bases: object

Deterministic connectivity monitor for tests.

Allows injecting state transitions without real network probes.

Parameters:

initial_state (ConnectivityState)

go_offline(reason='test offline')[source]

Simulate going offline.

Parameters:

reason (str)

Return type:

None

go_online(reason='test online')[source]

Simulate coming back online.

Parameters:

reason (str)

Return type:

None

ralph.recovery.agent_budget_registry

Registry mapping (phase, agent_name) to budget state.

class ralph.recovery.agent_budget_registry.AgentBudgetRegistry(budgets=None)[source]

Bases: object

Registry mapping (phase, agent_name) -> BudgetState.

Immutable-value-returning: debit returns a new registry instance. The previous reset method was removed in wt-024 memory-perf AC-01: it had zero callers (repo-wide grep) and violated the AGENTS.md “Absolutely Zero Dead code” rule.

Parameters:

budgets (dict[tuple[str, str], BudgetState] | None)

debit(phase, agent, failure)[source]

Return a new registry with the failure debited for (phase, agent).

The previous failures=(*current.failures, failure) accumulator was removed in wt-024 memory-perf AC-01: the failures tuple was appended on every debit and never read for any decision, while retaining heavyweight ClassifiedFailure objects (original_exception + traceback frames) for the lifetime of the registry. Only consumed is needed to drive the exhausted / remaining decisions.

Parameters:
Return type:

AgentBudgetRegistry

is_exhausted(phase, agent)[source]

Check if the budget for (phase, agent) is exhausted.

Parameters:
  • phase (str)

  • agent (str)

Return type:

bool

items()[source]

Iterate over ((phase, agent), state) pairs without exposing the internal dict.

Return type:

Iterable[tuple[tuple[str, str], BudgetState]]

set_budget(phase, agent, max_retries)[source]

Return a new registry with this budget initialized.

Parameters:
  • phase (str)

  • agent (str)

  • max_retries (int)

Return type:

AgentBudgetRegistry

ralph.recovery.budget_state

Immutable budget state for a single (phase, agent) pair.

class ralph.recovery.budget_state.BudgetState(max_retries, consumed=0)[source]

Bases: object

Immutable budget state for a single (phase, agent) pair.

max_retries and consumed are the only counters needed to drive every budget decision (exhausted / remaining). A previous failures: tuple[ClassifiedFailure, ...] accumulator was removed in wt-024 memory-perf AC-01: it was appended on every debit, never read for any decision, and retained heavyweight ClassifiedFailure objects (original_exception + traceback frames) across an entire run. Repo-wide grep confirmed zero readers.

Parameters:
  • max_retries (int)

  • consumed (int)

ralph.recovery.classified_failure

Structured classified failure model.

class ralph.recovery.classified_failure.ClassifiedFailure(category, reason, attributed_agent, attributed_phase, counts_against_budget, original_exception, raw_message, reset_session=False, reset_tool_registry=False, is_unavailable=False, watchdog_reason=None, unavailability_reason=None, resumable_session_id=None)[source]

Bases: object

A failure with its category, attribution, and budget-counting decision.

Parameters:
  • category (FailureCategory)

  • reason (str)

  • attributed_agent (str | None)

  • attributed_phase (str)

  • counts_against_budget (bool)

  • original_exception (BaseException | None)

  • raw_message (str)

  • reset_session (bool)

  • reset_tool_registry (bool)

  • is_unavailable (bool)

  • watchdog_reason (str | None)

  • unavailability_reason (UnavailabilityReason | None)

  • resumable_session_id (str | None)

ralph.recovery.failure_budget

Per-agent failure budget wrapper.

class ralph.recovery.failure_budget.FailureBudget(state)[source]

Bases: object

Per-agent failure budget wrapper.

Parameters:

state (BudgetState)

debit(failure)[source]

Return a new budget with the failure counted (only if it counts).

The previous failures=(*self.state.failures, failure) accumulator was removed in wt-024 memory-perf AC-01: the failures tuple was appended on every debit and never read for any decision, while retaining heavyweight ClassifiedFailure objects (original_exception + traceback frames) for the lifetime of the budget. Only consumed is needed to drive the exhausted / remaining decisions.

Parameters:

failure (ClassifiedFailure)

Return type:

FailureBudget

ralph.recovery.failure_category

Categories of pipeline failures for attribution and routing.

class ralph.recovery.failure_category.FailureCategory(*values)[source]

Bases: StrEnum

Categories of pipeline failures for attribution and routing.

ralph.recovery.failure_details

Shared helpers for extracting and matching rich failure details.

ralph.recovery.failure_details.contains_casefolded_marker(parts, markers)[source]

Return True when any marker appears in any part, case-insensitively.

Parameters:
  • parts (Iterable[str])

  • markers (Iterable[str])

Return type:

bool

ralph.recovery.failure_details.failure_detail_parts(exc)[source]

Return all textual detail surfaces associated with a failure.

Parameters:

exc (BaseException | str)

Return type:

list[str]

ralph.recovery.failure_details.has_stale_session_details(exc, markers)[source]

Return True when any failure surface carries a stale-session signal.

Parameters:
  • exc (BaseException | str)

  • markers (Iterable[str])

Return type:

bool

ralph.recovery.retry_prompt

Shared formatting helpers for technical retry prompts and retry hints.

ralph.recovery.retry_prompt.build_retry_error_block(*, failure_summary, detail=None, prompt_path=None, context_path=None)[source]

Return a shared error-first retry block.

The failure must lead the prompt. Original prompt and prior context paths are secondary references for continuing the same task after addressing the error.

Parameters:
  • failure_summary (str)

  • detail (str | None)

  • prompt_path (str | None)

  • context_path (str | None)

Return type:

str

ralph.recovery.seed_budget_registry

Policy-driven budget registry seeding.

ralph.recovery.seed_budget_registry.seed_budget_registry(bundle)[source]

Seed the budget registry from policy bundle configuration.

Parameters:

bundle (PolicyBundle)

Return type:

AgentBudgetRegistry

Runtime

ralph.runtime

Python runtime environment detection and test-timeout utilities.

This package combines two concerns that phase handlers and tests regularly need together: detecting the Python runtime environment, and managing wall-clock timeout budgets for test commands.

Main entry points:

  • detect_runtime_environment() — inspects the running Python interpreter and returns a RuntimeEnvironment with version, virtualenv status, and path details.

  • RuntimeEnvironment — structured runtime snapshot (PythonVersionInfo, virtualenv path, site-packages path).

  • PythonVersionInfo — major, minor, patch version tuple.

  • is_virtualenv() / detect_virtualenv_path() — virtualenv detection helpers.

  • run_command_with_timeout, timeout_seconds_from_env, build_timeout_env — re-exported from ralph.verify_timeout; used by test commands to enforce the 60-second test-suite budget.

  • SuiteTimeoutError — raised on suite timeout budget exhaustion.

class ralph.runtime.PythonVersionInfo(major, minor, micro, releaselevel, serial, implementation, executable, version)[source]

Bases: object

Structured Python runtime version metadata.

Parameters:
  • major (int)

  • minor (int)

  • micro (int)

  • releaselevel (str)

  • serial (int)

  • implementation (str)

  • executable (Path)

  • version (str)

classmethod from_sys(sys_module)[source]

Build version metadata from a sys-like module.

Parameters:

sys_module (SysModuleProtocol)

Return type:

PythonVersionInfo

class ralph.runtime.RuntimeEnvironment(python, executable, prefix, base_prefix, exec_prefix, base_exec_prefix, in_virtualenv, virtualenv_path, env)[source]

Bases: object

Snapshot of the active Python runtime environment.

Parameters:
  • python (PythonVersionInfo)

  • executable (Path)

  • prefix (Path)

  • base_prefix (Path)

  • exec_prefix (Path)

  • base_exec_prefix (Path)

  • in_virtualenv (bool)

  • virtualenv_path (Path | None)

  • env (Mapping[str, str])

get(name, default=None)[source]

Return an environment variable from the captured snapshot.

Parameters:
  • name (str)

  • default (str | None)

Return type:

str | None

exception ralph.runtime.SuiteTimeoutError(timeout_seconds)[source]

Bases: RuntimeError

Raised when a pytest invocation exceeds the configured suite timeout.

Parameters:

timeout_seconds (float) – The wall-clock cap (in seconds) that the subprocess exceeded before run_process reported TIMEOUT_EXIT_CODE. Surfaced as self.timeout_seconds for programmatic inspection.

Return type:

None

The error message embeds the policy-violation banner from _POLICY_FIX_MESSAGE so the agent sees the full fix guidance on first sight.

ralph.runtime.build_timeout_env(*, base_env=None, test_timeout_seconds=1.0, suite_timeout_seconds=60.0)[source]

Build a subprocess environment carrying the per-test and per-suite timeouts.

Parameters:
  • base_env (Mapping[str, str] | None) – Environment mapping to copy. When None, the current os.environ is used as the base.

  • test_timeout_seconds (float) – Value for RALPH_PYTEST_TEST_TIMEOUT_SECONDS (default DEFAULT_TEST_TIMEOUT_SECONDS = 1.0).

  • suite_timeout_seconds (float) – Value for RALPH_PYTEST_SUITE_TIMEOUT_SECONDS (default DEFAULT_SUITE_TIMEOUT_SECONDS = 60.0).

Returns:

A fresh dict containing every base entry plus the two timeout variables. Caller-owned: mutations do not affect os.environ or the base mapping.

Return type:

dict[str, str]

ralph.runtime.detect_runtime_environment(env=None, *, sys_module=<module 'sys' (built-in)>)[source]

Capture a structured snapshot of the active Python runtime.

Parameters:
  • env (Mapping[str, str] | None)

  • sys_module (SysModuleProtocol)

Return type:

RuntimeEnvironment

ralph.runtime.detect_virtualenv_path(env=None, *, sys_module=<module 'sys' (built-in)>)[source]

Return the detected virtual environment path, if any.

Parameters:
  • env (Mapping[str, str] | None)

  • sys_module (SysModuleProtocol)

Return type:

Path | None

ralph.runtime.is_virtualenv(env=None, *, sys_module=<module 'sys' (built-in)>)[source]

Return whether the current interpreter is running inside a virtual environment.

Parameters:
  • env (Mapping[str, str] | None)

  • sys_module (SysModuleProtocol)

Return type:

bool

ralph.runtime.run_command_with_timeout(command, *, cwd, env=None, suite_timeout_seconds=60.0, capture_output=True)[source]

Run command under the bounded subprocess manager with a suite timeout.

Parameters:
  • command (Sequence[str]) – The argv to invoke. command[0] is the executable.

  • cwd (Path) – Working directory for the subprocess.

  • env (Mapping[str, str] | None) – Environment mapping for the subprocess. None inherits the parent process environment.

  • suite_timeout_seconds (float) – Wall-clock cap (seconds) passed to run_process. Default is DEFAULT_SUITE_TIMEOUT_SECONDS (60 s). Note this is the per-invocation cap; the combined test budget is enforced upstream in ralph.verify.

  • capture_output (bool) – When True (default), capture stdout/stderr into the returned ProcessResult; when False, the subprocess writes directly to the parent’s streams.

Returns:

The ProcessResult from run_process.

Raises:

SuiteTimeoutError – When the subprocess exits with TIMEOUT_EXIT_CODE, indicating the suite exceeded suite_timeout_seconds.

Return type:

ProcessResult

Side effects:

Spawns a subprocess through the shared _VERIFY_TIMEOUT_PM ProcessManager. The subprocess inherits the parent environment (with the timeout env vars added when env is None); both stdout/stderr are routed per capture_output.

ralph.runtime.timeout_seconds_from_env(name, default)[source]

Read a timeout value from the process environment.

Parameters:
  • name (str) – Environment variable name. Recognised values include RALPH_PYTEST_TEST_TIMEOUT_SECONDS and RALPH_PYTEST_SUITE_TIMEOUT_SECONDS.

  • default (float) – Value returned when name is unset.

Returns:

The parsed float from the environment, or default if the variable is missing. Raises ValueError if the variable is set but not parseable as a float.

Return type:

float

ralph.runtime.environment

Runtime environment discovery helpers.

class ralph.runtime.environment.RuntimeEnvironment(python, executable, prefix, base_prefix, exec_prefix, base_exec_prefix, in_virtualenv, virtualenv_path, env)[source]

Bases: object

Snapshot of the active Python runtime environment.

Parameters:
  • python (PythonVersionInfo)

  • executable (Path)

  • prefix (Path)

  • base_prefix (Path)

  • exec_prefix (Path)

  • base_exec_prefix (Path)

  • in_virtualenv (bool)

  • virtualenv_path (Path | None)

  • env (Mapping[str, str])

get(name, default=None)[source]

Return an environment variable from the captured snapshot.

Parameters:
  • name (str)

  • default (str | None)

Return type:

str | None

ralph.runtime.environment.detect_runtime_environment(env=None, *, sys_module=<module 'sys' (built-in)>)[source]

Capture a structured snapshot of the active Python runtime.

Parameters:
  • env (Mapping[str, str] | None)

  • sys_module (SysModuleProtocol)

Return type:

RuntimeEnvironment

ralph.runtime.environment.detect_virtualenv_path(env=None, *, sys_module=<module 'sys' (built-in)>)[source]

Return the detected virtual environment path, if any.

Parameters:
  • env (Mapping[str, str] | None)

  • sys_module (SysModuleProtocol)

Return type:

Path | None

ralph.runtime.environment.is_virtualenv(env=None, *, sys_module=<module 'sys' (built-in)>)[source]

Return whether the current interpreter is running inside a virtual environment.

Parameters:
  • env (Mapping[str, str] | None)

  • sys_module (SysModuleProtocol)

Return type:

bool

ralph.runtime.verify_timeout

Compatibility re-export of the verify-timeout policy from ralph.verify_timeout.

This module makes ralph.runtime.verify_timeout a stable documented surface for callers that import through the ralph.runtime namespace. All public symbols are re-exported from ralph.verify_timeout unchanged.

Important

The 60-second ABSOLUTE and IMMUTABLE combined test budget for make verify is enforced by ralph/verify.py:_TOTAL_TEST_BUDGET_SECONDS via cumulative time.monotonic() tracking across ALL test steps. Per-suite timeouts in this module are SECONDARY caps only — raising them does not increase the combined budget. Splitting tests across N suites does NOT give N x 60 s.

Process

ralph.process

Process management package — single source of truth for all child processes.

Every subprocess Ralph spawns flows through ProcessManager. The manager records lifecycle transitions, emits observable events, and owns escalating termination via psutil for cross-platform process-tree teardown (Linux, macOS, and Windows). No POSIX-only APIs are used.

ralph.process.child_liveness

In-memory child liveness lease registry and canonical evidence classifier.

This module is the single source of truth for child-evidence freshness decisions. classify_child_snapshot() is the canonical verdict function: both the in-stream idle-watchdog path (execution_state.classify_quiet) and the post-exit path (execution_state.classify_exit / invoke._evidence_precedence) must call this function rather than re-encoding the stale-vs-fresh precedence rules independently.

Evidence is tracked per-child with heartbeat, progress, and terminal-ack signals using an injectable clock so tests can use deterministic FakeClock-compatible sources.

No on-disk persistence: the registry is instantiated per invoke and lives only as long as the invocation.

class ralph.process.child_liveness.AliveBy(*values)[source]

Bases: StrEnum

Typed corroboration reasons describing why child work still appears alive.

class ralph.process.child_liveness.ChildActivitySnapshot(scope_prefix, has_process, has_fresh_label, has_fresh_progress, oldest_live_child_seconds, active_count, terminal_count, has_fresh_heartbeat=False)[source]

Bases: object

Freshness-aware aggregate snapshot for a scope prefix.

Parameters:
  • scope_prefix (str)

  • has_process (bool)

  • has_fresh_label (bool)

  • has_fresh_progress (bool)

  • oldest_live_child_seconds (float | None)

  • active_count (int)

  • terminal_count (int)

  • has_fresh_heartbeat (bool)

class ralph.process.child_liveness.ChildEvidenceVerdict(alive_by, deferral_allowed, all_children_terminal=False)[source]

Bases: object

Unified verdict from child-liveness evidence classification.

Parameters:
  • alive_by (AliveBy | None)

  • deferral_allowed (bool)

  • all_children_terminal (bool)

alive_by

Why child work appears alive, or None if there is no evidence.

Type:

AliveBy | None

deferral_allowed

Whether WAITING_ON_CHILD deferral should apply.

Type:

bool

all_children_terminal

All Ralph-tracked children have terminated.

Type:

bool

class ralph.process.child_liveness.ChildLivenessRecord(child_id, scope_prefix, pid, started_at, last_progress_at, last_heartbeat_at, last_ack_at, last_known_phase='spawned', terminal_state=None, lease_expires_at=None)[source]

Bases: object

Immutable snapshot of a single child’s liveness state.

Parameters:
  • child_id (str)

  • scope_prefix (str)

  • pid (int | None)

  • started_at (float)

  • last_progress_at (float | None)

  • last_heartbeat_at (float | None)

  • last_ack_at (float | None)

  • last_known_phase (str)

  • terminal_state (str | None)

  • lease_expires_at (float | None)

class ralph.process.child_liveness.ChildLivenessRegistry(*, progress_ttl, heartbeat_ttl, stale_label_ttl, exit_reconcile, now=<built-in function monotonic>)[source]

Bases: object

In-memory registry of active child leases with freshness tracking.

All methods are synchronous and safe to call from the main thread. The registry is not thread-safe by design: the invoke loop drives all operations from a single call site.

Parameters:
  • progress_ttl (float) – Seconds since last progress signal before child is stale.

  • heartbeat_ttl (float) – Seconds since last heartbeat before heartbeat is stale.

  • stale_label_ttl (float) – Grace period (seconds) after evidence goes stale.

  • exit_reconcile (float) – Window (seconds) after terminal ack during which the record is retained before being dropped from active counts.

  • now (Callable[[], float]) – Callable returning current monotonic time; defaults to time.monotonic.

active_pids(scope_prefix)[source]

Return non-terminal PIDs of children matching scope_prefix.

Prunes stale records first so returned PIDs correspond to children whose evidence has not yet aged out of the registry.

Parameters:

scope_prefix (str)

Return type:

set[int]

has_records(scope_prefix)[source]

Return True when any record currently matches the given scope prefix.

Parameters:

scope_prefix (str)

Return type:

bool

prune_stale(now=None)[source]

Remove records whose evidence is fully stale.

A record is pruned when: - It has a terminal state AND the ack is outside the exit_reconcile window, OR - It has no terminal state AND no progress ever, AND its label age > stale_label_ttl, OR - It has no terminal state AND its last progress is older than progress_ttl.

Returns:

Number of records pruned.

Parameters:

now (float | None)

Return type:

int

record_heartbeat(child_id)[source]

Record a heartbeat for a child (advances last_heartbeat_at only).

Parameters:

child_id (str)

Return type:

None

record_progress(child_id, *, phase=None)[source]

Record progress for a child (advances both progress and heartbeat).

Parameters:
  • child_id (str)

  • phase (str | None)

Return type:

None

record_terminal_ack(child_id, *, terminal_state='complete')[source]

Record that a child has terminated.

Parameters:
  • child_id (str)

  • terminal_state (str)

Return type:

None

register_child(child_id, scope_prefix, *, pid=None, phase='spawned')[source]

Register a new child with the registry.

Parameters:
  • child_id (str)

  • scope_prefix (str)

  • pid (int | None)

  • phase (str)

Return type:

None

snapshot(scope_prefix)[source]

Return an aggregated freshness snapshot for all children matching scope_prefix.

Parameters:

scope_prefix (str)

Return type:

ChildActivitySnapshot

ralph.process.child_liveness.classify_child_snapshot(snapshot, *, has_os_descendants=False)[source]

Classify child-liveness evidence from a snapshot into a typed verdict.

This is the single source of truth for stale/fresh precedence logic. Both execution_state and invoke corroboration must consume this function rather than re-implementing the precedence rules independently.

Parameters:
  • snapshot (ChildActivitySnapshot) – Freshness-aware aggregate snapshot from a registry or probe.

  • has_os_descendants (bool) – Whether OS-level descendants exist (from process tree scan). Only consulted when no scoped Ralph evidence is present.

Returns:

A ChildEvidenceVerdict encoding alive_by classification, deferral_allowed, and all_children_terminal flags.

Return type:

ChildEvidenceVerdict

ralph.process.liveness

LivenessProbe protocol and implementations for aggregate-tree idle evaluation.

The LivenessProbe is an injectable seam so unit tests can fake agent-tree activity without spawning real processes.

class ralph.process.liveness.DefaultLivenessProbe(registry=None)[source]

Bases: object

Production probe: queries the ProcessManager singleton for active labels.

Accepts an optional ChildLivenessRegistry for freshness-aware child_snapshot(). When no registry is supplied, child_snapshot() returns a conservative snapshot based on ProcessManager labels only (has_process=True/False, no freshness).

Parameters:

registry (ChildLivenessRegistry | None)

class ralph.process.liveness.FakeLivenessProbe(*, active=False, active_labels=None, snapshot=None)[source]

Bases: object

Test-only probe that returns a fixed activity answer.

When active_labels is provided the probe simulates a specific set of active process labels: any_agent_active(prefix) returns True only when at least one label in active_labels starts with prefix. This lets tests distinguish between related and unrelated agent workers.

When active_labels is None the probe falls back to the flat active flag (existing behaviour, unchanged).

When snapshot is provided, child_snapshot() returns it for any prefix.

Parameters:
class ralph.process.liveness.LivenessProbe(*args, **kwargs)[source]

Bases: Protocol

Protocol for checking whether any tracked agent label is still active.

any_agent_active(label_prefix)[source]

Return True if any tracked process whose label starts with label_prefix is running.

Parameters:

label_prefix (str)

Return type:

bool

child_snapshot(scope_prefix)[source]

Return a freshness-aware snapshot for all children matching scope_prefix.

Parameters:

scope_prefix (str)

Return type:

ChildActivitySnapshot

ralph.process.manager

ProcessManager — single source of truth for every child process Ralph spawns.

ralph.process.pty

POSIX PTY process primitives for unattended interactive runtimes.

This module owns the low-level pseudo-terminal spawn path used by transports that must behave like a real interactive terminal session. The parent process keeps the master file descriptor; the child gets the slave side as its controlling terminal.

class ralph.process.pty.PtyProcess(pid, master_fd, slave_fd, _returncode=None, _closed=False)[source]

Bases: object

Tracked PTY child process owned by the parent master file descriptor.

Parameters:
  • pid (int)

  • master_fd (int)

  • slave_fd (int)

  • _returncode (int | None)

  • _closed (bool)

ralph.process.pty.read_master_chunk(master_fd, max_bytes=4096)[source]

Read one chunk from the PTY master, tolerating EIO-on-EOF semantics.

Parameters:
  • master_fd (int)

  • max_bytes (int)

Return type:

bytes

ralph.process.pty.spawn_pty_process(command, *, cwd, env, cols=80, rows=24)[source]

Spawn a child under a real PTY and return the parent-side handle.

Parameters:
  • command (Sequence[str])

  • cwd (str | None)

  • env (dict[str, str] | None)

  • cols (int)

  • rows (int)

Return type:

PtyProcess

ralph.process.pty.wait_for_master_readable(master_fd, timeout_seconds)[source]

Return True when the PTY master has readable data within the timeout.

Parameters:
  • master_fd (int)

  • timeout_seconds (float)

Return type:

bool

ralph.process.mcp_supervisor

Active MCP server supervision during agent execution.

The McpSupervisor runs a background thread that polls bridge health on a fixed interval while an agent attempt is executing. When the MCP server crashes, the supervisor restarts it on the stable endpoint so the agent can continue. If the restart budget is exhausted, the error is stored and re-raised when the context manager exits.

class ralph.process.mcp_supervisor.McpSupervisor(bridge, *, check_interval=datetime.timedelta(seconds=2), on_restart=None, on_error=None)[source]

Bases: object

Background-thread supervisor for an active MCP server bridge.

Usage:

with McpSupervisor(bridge, on_restart=subscriber.record_mcp_restart):
    output = invoke_agent(...)
    stream_output(output)

The supervisor polls check_mcp_bridge_health(bridge) every check_interval seconds. Restarts are recorded via the optional on_restart callback. If the restart budget is exhausted, the stored McpServerError is re-raised when the context manager exits — taking priority over any agent-level error.

Parameters:

ralph.process.monitor

Process monitoring for agent-agnostic subagent discovery and output capture.

Discovery strategies are documentation-grounded only. When a path cannot be established from official docs, the strategy reports an empty mapping rather than inventing a convention.

Cross-transport contract

For each supported transport the watchdog must surface what every active subagent is doing in real time. The transport-specific source of that evidence differs:

  • OpenCode emits structured child lifecycle events on stdout that the OpenCodeExecutionStrategy ingests into a per-invocation ChildLivenessRegistry. The factory returns OpenCodeRegistryDiscoveryStrategy for the OPENCODE transport when a registry is provided so a per-child RegistryBackedSubagentOutputCapture can surface textual descriptions of progress / heartbeat / terminal events.

  • Claude / Claude-interactive / Codex / Nanocoder / Generic / Agy / Pi do not document a stable per-worker subagent log path. The factory returns NullDiscoveryStrategy for these transports; real-time subagent visibility flows through the cross-transport subagent activity sink (IdleWatchdog.record_subagent_work()) which the line-loop observes invoke on every child-signal line.

ralph.process.teardown

Process subtree teardown utilities.

Ensures that every subagent spawned by a host process is reaped when a phase, iteration, or session ends. The teardown walks the entire process tree (all descendants, transitively) and escalates from SIGTERM to SIGKILL after a short grace window.

When the host process has already exited, the descendants are reaped by signaling the host’s process group (the host is the session leader because agents are spawned with start_new_session=True). This closes the race where a dead host PID can no longer be enumerated with psutil but its children still exist.

class ralph.process.teardown.DefaultProcessTeardown(kill_escalation_ms=5000)[source]

Bases: object

Reap a process subtree using psutil.

Sends SIGTERM to the host and all descendants, waits up to KILL_ESCALATION_CEILING_MS for them to exit, then sends SIGKILL to any survivors. The implementation gracefully handles processes that disappear between enumeration and signal delivery.

Parameters:

kill_escalation_ms (float) – Milliseconds to wait between SIGTERM and SIGKILL. Defaults to KILL_ESCALATION_CEILING_MS.

teardown_subtree(host_pid)[source]

Kill the host process and all of its descendants.

Parameters:

host_pid (int)

Return type:

None

class ralph.process.teardown.ProcessTeardown(*args, **kwargs)[source]

Bases: Protocol

Protocol for reaping a process subtree.

teardown_subtree(host_pid)[source]

Kill the entire process subtree rooted at host_pid.

Must reap the host and all descendants, transitively. Implementations should escalate from SIGTERM to SIGKILL after a bounded grace window.

Parameters:

host_pid (int)

Return type:

None

ralph.process.teardown.teardown_subtree(host_pid, *, kill_escalation_ms=5000)[source]

Convenience function that reaps a subtree with the default implementation.

Parameters:
  • host_pid (int)

  • kill_escalation_ms (float)

Return type:

None

API

ralph.api

Public API integrations exposed by Ralph.

This package is the canonical entry point for callers that want to use Ralph’s outbound integrations without depending on the CLI entry point itself. The six public names cover the OpenCode catalog and local model preflight use cases:

  • ModelEntry — the immutable record returned for every catalog entry. Carries the required id and optional name / provider fields; frozen=True so callers can hash and compare entries safely.

  • fetch_catalog() — returns the full list[ModelEntry] from https://models.dev/api.json. The result is cached for the lifetime of the calling process with a 5-minute TTL; the TTL is rechecked on every call so a long-running orchestrator does not retain stale data indefinitely. fetch_catalog.cache_clear() bypasses the TTL for explicit invalidation.

  • get_model_by_id() — look up a single ModelEntry by its fully-qualified "provider/model" identifier; returns None when the id is absent.

  • list_providers() — sorted unique list of every provider present in the current catalog snapshot.

  • search_models() — case-insensitive substring search over name, provider, and id; returns list[ModelEntry].

  • validate_local_model_support() — run a local OpenCode preflight probe (opencode models --refresh <provider>) and return None when the local binary supports model_id or a human-readable diagnostic string when it does not. Useful as the early-failure check before launching an agent that targets a specific model.

ralph.api.opencode

Fetch and cache the OpenCode model catalog from models.dev.

This module provides access to the OpenCode model catalog for discovering available models and providers.

class ralph.api.opencode.ProcessRunner(*args, **kwargs)[source]

Bases: Protocol

Callable process-execution seam used for local OpenCode preflight probes.

ralph.api.opencode.get_model_by_id(model_id)[source]

Get a specific model by ID.

Parameters:

model_id (str) – Model identifier to look up.

Returns:

ModelEntry if found, None otherwise.

Return type:

ModelEntry | None

ralph.api.opencode.list_providers()[source]

List all unique providers in the catalog.

Returns:

Sorted list of provider names.

Return type:

list[str]

ralph.api.opencode.search_models(query)[source]

Search models by name or provider.

Parameters:

query (str) – Search query (case-insensitive).

Returns:

List of matching ModelEntry instances.

Return type:

list[ModelEntry]

ralph.api.opencode.validate_local_model_support(model_id, *, command='opencode', env_path=None, _run_process=<function run_process>)[source]

Return a human-readable error when the local OpenCode binary cannot use a model.

Two-mode return value:

  • None — the local opencode binary (resolved from env_path or PATH) confirmed the model is supported after a models --refresh <provider> invocation.

  • Diagnostic str — the binary is missing, the refresh command failed, or the model is absent from the refreshed model list. The string is suitable for surfacing to the user before launching an agent that targets model_id.

Parameters:
  • model_id (str) – Fully-qualified "provider/model" identifier. When "/" is absent the function returns None immediately (the caller passed a bare provider name).

  • command (str) – Executable name or path to invoke. Defaults to "opencode", resolved via shutil.which().

  • env_path (str | None) – Optional override for the PATH used to resolve command. When None the current process’s PATH is used.

  • _run_process (ProcessRunner) – Subprocess seam (ProcessRunner protocol). Defaults to ralph.executor.process.run_process(); injectable for tests.

Returns:

None when model_id is supported locally; otherwise a multi-line diagnostic string describing the failure mode.

Return type:

str | None

Side effects:

Spawns two subprocesses: <command> --version (for the diagnostic banner) and <command> models --refresh <provider> (the actual preflight probe). Both run under ralph.executor.process.run_process() with the _LOCAL_COMMAND_TIMEOUT_SECS cap. Resolution walks the env_path (or PATH) using shutil.which() and os.access so duplicate / non-executable entries are filtered out.

ralph.supervising

Trackable workflow instance model for orchestration use cases.

Exposes the minimum product-facing information an external orchestrator needs to monitor a running Ralph Workflow instance: stable identity, lifecycle status, current pipeline stage, and recent operational activity.

class ralph.supervising.InstanceStatus(*values)[source]

Bases: StrEnum

Lifecycle status of a Ralph Workflow instance.

class ralph.supervising.WorkflowInstanceView(instance_id, run_id, lifecycle_status, current_stage, recent_activity)[source]

Bases: object

Immutable view of a single Ralph Workflow instance for orchestration.

Parameters:
  • instance_id (str)

  • run_id (str | None)

  • lifecycle_status (InstanceStatus)

  • current_stage (str | None)

  • recent_activity (tuple[str, ...])

instance_id

Stable orchestration identity assigned at tracker construction, or the runtime run_id when projected directly from a snapshot. For tracker-based supervision, this is always a non-empty str.

run_id

Optional runtime identifier copied from the live pipeline snapshot. This may be None before startup or when the underlying system does not assign a runtime identity. It is separate from the stable instance_id so that a supervising orchestrator can track the same instance across restarts or reconnects without confusion.

lifecycle_status

Observable lifecycle state of the instance.

current_stage

Active pipeline stage name, or None when no stage is active (including before startup, after terminal states, and when phase is unset).

recent_activity

Recent operational output, ordered oldest to newest.

ralph.supervising.instance_view_from_snapshot(snapshot, *, _instance_id_override=None)[source]

Project a PipelineSnapshot into a WorkflowInstanceView.

When called with _instance_id_override, that stable identity is used and snapshot.run_id is copied to the view’s run_id field. This form is used internally by WorkflowInstanceTracker to preserve the orchestrator-assigned identity while exposing the runtime run_id separately.

When called without an identity override (the default), the view’s instance_id is taken directly from snapshot.run_id. This form is only valid when snapshot.run_id is not None. If snapshot.run_id is None and no override is provided, a ValueError is raised because the supervising contract requires a stable orchestrator-facing identity.

Parameters:
  • snapshot (PipelineSnapshot) – The pipeline snapshot to project.

  • _instance_id_override (str | None) – Stable identity to use instead of snapshot.run_id. Should be supplied by WorkflowInstanceTracker or when the caller needs to project a snapshot without a runtime identity.

Raises:

ValueError – If snapshot.run_id is None and no _instance_id_override is provided. The supervising contract requires a stable identity.

Return type:

WorkflowInstanceView

Utilities

ralph.checkpoint

Pipeline checkpoint state: construction, execution history, and size monitoring.

This package provides the building blocks for saving and inspecting pipeline checkpoints. A checkpoint is written to disk after each phase completes so that an interrupted run can resume from the last completed phase.

Main entry points:

  • CheckpointBuilder — constructs and persists a checkpoint payload to .agent/checkpoint.json.

  • CheckpointPayload — the serialisable checkpoint data model (phase, state, metadata).

  • RunContext — carries per-invocation context (workspace path, session id, config) used by phases and passed into CheckpointBuilder.

  • ExecutionHistory, ExecutionStep, StepOutcome — append-only log of phase outcomes stored inside the checkpoint; used by the recovery controller to decide whether to retry or escalate.

  • CheckpointSizeMonitor, SizeThresholds, SizeAlert, SizeCheckResult — monitors the .agent/ directory size and emits alerts when thresholds are exceeded.

Use ralph --inspect-checkpoint on the CLI to display the current checkpoint.

ralph.checkpoint.builder

Builder helpers for Python checkpoint payload extensions.

class ralph.checkpoint.builder.CheckpointBuilder(_state=None, _run_context=None, _execution_history=<factory>, _working_dir='', _policy=None)[source]

Bases: object

Builder for assembling enriched Python checkpoint payloads.

Parameters:
build()[source]

Build the checkpoint payload or raise if required state is missing.

Return type:

CheckpointPayload

execution_history(execution_history)[source]

Attach bounded execution history.

Parameters:

execution_history (ExecutionHistory)

Return type:

CheckpointBuilder

classmethod new()[source]

Create a fresh checkpoint builder.

Return type:

CheckpointBuilder

pipeline_policy(policy)[source]

Attach the pipeline policy for policy-driven progress derivation.

Parameters:

policy (PipelinePolicy)

Return type:

CheckpointBuilder

run_context(run_context)[source]

Attach run lineage metadata.

Parameters:

run_context (RunContext)

Return type:

CheckpointBuilder

state(state)[source]

Attach the pipeline state.

Parameters:

state (PipelineState)

Return type:

CheckpointBuilder

working_dir(working_dir)[source]

Attach the working directory captured for the checkpoint.

Parameters:

working_dir (str)

Return type:

CheckpointBuilder

ralph.checkpoint.execution_history

Bounded checkpoint execution history models.

class ralph.checkpoint.execution_history.ExecutionHistory(steps=(), file_snapshots=<factory>)[source]

Bases: object

Bounded execution history plus checkpoint-relevant file snapshots.

Parameters:
  • steps (tuple[ExecutionStep, ...])

  • file_snapshots (dict[str, str])

add_step_bounded(step, limit)[source]

Return a copy with the step appended and bounded to the given limit.

Parameters:
Return type:

ExecutionHistory

clone_bounded(limit)[source]

Clone the history while keeping only the most recent steps.

Parameters:

limit (int)

Return type:

ExecutionHistory

classmethod new(file_snapshots=None)[source]

Create an empty execution history.

Parameters:

file_snapshots (dict[str, str] | None)

Return type:

ExecutionHistory

to_dict()[source]

Return a JSON-safe dictionary representation.

Return type:

dict[str, object]

ralph.checkpoint.run_context

Run lineage helpers for checkpoint payloads.

class ralph.checkpoint.run_context.RunContext(run_id, parent_run_id=None, resume_count=0, actual_developer_runs=0, actual_reviewer_runs=0, recovery_cycle_count=0, fallover_history=<factory>, last_failure_category=None)[source]

Bases: object

Track run lineage and actual completed work counts.

Parameters:
  • run_id (str)

  • parent_run_id (str | None)

  • resume_count (int)

  • actual_developer_runs (int)

  • actual_reviewer_runs (int)

  • recovery_cycle_count (int)

  • fallover_history (list[dict[str, object]])

  • last_failure_category (str | None)

classmethod new()[source]

Create a fresh run context.

Return type:

RunContext

record_developer_iteration()[source]

Return a copy with one more completed developer iteration.

Return type:

RunContext

record_reviewer_pass()[source]

Return a copy with one more completed reviewer pass.

Return type:

RunContext

classmethod resumed_from(previous)[source]

Create a new run context for a resumed session.

Parameters:

previous (RunContext)

Return type:

RunContext

to_dict()[source]

Return a JSON-safe dictionary representation.

Return type:

dict[str, object]

ralph.checkpoint.size_monitor

Checkpoint size monitoring helpers.

class ralph.checkpoint.size_monitor.CheckpointSizeMonitor(thresholds=<factory>)[source]

Bases: object

Check serialized checkpoint sizes against configured thresholds.

Parameters:

thresholds (SizeThresholds)

check_json(json_text)[source]

Check a serialized JSON payload by its byte length.

Parameters:

json_text (str)

Return type:

SizeAlert | SizeCheckResult

check_size(size_bytes)[source]

Return the alert level for a serialized checkpoint size.

Parameters:

size_bytes (int)

Return type:

SizeAlert | SizeCheckResult

classmethod new()[source]

Create a monitor with default thresholds.

Return type:

CheckpointSizeMonitor

classmethod with_thresholds(thresholds)[source]

Create a monitor with custom thresholds.

Parameters:

thresholds (SizeThresholds)

Return type:

CheckpointSizeMonitor

ralph.checkpoint.checkpoint_payload

Checkpoint payload model combining state and metadata.

class ralph.checkpoint.checkpoint_payload.CheckpointPayload(state, run_context, execution_history=<factory>, working_dir='')[source]

Bases: object

Checkpoint payload combining pipeline state with extension metadata.

Parameters:
property phase: str

Expose the current phase directly for checkpoint summaries.

to_dict()[source]

Return a JSON-safe dictionary representation.

Return type:

dict[str, object]

ralph.checkpoint.execution_step

Execution step records for checkpoint history.

class ralph.checkpoint.execution_step.ExecutionStep(phase, iteration, step_type, outcome, timestamp=<factory>, agent=None, duration_secs=None)[source]

Bases: object

Single history entry for checkpoint replay and auditing.

Parameters:
  • phase (str)

  • iteration (int)

  • step_type (str)

  • outcome (StepOutcome)

  • timestamp (str)

  • agent (str | None)

  • duration_secs (int | None)

classmethod new(phase, iteration, step_type, outcome)[source]

Create a new execution step.

Parameters:
  • phase (str)

  • iteration (int)

  • step_type (str)

  • outcome (StepOutcome)

Return type:

ExecutionStep

to_dict()[source]

Return a JSON-safe dictionary representation.

Return type:

dict[str, object]

ralph.checkpoint.size_alert

Alert levels for checkpoint size checks.

class ralph.checkpoint.size_alert.SizeAlert(*values)[source]

Bases: StrEnum

Alert level for checkpoint size checks.

ralph.checkpoint.size_check_result

Structured results for checkpoint size checks.

class ralph.checkpoint.size_check_result.SizeCheckResult(level, message=None)[source]

Bases: object

Structured checkpoint size check result.

Parameters:
  • level (str)

  • message (str | None)

ralph.checkpoint.size_thresholds

Threshold values for checkpoint size checks.

class ralph.checkpoint.size_thresholds.SizeThresholds(warn_threshold=1572864, error_threshold=2097152)[source]

Bases: object

Warning and error thresholds in bytes.

Parameters:
  • warn_threshold (int)

  • error_threshold (int)

ralph.checkpoint.step_outcome

Outcome metadata for checkpoint execution steps.

class ralph.checkpoint.step_outcome.StepOutcome(kind, output=None, files_modified=<factory>, exit_code=None, recoverable=None, error=None, completed=None, remaining=None, reason=None)[source]

Bases: object

Outcome metadata for a single execution step.

Parameters:
  • kind (str)

  • output (str | None)

  • files_modified (list[str])

  • exit_code (int | None)

  • recoverable (bool | None)

  • error (str | None)

  • completed (str | None)

  • remaining (str | None)

  • reason (str | None)

classmethod failure(error, *, recoverable)[source]

Create a failure outcome.

Parameters:
  • error (str)

  • recoverable (bool)

Return type:

StepOutcome

classmethod partial(completed, remaining)[source]

Create a partial outcome.

Parameters:
  • completed (str)

  • remaining (str)

Return type:

StepOutcome

classmethod skipped(reason)[source]

Create a skipped outcome.

Parameters:

reason (str)

Return type:

StepOutcome

classmethod success(output=None, files_modified=None)[source]

Create a success outcome.

Parameters:
  • output (str | None)

  • files_modified (list[str] | None)

Return type:

StepOutcome

to_dict()[source]

Return a JSON-safe dictionary representation.

Return type:

dict[str, object]

ralph.diagnostics

Agent and system diagnostics.

This module provides comprehensive diagnostic information for troubleshooting Ralph configuration and environment issues.

class ralph.diagnostics.AgentDiagnostics(total_agents, available_agents, unavailable_agents, agent_status=<factory>)[source]

Bases: object

Diagnostics for all agents.

Parameters:
  • total_agents (int)

  • available_agents (int)

  • unavailable_agents (int)

  • agent_status (list[AgentStatus])

classmethod test(registry, *, is_available_fn=<function _is_agent_available>)[source]

Test agent availability using the given registry.

Parameters:
  • registry (AgentRegistry)

  • is_available_fn (Callable[[str], bool])

Return type:

AgentDiagnostics

class ralph.diagnostics.AgentStatus(name, display_name, available, json_parser, command)[source]

Bases: object

Status of a single agent.

Parameters:
  • name (str)

  • display_name (str)

  • available (bool)

  • json_parser (str)

  • command (str)

class ralph.diagnostics.DiagnosticReport(system, agents, fs_health=None)[source]

Bases: object

Complete diagnostic report combining system and agent information.

Parameters:
system

System information.

Type:

ralph.diagnostics.system_info.SystemInfo

agents

Agent availability diagnostics.

Type:

ralph.diagnostics.agent_diagnostics.AgentDiagnostics

fs_health

Filesystem-environment health for the workspace volume (RFC-013 P4). None when workspace_root was not provided to run_diagnostics.

Type:

ralph.diagnostics.fs_health.FsHealth | None

class ralph.diagnostics.FsHealth(volume_root, spotlight_indexing_enabled=None, fsevents_journal_bytes=None, warnings=<factory>)[source]

Bases: object

Filesystem-environment health snapshot for the workspace volume.

Parameters:
  • volume_root (str)

  • spotlight_indexing_enabled (bool | None)

  • fsevents_journal_bytes (int | None)

  • warnings (list[str])

volume_root

Absolute path to the volume containing the workspace.

Type:

str

spotlight_indexing_enabled

When non-None, True iff Spotlight (mdutil -s) reports “Indexing enabled” on the volume. None when Spotlight status cannot be determined (e.g. non-macOS host, mdutil missing, or subprocess error).

Type:

bool | None

fsevents_journal_bytes

Total size of files under <volume>/.fseventsd. None when the directory cannot be enumerated (locked volume, permissions quirk).

Type:

int | None

warnings

Human-readable operator warnings. Each entry is one diagnostic the operator should act on.

Type:

list[str]

classmethod gather(workspace_root, *, run_command=<function _run_subprocess_mdutil>)[source]

Probe the workspace volume and return a populated FsHealth.

Parameters:
  • workspace_root (Path) – Workspace directory whose containing volume is being probed.

  • run_command (_SubprocessRunner) – Subprocess runner; defaults to subprocess.run. Injectable for tests.

Returns:

FsHealth populated with the volume root, Spotlight status, .fseventsd journal size, and any operator warnings. On non-darwin hosts only volume_root is set.

Return type:

FsHealth

class ralph.diagnostics.SystemInfo(os, arch, working_directory, shell, git_version, git_repo, git_branch, uncommitted_changes)[source]

Bases: object

System information for diagnostics.

Parameters:
  • os (str)

  • arch (str)

  • working_directory (str | None)

  • shell (str | None)

  • git_version (str | None)

  • git_repo (bool)

  • git_branch (str | None)

  • uncommitted_changes (int | None)

classmethod gather(env=None)[source]

Gather system information.

Parameters:

env (Mapping[str, str] | None)

Return type:

SystemInfo

ralph.diagnostics.run_diagnostics(registry, *, env=None, is_available_fn=<function _is_agent_available>, workspace_root=None)[source]

Run all diagnostics and return the combined report.

Parameters:
  • registry (AgentRegistry) – Agent registry to check for diagnostics.

  • env (Mapping[str, str] | None) – Environment mapping for diagnostic commands (defaults to os.environ).

  • is_available_fn (Callable[[str], bool]) – Callable to check if an agent command is available.

  • workspace_root (Path | None) – Optional workspace root. When supplied, the report also includes an FsHealth snapshot for the volume containing the workspace (Spotlight status, .fseventsd journal size, operator warnings).

Returns:

DiagnosticReport containing all diagnostic information.

Return type:

DiagnosticReport

ralph.diagnostics.fs_health

Filesystem-health diagnostics for the workspace volume (macOS-focused).

Long multi-instance runs on an external volume can drive the macOS fseventsd daemon to a full core when (a) Spotlight indexes the churned paths and (b) the volume’s .fseventsd journal bloats. This check surfaces both so operators apply the documented mitigations (see docs/sphinx/diagnostics.md, “External-volume filesystem hygiene”).

class ralph.diagnostics.fs_health.FsHealth(volume_root, spotlight_indexing_enabled=None, fsevents_journal_bytes=None, warnings=<factory>)[source]

Bases: object

Filesystem-environment health snapshot for the workspace volume.

Parameters:
  • volume_root (str)

  • spotlight_indexing_enabled (bool | None)

  • fsevents_journal_bytes (int | None)

  • warnings (list[str])

volume_root

Absolute path to the volume containing the workspace.

Type:

str

spotlight_indexing_enabled

When non-None, True iff Spotlight (mdutil -s) reports “Indexing enabled” on the volume. None when Spotlight status cannot be determined (e.g. non-macOS host, mdutil missing, or subprocess error).

Type:

bool | None

fsevents_journal_bytes

Total size of files under <volume>/.fseventsd. None when the directory cannot be enumerated (locked volume, permissions quirk).

Type:

int | None

warnings

Human-readable operator warnings. Each entry is one diagnostic the operator should act on.

Type:

list[str]

classmethod gather(workspace_root, *, run_command=<function _run_subprocess_mdutil>)[source]

Probe the workspace volume and return a populated FsHealth.

Parameters:
  • workspace_root (Path) – Workspace directory whose containing volume is being probed.

  • run_command (_SubprocessRunner) – Subprocess runner; defaults to subprocess.run. Injectable for tests.

Returns:

FsHealth populated with the volume root, Spotlight status, .fseventsd journal size, and any operator warnings. On non-darwin hosts only volume_root is set.

Return type:

FsHealth

ralph.display

Display helpers for CLI output.

These exports cover progress rendering, phase/status display, and simple table views used by CLI diagnostics and listing commands.

Important

Display Architecture and DI Contract

Single source of truth: DisplayContext is the only permitted source of Console, Theme, terminal width, color policy, display mode, and adaptive character limits. No renderer may construct its own rich.Console.

Single display owner: ParallelDisplay is the single source of truth for all user-facing display logic in Ralph Workflow. All 42 consolidated emit_* methods (41 instance methods on ParallelDisplay plus the module-level emit_activity_line) own every banner, table, panel, and one-shot status surface. The legacy ralph.display.phase_banner, ralph.display.artifact_renderer, ralph.display.first_run_panel, ralph.display.tables, ralph.banner, and ralph.cli.options modules have been deleted. The persistent bottom Status Bar is composed via the ralph.display.status_bar module: StatusBar (a lifecycle class reachable as ParallelDisplay.status_bar) composes the Live region, and the pure free function ralph.display.status_bar.render_status_bar(model, ctx, *, home=None) owns the layout / color / spacing / alignment / truncation logic (its pure-function shape is what makes the layout testable in isolation). The single push-side surface is ParallelDisplay.update_status_bar(model); StatusBar.update(model) stores the model and the persistent footer is rendered on the ralph.display.status_bar._STATUS_BAR_REFRESH_PER_SECOND = 4.0 Hz cadence (i.e. no eager live.refresh() from update). The Status Bar is the single owner of the run-level footer (working directory, active phase, applicable cycle counts) on real-TTY runs, gated on ctx.console.is_terminal AND ctx.console.file.isatty() to stay out of non-interactive output.

DI requirement: Every public emit method on ParallelDisplay is reachable through a DisplayContext; callers resolve an active display via resolve_active_display(display_context) and call display.emit_*. There are no silent Console-only fallbacks in production code. Callers must construct a DisplayContext via make_display_context() before invoking renderers.

Invariant enforcement: tests/display/test_di_invariants.py scans every file under ralph/display/ to assert that Console( and Theme( only appear in theme.py, and that os.environ/os.getenv only appear in context.py and content_condenser.py. The companion tests/display/test_single_mode_anti_drift.py AST-scans ralph/display/ to assert that no future commit re-introduces a compact / medium / wide branch (single default mode is the only owner of display layout).

Display mode (single default): After the wt-028-display consolidation, DisplayContext.mode is always the literal string "default". There is no width-based dispatch, no compact / medium / wide tier, and no per-mode limits table. The historical RALPH_FORCE_NARROW env var is silently ignored. The persistent bottom Status Bar is the single owner of run-level layout, color, spacing, truncation, and live-update behavior. Width-driven degradation happens in the documented order below so the bar always fits ctx.width and remains readable at every applicable width (see ralph.display.status_bar for the full implementation):

  1. Long paths middle-truncate to absorb excess length on long paths.

  2. Long phase labels tail-truncate to absorb excess length on labels.

  3. Iteration label form degrades canonical (Dev 1/3 / Analysis 2/5) -> compact (D1/3 / A2/5) -> minimal (1/3 / 2/5) below the canonical-fit threshold (40 cols).

  4. The phase marker is dropped below the marker-fit threshold.

  5. Per-iteration glyphs are dropped below the glyph-fit threshold.

  6. Iteration segments drop one at a time (outer_dev first, then inner_analysis, then both) below the iteration-visibility threshold (14 cols). Below that threshold the bar degrades cleanly to whatever subset of phase + path fits.

Environment variable precedence (highest to lowest):

  • force_width argument to make_display_context() — overrides terminal width detection.

  • COLUMNS (positive integer) — overrides the console’s auto-detected width.

  • console.width — the default fallback from Rich’s terminal detection.

Color environment variables:

  • NO_COLOR (any value) — disables all color output. Takes precedence over FORCE_COLOR.

  • FORCE_COLOR (any value) — enables color output on non-TTY streams.

Glyph environment variables:

  • RALPH_FORCE_ASCII (1/true/yes/on) — disables Unicode glyphs; renderers use ASCII fallbacks (e.g. -> instead of ).

  • TERM=dumb — disables Unicode glyphs via the same fallback path.

Streaming environment variables:

  • RALPH_STREAMING_DEDUP (0/false/no/off) — disables consecutive-fragment deduplication in streaming blocks.

  • RALPH_STREAMING_CHECKPOINTS (0/false/no/off) — disables periodic checkpoint lines during long streaming blocks.

Long-content environment variables:

  • RALPH_LONG_CONTENT_SUMMARY (0/false/no/off) — disables fallback-headline generation for long content blocks (handled in content_condenser.py).

  • RALPH_LONG_CONTENT_AI_SUMMARY (0/false/no/off) — disables AI-based headline generation for long content blocks.

Width refresh (cross-platform): The runner installs a width refresher via install_width_refresher() at pipeline start. On POSIX this uses a SIGWINCH signal handler; on Windows or non-main threads it falls back to a poll-based daemon thread. Either path calls DisplayContext.refreshed() which re-reads the current terminal width and recomputes adaptive limits while keeping the mode at "default". Renderers that buffer adaptive limits (e.g. PlainLogRenderer) call refreshed() at phase boundaries via flush_blocks() to pick up new sizes. The runner also keeps its live display object and nested plain renderer synced with the refreshed context so later banners and summaries use the new limits. The returned stop callback is invoked on shutdown to clean up any poll thread.

class ralph.display.ParallelDisplay(display_context, *, subscriber=None, workspace_root=None, run_id=None, pipeline_policy=None, is_quiet=False, clock=None, monotonic=None)[source]

Bases: object

Multiplexed terminal display for parallel pipeline workers.

Maintains per-worker RingBuffer instances through an ActivityRouter and renders them as a live Rich table while agents are running.

All display logic lives on this class; the previously separate PlainLogRenderer in ralph.display.plain_renderer has been inlined as private methods and instance state. The 22 state attributes that used to live on _PlainLogRendererBase (run counters, phase counters, active streaming block map, last-emitted tool signatures, last-broadcast signature caches) are documented in __slots__ so the existing __slots__ discipline is preserved.

Parameters:
  • display_context (DisplayContext)

  • subscriber (PipelineSubscriber | None)

  • workspace_root (Path | None)

  • run_id (str | None)

  • pipeline_policy (PipelinePolicy | None)

  • is_quiet (bool)

  • clock (Callable[[], datetime] | None)

  • monotonic (Callable[[], float] | None)

begin_phase(phase)[source]

Start timing a new phase and reset its counters.

Parameters:

phase (str)

Return type:

None

property console: Console

Expose console for external renderers.

property display_context: DisplayContext

Return the DisplayContext this display renders against.

drop_unit(unit_id)[source]

Release per-unit state so long parallel sessions don’t accumulate state across waves.

Removes the unit’s overflow log, overflow-warning flag, drop-warning timestamp, last-emitted tool signature, last worker-state snapshot, active streaming block, last checkpoint char count, and propagates the drop to the embedded ActivityRouter. Safe to call for a unit that was never added; missing entries are silently skipped.

Parameters:

unit_id (str)

Return type:

None

emit(unit_id, line)[source]

Emit a raw line directly to the consolidated log renderer.

Bare lifecycle tokens (e.g. prefixed transcript noise) are silently dropped before reaching the renderer. If unit_id is None, defaults to “run”.

Parameters:
  • unit_id (str | None)

  • line (str)

Return type:

None

emit_activity_line(unit_id, kind, content, *, options=None, condensed_ref=None, condensed_flag=False, summary_line=None, ai_summary_line=None, tool_signature=None)[source]

Emit a kind-tagged, level-badged content line.

Parameters:
  • unit_id (str)

  • kind (str)

  • content (str)

  • options (ActivityLineOptions | None)

  • condensed_ref (str | None)

  • condensed_flag (bool)

  • summary_line (str | None)

  • ai_summary_line (str | None)

  • tool_signature (tuple[str, str] | None)

Return type:

None

emit_agents_table(agents)[source]

Render the agent table for –list-agents.

Port of ralph.cli.options.display_agents_table().

Parameters:

agents (Mapping[str, object])

Return type:

None

emit_analysis_decision(workspace_root, drain)[source]

Render an analysis decision artifact as a titled block.

Port of ralph.display.artifact_renderer.render_analysis_decision().

Parameters:
  • workspace_root (Path)

  • drain (str)

Return type:

None

emit_analysis_result(phase, decision, reason=None)[source]

Emit the analysis-cycle result line.

Composed of an INFO/META header and a body that names the phase, decision, and optional reason; the style is decided by the phase_style_for_phase helper.

Parameters:
  • phase (str)

  • decision (str)

  • reason (str | None)

Return type:

None

emit_blank_line()[source]

Print a single blank line for visual spacing.

Return type:

None

emit_capability_summary(state, *, workspace_root=None)[source]

Print the baseline capabilities summary table.

Port of ralph.cli._capability_summary.print_capability_summary(). The base table and skill-root coverage table are built by the standalone helper module (collected via lazy import to avoid a circular import). The print side goes through self._console.print so the entire transcript is consolidated on ParallelDisplay.

Parameters:
  • state (CapabilityState)

  • workspace_root (Path | None)

Return type:

None

emit_checkpoint_summary_table(options)[source]

Render the checkpoint summary table.

Port of ralph.display.tables.show_checkpoint_summary(). options is a CheckpointSummaryOptions-like object with phase (str) and budget_progress (Mapping[str, tuple[int, int]]).

Parameters:

options (object)

Return type:

None

emit_commit_message(workspace_root)[source]

Render the commit message artifact as a titled block.

Port of ralph.display.artifact_renderer.render_commit_message().

Parameters:

workspace_root (Path)

Return type:

None

emit_completion_summary_panel(snapshot, *, options=None)[source]

Emit the end-of-run completion summary panel.

This is one of the consolidated emit_* methods on the class; the canonical set lives in tests/display/test_parallel_display_drift_prevention.py. The 2-segment [run-completion] section tag is intentionally a companion to [run-end]: [run-end] is the one-line run-stop recap emitted before this method; [run-completion] is the full completion panel emitted at the very end of the run.

Visual-hierarchy contract:

  • Section rule ([run-completion]) is emitted unconditionally (single default-mode layout).

  • The body is delegated to ralph.display.completion_summary.render_completion_summary_group() and printed via self._console.print(group, ...).

  • The body itself begins with a titled Rule (Pipeline Complete / Pipeline Failed); the adjacent section rule and body title Rule are intentional visual punctuation and match the layering pattern used by emit_phase_transition() (section rule + transition banner) and emit_phase_close_banner() (section rule + body that contains titled Rules).

  • The section rule is the stable log-line tag for downstream parsers; the body title Rule is the human-readable title.

Quiet-mode contract:

Unlike every other emit_* method, this method intentionally does NOT short-circuit on self._is_quiet. The completion summary is the only dashboard surface that must remain visible in --quiet mode so the user can see the final pipeline result without re-running with non-quiet verbosity. test_runner_quiet_mode.py::test_quiet_mode_suppresses_dashboard_header_and_phase_banners and tests/integration/test_transcript_end_to_end.py::test_quiet_mode_suppresses_run_start_and_phase_close pin this contract.

Parameters:
  • snapshot (PipelineSnapshot) – The pipeline snapshot to render.

  • options (CompletionSummaryOptions | None) – Optional CompletionSummaryOptions instance. When None (the default), a fresh CompletionSummaryOptions() is constructed.

Return type:

None

emit_config_table(config)[source]

Render the effective config panel for –check-config.

Port of ralph.display.tables.show_config().

Parameters:

config (UnifiedConfig)

Return type:

None

emit_development_artifact(workspace_root)[source]

Render development results using the authoritative Markdown handoff.

Port of ralph.display.artifact_renderer.render_development_artifact().

Parameters:

workspace_root (Path)

Return type:

None

emit_diagnose_inventory_table(rows)[source]

Render the diagnose inventory table.

rows is a list of tuples; each tuple is one row whose items become the cells of that row in column order. The first column is the Server (theme.cat.meta), the second is the Origin, the third is the Transport and the fourth is the Exposure. If a row has fewer than 4 cells the missing cells are filled with "-".

Parameters:

rows (Sequence[tuple[object, ...]])

Return type:

None

emit_diagnose_probe_table(rows)[source]

Render the diagnose probe (transport compatibility) table.

Each row is a 5-tuple: (server, claude, codex, opencode, agy). Missing cells default to "-".

Parameters:

rows (Sequence[tuple[object, ...]])

Return type:

None

emit_diagnose_servers_table(rows)[source]

Render the diagnose MCP servers (custom health) table.

Each row is a 5-tuple: (server, transport, status, tools, detail). Missing cells default to "-".

Parameters:

rows (Sequence[tuple[object, ...]])

Return type:

None

emit_dry_run_summary(*, phase, iterations, details=None)[source]

Render the dry-run summary block for the run command.

details is an optional mapping of extra key/value lines to print after the standard phase / iteration lines.

Parameters:
  • phase (str)

  • iterations (int)

  • details (Mapping[str, object] | None)

Return type:

None

emit_fallback_next_steps(next_steps)[source]

Emit the fallback next-steps list.

Ports ralph.cli.commands.init._print_fallback_next_steps().

Parameters:

next_steps (list[str])

Return type:

None

emit_first_run_panel(content)[source]

Print the first-run welcome Panel to self._ctx.console.

Port of ralph.display.first_run_panel.render_first_run_panel().

Parameters:

content (list[RenderableType])

Return type:

None

emit_fix_artifact(workspace_root)[source]

Render fix result artifacts as a titled block.

Port of ralph.display.artifact_renderer.render_fix_artifact().

Parameters:

workspace_root (Path)

Return type:

None

emit_info_panel(*, title, content)[source]

Render a theme.phase.planning bordered info Panel.

Used by diagnose to surface the “Next steps” panel and any free-form info block. Replaces the inline Panel(...) call in diagnose.py.

Parameters:
  • title (str)

  • content (str)

Return type:

None

emit_log_line(unit_id, line)[source]

Emit a per-unit raw-log line routed through emit_activity_line with kind=raw.

The line is sanitized, timestamped with the configured clock, and rendered with the standard INFO/META badge contract. No-op when is_quiet is true so machine-friendly runs stay clean.

Parameters:
  • unit_id (str)

  • line (str)

Return type:

None

emit_metrics_table(metrics)[source]

Render the metrics table for pipeline summary stats.

Port of ralph.display.tables.show_metrics().

Parameters:

metrics (dict[str, int])

Return type:

None

emit_missing_plan_hint()[source]

Emit a plain INFO line when the plan artifact is absent at phase completion.

Port of ralph.display.artifact_renderer.render_missing_plan_hint().

Return type:

None

emit_parsed_event(unit_id, kind, content, metadata)[source]

Route a pre-parsed agent event through the structured activity path.

Parameters:
  • unit_id (str)

  • kind (ActivityEventKind)

  • content (str | None)

  • metadata (dict[str, object])

Return type:

None

emit_phase_close(phase, produced, *, options=None, phase_role=None, iteration_context=None, exit_trigger=None)[source]

Emit a single-line recap at the end of a phase.

Parameters:
  • phase (str)

  • produced (str)

  • options (PhaseCloseOptions | None)

  • phase_role (str | None)

  • iteration_context (TypeAliasForwardRef('ralph.display.phase_status.PhaseIterationContext') | None)

  • exit_trigger (str | None)

Return type:

None

emit_phase_close_banner(exit_model, *, pipeline_policy=None)[source]

Display the close of a pipeline phase from a lifecycle exit model.

Port of ralph.display.phase_banner.show_phase_close_banner(). The rich, model-based phase-close banner (full stats line, review outcome, debug breadcrumb, and trailing titled Rule).

Note

This method is semantically distinct from the existing emit_phase_close() (one-line recap) and emit_phase_close_from_exit() (one-line recap from a PhaseExitModel). The two recap methods stay unchanged; this banner method is the rich, model-based close banner. Do not collapse the three methods.

Parameters:
Return type:

None

emit_phase_close_from_exit(exit_model)[source]

Emit a phase-close recap from a PhaseExitModel.

Parameters:

exit_model (PhaseExitModel)

Return type:

None

emit_phase_start(phase, *, agent_name=None, pipeline_policy=None)[source]

Display the start of a pipeline phase (no iteration context).

Port of ralph.display.phase_banner.show_phase_start().

Parameters:
  • phase (str)

  • agent_name (str | None)

  • pipeline_policy (PipelinePolicy | None)

Return type:

None

emit_phase_start_from_entry(entry, *, pipeline_policy=None)[source]

Display the start of a pipeline phase from a lifecycle entry model.

Port of ralph.display.phase_banner.show_phase_start_from_entry(). Canonical model-based path (single default-mode layout): emits a titled Rule with phase label, outer development iteration, inner analysis iteration, and an optional agent line.

Parameters:
Return type:

None

emit_phase_transition(from_phase, to_phase, *, context=None, pipeline_policy=None)[source]

Display a visual transition between pipeline phases.

Port of ralph.display.phase_banner.show_phase_transition(). Major transitions get a prominent Rule banner; minor transitions get a simple titled Rule. The leading section rule is always emitted in the single default mode (no per-mode gating remains).

Parameters:
  • from_phase (str)

  • to_phase (str)

  • context (dict[str, object] | None)

  • pipeline_policy (PipelinePolicy | None)

Return type:

None

emit_plan_artifact(workspace_root)[source]

Render the agent-facing plan handoff, falling back to the JSON summary.

Port of ralph.display.artifact_renderer.render_plan_artifact().

Parameters:

workspace_root (Path)

Return type:

None

emit_providers_table(providers)[source]

Render the providers table for –list-providers.

Port of ralph.cli.options.display_providers_table().

Parameters:

providers (list[str])

Return type:

None

emit_renderable(renderable)[source]

Print a pre-built rich Renderable (Table, Panel, Group, …) through the display.

Used by diagnose and smoke tables whose row shape does not match the dedicated emit_diagnose_* / emit_metrics_* helpers. The renderable is printed through self._console so the section-rule contract and quiet-mode suppression still apply.

Parameters:

renderable (object)

Return type:

None

emit_review_artifact(workspace_root)[source]

Render review findings using the authoritative Markdown handoff.

Port of ralph.display.artifact_renderer.render_review_artifact().

Parameters:

workspace_root (Path)

Return type:

None

emit_run_end(*, phase, total_agent_calls=0, pr_url=None, exit_trigger=None, outer_dev_iteration=None)[source]

Emit a one-time run-end orientation block at pipeline stop.

Parameters:
  • phase (str)

  • total_agent_calls (int)

  • pr_url (str | None)

  • exit_trigger (str | None)

  • outer_dev_iteration (int | None)

Return type:

None

emit_run_start(orientation)[source]

Emit a one-time run-start orientation block at pipeline start.

Parameters:

orientation (RunStartOrientation)

Return type:

None

emit_skill_failure_warning(failures)[source]

Emit a single warning line listing the skill-failure entries.

Ports ralph.cli.commands.init._print_skill_failure_warning().

Parameters:

failures (list[str])

Return type:

None

emit_snapshot(snapshot)[source]

Sink for PipelineSubscriber snapshot events.

The constructor wires on_snapshot=self.emit_snapshot. A snapshot becomes a series of INFO/META lines tagged with the snapshot’s unit_id and the originating worker’s metadata.

Parameters:

snapshot (PipelineSnapshot)

Return type:

None

emit_status(message)[source]

Emit a status line through the consolidated display.

Ports the prior _status_text helper in ralph.cli.commands.init (one of the 13+ direct console.print call sites).

Parameters:

message (str)

Return type:

None

emit_status_line(unit_id, status)[source]

Emit a status line with the same TIMESTAMP LEVEL CAT badge as other lines.

No-op when is_quiet is true; quiet-mode machine-friendly runs must not surface per-unit status banners.

Parameters:
  • unit_id (str)

  • status (str)

Return type:

None

emit_warn_line(unit_id, tag, message)[source]

Emit a WARN META line for a specific tag.

Both tag and message are display-bound user-controlled strings. They are sanitized for control characters, embedded newlines, and ANSI escapes before being interpolated into the fixed-format line so a malformed or hostile caller cannot break the transcript line layout or inject control sequences into the user’s scrollback.

Parameters:
  • unit_id (str)

  • tag (str)

  • message (str)

Return type:

None

emit_warning(message)[source]

Emit a warning line through the consolidated display.

Ports the prior warning console.print calls in ralph.cli.commands.init.

Parameters:

message (str)

Return type:

None

emit_welcome_banner(*, version)[source]

Print the Ralph Workflow welcome banner.

Port of ralph.banner.show_banner().

Parameters:

version (str)

Return type:

None

flush_blocks()[source]

Close all open streaming blocks and refresh display context.

Return type:

None

property last_phase_artifact_outcome: str

Return the artifact outcome from the most recently closed phase.

property last_phase_counters: PhaseCounters | None

Return the counters from the most recently closed phase, if available.

Returns None when no phase has been closed yet.

property last_phase_elapsed_seconds: float

Return elapsed time of the most recently closed phase in seconds.

property phase_close_emitted: bool

Return True when emit_phase_close_from_exit was called for the current phase.

record_artifact_outcome(outcome)[source]

Record artifact outcome without emitting a log line.

Parameters:

outcome (str)

Return type:

None

property status_bar: object

Return the composed StatusBar (owner of the persistent footer).

classmethod strip_markup(line)[source]

Strip Rich markup and ANSI escapes from a line, returning plain text.

Parameters:

line (str)

Return type:

str

update_status_bar(model)[source]

Push a new StatusBarModel to the composed StatusBar.

Outside the one-shot emit_* surface; reachable through ParallelDisplay. No-op when the bar is inactive (the model is still stored so the next render can pick it up).

Parameters:

model (object)

Return type:

None

class ralph.display.PhaseIterationContext(outer_dev=None, outer_dev_cap=None, inner_analysis=None, inner_analysis_cap=None)[source]

Bases: object

Canonical iteration context for phase start/close rendering.

Parameters:
  • outer_dev (int | None)

  • outer_dev_cap (int | None)

  • inner_analysis (int | None)

  • inner_analysis_cap (int | None)

outer_dev

Outer development cycle number (None if not in outer loop).

Type:

int | None

outer_dev_cap

Budget cap for outer dev cycles (shows Dev N/cap when set).

Type:

int | None

inner_analysis

Inner analysis cycle number (None if not in analysis).

Type:

int | None

inner_analysis_cap

Max inner analysis cycles (None if unknown).

Type:

int | None

context_labels()[source]

Return (label, style_key) pairs for rendering, in display priority order.

Order: outer dev (highest visibility) → inner analysis.

Return type:

list[tuple[str, str]]

has_context()[source]

Return True if any iteration context is set.

Return type:

bool

class ralph.display.RunStartOrientation(prompt_path=None, developer_agent=None, developer_model=None, developer_iters=None, parallel_max_workers=None, plan_present=False, verbosity=None, workspace_root=None, legend_enabled=True)[source]

Bases: object

Orientation data emitted once at pipeline start as a structured block.

Parameters:
  • prompt_path (str | None)

  • developer_agent (str | None)

  • developer_model (str | None)

  • developer_iters (int | None)

  • parallel_max_workers (int | None)

  • plan_present (bool)

  • verbosity (str | None)

  • workspace_root (str | None)

  • legend_enabled (bool)

class ralph.display.StatusBar(display)[source]

Bases: object

Lifecycle owner for the persistent bottom Status Bar.

The StatusBar is composed by ralph.display.parallel_display.ParallelDisplay and reachable via pd.status_bar. The public push-side surface is ralph.display.parallel_display.ParallelDisplay.update_status_bar() (callers invoke display.update_status_bar(model)); StatusBar.update(model) is the internal storage seam the public method forwards into so the Live region picks the model up on its next refresh tick. The start() and stop() methods are wired through ParallelDisplay’s own start() / stop() lifecycle. Reads happen via last_model.

Parameters:

display (ParallelDisplay)

_display

Same-package reference to the owning ParallelDisplay instance. Reads display._ctx (live DisplayContext that the runner keeps fresh via SIGWINCH / poll refreshers) and display._is_quiet.

_home

Home directory resolved once at construction; passed to render_status_bar so render stays pure.

_model

Last model supplied via update(); None until first update.

_live

Lazily-constructed rich.live.Live instance (or None).

_lock

Threading lock guarding _model assignment.

property is_active: bool

Return True when a Live region is currently active for this StatusBar.

property last_model: StatusBarModel | None

Return the most recent StatusBarModel supplied via update().

start()[source]

Begin rendering the Status Bar inside a transient Rich Live region.

No-op when the real-TTY gate is closed (non-tty console, redirected output, StringIO test console, quiet mode), or when a Live region is already active. Idempotent.

The Live region is constructed with get_renderable=self._renderable so each refresh tick re-reads the latest model — the initial renderable argument is only the first-frame content.

Correctness: _live is committed to self._live ONLY after Live.start() succeeds. If Live.start() raises (e.g. on a console whose Live.start() path is broken, or a parent that suppresses the underlying terminal), the exception is swallowed but self._live stays None. This keeps is_active honest (is_active is defined as self._live is not None) so a later start() retry still succeeds and stop() on an unstarted bar remains a no-op.

Return type:

None

stop()[source]

Tear down the Live region. Idempotent and safe to call without start().

Return type:

None

update(model)[source]

Store model for the Live region to pick up on its next refresh tick.

This is the internal storage seam the public push-side surface ralph.display.parallel_display.ParallelDisplay.update_status_bar() forwards into. Callers should NOT invoke status_bar.update(model) directly; the consolidated contract is display.update_status_bar(model).

On interactive consoles the update is intentionally a pure store: it does NOT force an immediate live.refresh(). The persistent footer is owned by the Live region’s _STATUS_BAR_REFRESH_PER_SECOND cadence (4.0 Hz / 250 ms by default), so update calls feed a fresh StatusBarModel and the next refresh tick renders it. On Rich “dumb terminal” consoles where Live.start() succeeds but Rich refuses to draw frames, the fallback renderer erases the previous fallback row and emits one bounded replacement row so is_active stays observable.

Safe to call before start(); in that case the model is stored and the subsequent start() constructs the Live region using the latest model as its initial renderable. Thread-safe under _lock.

Parameters:

model (StatusBarModel)

Return type:

None

class ralph.display.StatusBarModel(workspace_root, phase_label, phase_style, outer_dev_iteration=None, outer_dev_cap=None, inner_analysis=None, inner_analysis_cap=None)[source]

Bases: object

Immutable view-model for the persistent Status Bar footer.

Parameters:
  • workspace_root (str)

  • phase_label (str)

  • phase_style (str)

  • outer_dev_iteration (int | None)

  • outer_dev_cap (int | None)

  • inner_analysis (int | None)

  • inner_analysis_cap (int | None)

workspace_root

Working-directory path to display.

Type:

str

phase_label

Human-readable phase label (e.g. Development).

Type:

str

phase_style

Rich style string applied to the phase label (e.g. theme.phase.development); also carries textual meaning so the bar is readable when color is disabled.

Type:

str

outer_dev_iteration

Current outer development cycle (1-indexed), or None when the active phase does not track outer progress.

Type:

int | None

outer_dev_cap

Outer development cap, or None when unknown.

Type:

int | None

inner_analysis

Current inner analysis iteration (1-indexed), or None when the active phase does not track analysis cycles.

Type:

int | None

inner_analysis_cap

Inner analysis iteration cap, or None when unknown.

Type:

int | None

ralph.display.build_default_display_legacy_bridge(workspace_root, display_context, pipeline_policy=None, *, is_quiet=False)[source]

Construct the default ParallelDisplay.

Single source of truth that replaces the legacy build_default_display helper from ralph.pipeline.legacy_console_display. Rich is a verified required dependency (declared in pyproject.toml line 22: rich>=13.0) so the construction cannot fail.

Parameters:
Return type:

ParallelDisplay

ralph.display.emit_activity_line(display, unit_id, line, display_context=None)[source]

Emit a raw activity line through the given display, or no-op if None.

Replaces the legacy emit_display_line helper from ralph.pipeline.legacy_console_display. Bare lifecycle lines are dropped by ParallelDisplay itself; this helper just routes the line to the correct unit_id. When display is None but a display_context is provided, the line is written to the context’s console for legacy compatibility.

Parameters:
Return type:

None

ralph.display.format_analysis_cycle(n, cap=None)[source]

Return canonical label for inner analysis cycle (1-indexed).

Parameters:
  • n (int)

  • cap (int | None)

Return type:

str

ralph.display.format_dev_cycle(n, cap=None)[source]

Return canonical label for outer development cycle number (1-indexed).

When cap is provided (and positive), shows Dev N/cap to make the remaining budget immediately visible. Without a cap, shows Dev #N.

Parameters:
  • n (int)

  • cap (int | None)

Return type:

str

ralph.display.get_display_context(display, display_context=None)[source]

Return the DisplayContext a caller should render against.

Single source of truth for the legacy get_display_context helper. The display’s own context is preferred when present (tries display_context first, then _ctx for back-compat with fakes that store it privately); otherwise the caller-provided context is used.

Parameters:
Return type:

DisplayContext

ralph.display.install_sigwinch_refresher(ctx_holder, on_refresh=None)[source]

Install a SIGWINCH handler that refreshes DisplayContext on terminal resize.

On POSIX systems, this installs a signal handler that replaces the DisplayContext in ctx_holder[0] with a refreshed version that reflects the new terminal size. An optional callback can keep any long-lived display objects synced with that refreshed context.

On non-POSIX systems (Windows), this function is a no-op.

Parameters:
  • ctx_holder (list[DisplayContext]) – A single-element list whose 0th element is the DisplayContext to refresh on SIGWINCH. The handler replaces ctx_holder[0] with ctx_holder[0].refreshed().

  • on_refresh (Callable[[DisplayContext], None] | None) – Optional callback invoked with the refreshed context after ctx_holder[0] is replaced.

Return type:

None

Note

This function must be called from the main thread, as signal.signal only works in the main thread. If called from a non-main thread, the function returns silently without installing the handler.

ralph.display.install_width_refresher(ctx_holder, on_refresh=None)[source]

Install a width refresher using the best available strategy.

On POSIX main thread: uses SIGWINCH signal handler (install_sigwinch_refresher). On Windows or non-main thread: falls back to poll-based refresher (install_poll_refresher).

Parameters:
  • ctx_holder (list[DisplayContext]) – A single-element list whose 0th element is the DisplayContext to refresh on resize.

  • on_refresh (Callable[[DisplayContext], None] | None) – Optional callback invoked with the refreshed context after ctx_holder[0] is replaced.

Returns:

A stop() callable (for poll-based refresher; SIGWINCH handler has no cleanup).

Return type:

Callable[[], None]

ralph.display.phase_style_for_phase(phase, pipeline_policy=None)[source]

Public accessor that exposes the private _phase_style helper.

Callers that previously imported phase_style from ralph.display.phase_banner should import this accessor instead so they can route through ParallelDisplay’s consolidated surface.

Parameters:
Return type:

str

ralph.display.render_status_bar(model, ctx, *, home=None)[source]

Render the single-line Status Bar footer for the given model.

This function is PURE: no I/O, no env reads, no Console construction, no Path.home() calls. home is a parameter so callers can supply the resolved home directory once (the StatusBar lifecycle resolves it at construction; tests pass an explicit value).

The single default-mode layout renders phase + dir + (any applicable outer_dev) + (any applicable inner_analysis) at every width where the iteration segments fit. When ctx.width is too narrow to fit the canonical forms (Dev 1/3 / Analysis 2/5) the labels degrade through compact (D1/3 / A2/5) and minimal (1/3 / 2/5) forms, the phase marker and per-iteration glyphs are dropped at the marker-fit / glyph-fit thresholds, and finally the iteration segments drop one at a time at very narrow widths (below 14 cols) so the bar still fits ctx.width.

The phase and path labels are tail/middle truncated to fit the remaining budget. len(text.plain) <= ctx.width always holds (a final Text.truncate clamp covers the 1-2 col edge case where the phase|path separator alone exceeds the budget), and the rendered text never contains a newline.

Parameters:
  • model (StatusBarModel) – Immutable view-model describing the bar contents.

  • ctx (DisplayContext) – Display context providing mode, glyphs, and theme-aware style.

  • home (str | None) – Optional home directory; when supplied and model.workspace_root starts with it, the rendered path is home-relative.

Returns:

A single-line rich.text.Text carrying the bar contents. The rendered text never contains \n so the bar cannot wrap into the working area, and len(text.plain) <= ctx.width so the bar fits any terminal width (including widths below 14 cols where iteration segments drop entirely to honor the len(text.plain) <= ctx.width invariant).

Return type:

Text

ralph.display.resolve_active_display(display, display_context=None)[source]

Return the given display, constructing a ParallelDisplay from the context if needed.

The context is required when display is None. Rich is a required dependency (declared in pyproject.toml line 22: rich>=13.0), so ParallelDisplay always initialises successfully here.

A DisplayContext passed as display is unwrapped to its display_context slot and a fresh ParallelDisplay is constructed, so callers that only have a context still get a real display.

Parameters:
Return type:

ParallelDisplay

ralph.display.resolve_display(display, display_context=None, *, is_quiet=False)[source]

Return the given display or construct one from the context.

Single source of truth that replaces the legacy resolve_display helper from ralph.pipeline.legacy_console_display. Pass-through for non-None inputs; constructs a ParallelDisplay from the supplied context when display is None. When is_quiet=True, the constructed display short-circuits all banner and log-line emissions (see ParallelDisplay quiet-mode contract).

Parameters:
Return type:

ParallelDisplay

ralph.display.status_text(label, value, style)[source]

Build a styled status line as a plain string.

Replaces the legacy status_text helper from ralph.pipeline.legacy_console_display. Returns plain text — the caller passes it through emit_activity_line which uses ParallelDisplay.emit (plain log routing) for rendering.

Parameters:
  • label (str)

  • value (str)

  • style (str)

Return type:

str

ralph.display.strip_markup(line)[source]

Strip Rich markup tags from a line, returning plain text.

Parameters:

line (str)

Return type:

str

ralph.display.subscriber_for_display(display)[source]

Return the pipeline subscriber attached to the given display, when present.

Parameters:

display (ParallelDisplay | None)

Return type:

PipelineSubscriber | None

ralph.display.activity_model

Typed cross-layer activity contract for parser and display integration.

class ralph.display.activity_model.ActivityEventKind(*values)[source]

Bases: StrEnum

Canonical event kinds emitted across providers.

class ralph.display.activity_model.ActivityProvider(*values)[source]

Bases: StrEnum

Canonical provider identity for agent activity events.

Each value is the canonical identity used on the activity-event bus (the AgentActivityEvent.provider field). The enum mirrors AgentTransport for the agents where one identity implies the other (claude, opencode, codex, gemini, agy, generic). Claude Interactive, Nanocoder, and Pi are listed separately because they have their own parsers and the prompt’s “ALL supported agents” requirement means their activity-event stream must also be surfaced through the router /on_event path – not silently collapsed to GENERIC by the CLI-substring detection in detect_provider_from_command.

class ralph.display.activity_model.ActivityVisibilityHint(*values)[source]

Bases: StrEnum

Visibility intent used by later presenter and display layers.

class ralph.display.activity_model.AgentActivityEvent(provider, kind, content=None, metadata=<factory>, visibility=ActivityVisibilityHint.VISIBLE, source='', sequence=None, timestamp=None)[source]

Bases: object

Typed canonical activity event for future parser normalization work.

Parameters:
class ralph.display.activity_model.EventOptions(content=None, metadata=None, visibility=ActivityVisibilityHint.VISIBLE, source='')[source]

Bases: object

Options for constructing an AgentActivityEvent.

Parameters:
ralph.display.activity_model.make_event(*, provider, kind, options=None)[source]

Construct an AgentActivityEvent with an auto-incremented sequence and UTC timestamp.

Parameters:
Return type:

AgentActivityEvent

ralph.display.activity_model.render_event_line(kind, content, *, timestamp=None)[source]

Format a single activity event as a rich-markup string for terminal display.

Parameters:
Return type:

str

ralph.display.activity_event_kind

Canonical activity event kinds.

class ralph.display.activity_event_kind.ActivityEventKind(*values)[source]

Bases: StrEnum

Canonical event kinds emitted across providers.

ralph.display.activity_provider

Canonical provider identities for activity events.

class ralph.display.activity_provider.ActivityProvider(*values)[source]

Bases: StrEnum

Canonical provider identity for agent activity events.

Each value is the canonical identity used on the activity-event bus (the AgentActivityEvent.provider field). The enum mirrors AgentTransport for the agents where one identity implies the other (claude, opencode, codex, gemini, agy, generic). Claude Interactive, Nanocoder, and Pi are listed separately because they have their own parsers and the prompt’s “ALL supported agents” requirement means their activity-event stream must also be surfaced through the router /on_event path – not silently collapsed to GENERIC by the CLI-substring detection in detect_provider_from_command.

ralph.display.activity_provider.provider_for_transport(transport)[source]

Return the canonical ActivityProvider for an AgentTransport value.

Falls back to ActivityProvider.GENERIC when transport is None or unknown so callers can blindly forward optional transport values without worrying about the ActivityProvider enum.

Parameters:

transport (str | None)

Return type:

ActivityProvider

ralph.display.activity_visibility_hint

Visibility hints for activity event presentation.

class ralph.display.activity_visibility_hint.ActivityVisibilityHint(*values)[source]

Bases: StrEnum

Visibility intent used by later presenter and display layers.

ralph.display.activity_router

Activity router: parser → ActivityModel → RingBuffer.

class ralph.display.activity_router.ActivityRouter(*, parser_factory=None, buffer_factory=None, on_event=None, raw_overflow_callback=None)[source]

Bases: object

Wire a per-unit parser to its RingBuffer via the typed activity model.

Each unit_id owns an isolated parser instance and an isolated ring buffer so output from different workers never interferes. Parser exceptions are caught per-line and recorded as ERROR events — a malformed line must never crash the caller.

Parameters:
  • parser_factory (Callable[[ActivityProvider], AgentParser] | None)

  • buffer_factory (Callable[[], RingBuffer] | None)

  • on_event (Callable[[str, ActivityEventKind, str | None, str | None, dict[str, object]], None] | None)

  • raw_overflow_callback (Callable[[str, str], None] | None)

drop_unit(unit_id)[source]

Release per-unit state so long parallel sessions don’t accumulate state across waves.

Removes the unit’s RingBuffer and AgentParser entries from self._buffers and self._parsers so the per-unit memory is released when the unit is no longer needed. Safe to call for a unit that was never added; it just no-ops.

Parameters:

unit_id (str)

Return type:

None

push_raw_line(unit_id, raw_line, *, provider=ActivityProvider.GENERIC, raw_reference=None)[source]

Never raises — parser failures are converted to ERROR events.

Parameters:
  • unit_id (str)

  • raw_line (str)

  • provider (ActivityProvider)

  • raw_reference (str | None)

Return type:

None

ralph.display.activity_router.detect_provider_from_command(command)[source]

Infer the ActivityProvider from the agent command executable name.

Parameters:

command (list[str])

Return type:

ActivityProvider

ralph.display.activity_router.map_parser_type_to_kind(parser_type)[source]

Convert a parser output type string to the canonical ActivityEventKind.

Parameters:

parser_type (str)

Return type:

ActivityEventKind

ralph.display.artifact_reader

Helpers for reading plan and analysis-decision artifacts.

These readers are intentionally tolerant: missing files, malformed JSON, or unexpected schemas all return None rather than raising. This keeps the display resilient when artifacts are partially written or absent (for example during the first iteration before any analysis has run).

class ralph.display.artifact_reader.AnalysisDecisionSummary(drain, decision, reason=None, iso_ts=None)[source]

Bases: object

A stable projection of an *_analysis_decision.json artifact.

Parameters:
  • drain (str)

  • decision (str)

  • reason (str | None)

  • iso_ts (str | None)

class ralph.display.artifact_reader.PlanSummary(summary=None, scope_items=(), total_steps=0, risks_mitigations=<factory>)[source]

Bases: object

A stable, presentation-friendly projection of a plan.json artifact.

Parameters:
  • summary (str | None)

  • scope_items (tuple[str, ...])

  • total_steps (int)

  • risks_mitigations (tuple[str, ...])

ralph.display.artifact_reader.read_latest_analysis_decision(workspace_root, drain)[source]

Read the latest decision artifact for drain.

Looks at {drain}_decision.json first (canonical name used by phase handlers), then {drain}.json.

Parameters:
  • workspace_root (Path)

  • drain (str)

Return type:

AnalysisDecisionSummary | None

ralph.display.artifact_reader.read_plan_artifact(workspace_root)[source]

Read .agent/artifacts/plan.json and project a PlanSummary.

Returns None if the file is missing or malformed beyond recovery. Always returns a populated PlanSummary when the file parses, even if some fields are missing — empty defaults ("", (), 0) are filled in.

Parameters:

workspace_root (Path)

Return type:

PlanSummary | None

ralph.display.context

Single source of truth for Ralph CLI display dependencies.

No renderer may construct its own Console. All display code must receive a DisplayContext (or build one via make_display_context) that owns the console, theme, terminal width, color policy, mode, and adaptive limits.

After the wt-028-display consolidation, DisplayContext.mode is always the string 'default'. There is no width-based dispatch, no compact / medium / wide tier, and no per-mode limits table. The persistent bottom Status Bar is the single owner of run-level layout, color, spacing, truncation, and live-update behavior; width-driven degradation happens in the documented order below so the bar always fits ctx.width and remains readable at every applicable width:

  1. Long paths middle-truncate to absorb excess length on long paths.

  2. Long phase labels tail-truncate to absorb excess length on labels.

  3. Iteration label form degrades canonical (Dev 1/3 / Analysis 2/5) -> compact (D1/3 / A2/5) -> minimal (1/3 / 2/5) below the canonical-fit threshold (40 cols).

  4. The phase marker is dropped below the marker-fit threshold.

  5. Per-iteration glyphs are dropped below the glyph-fit threshold.

  6. Iteration segments drop one at a time (outer_dev first, then inner_analysis, then both) below the iteration-visibility threshold (14 cols). Below that threshold the bar degrades cleanly to whatever subset of phase + path fits.

See ralph.display.status_bar for the full implementation contract and tests/display/test_status_bar.py for the regression suite that locks these invariants end-to-end.

class ralph.display.context.DisplayContext(console, theme, width, mode, color_enabled, glyphs_enabled, headline_max_chars, condenser_soft_limit, condenser_hard_limit, streaming_checkpoint_chars, streaming_checkpoint_fragments, streaming_dedup_enabled, streaming_checkpoints_enabled, thinking_preview_min_chars, tool_result_headline_min_chars, env=<factory>, _resolved_env=<factory>, _force_width=None, _force_glyphs=None)[source]

Bases: object

Immutable container for all display configuration and dependencies.

This is the single source of truth for display behavior. No renderer may construct its own Console. Obtain one via make_display_context().

Parameters:
  • console (Console)

  • theme (Theme)

  • width (int)

  • mode (Literal['default'])

  • color_enabled (bool)

  • glyphs_enabled (bool)

  • headline_max_chars (int)

  • condenser_soft_limit (int)

  • condenser_hard_limit (int)

  • streaming_checkpoint_chars (int)

  • streaming_checkpoint_fragments (int)

  • streaming_dedup_enabled (bool)

  • streaming_checkpoints_enabled (bool)

  • thinking_preview_min_chars (int)

  • tool_result_headline_min_chars (int)

  • env (Mapping[str, str])

  • _resolved_env (_ResolvedEnv)

  • _force_width (int | None)

  • _force_glyphs (bool | None)

console

Rich Console instance for all rendering.

Type:

Console

theme

Rich Theme with Ralph’s Okabe-Ito color palette.

Type:

Theme

width

Effective terminal width in characters.

Type:

int

mode

Display mode. Always 'default' (the single mode).

Type:

Literal[‘default’]

color_enabled

True when color output is enabled.

Type:

bool

glyphs_enabled

True when Unicode glyphs should be used, False for ASCII fallbacks.

Type:

bool

headline_max_chars

Max characters for condensed headlines.

Type:

int

condenser_soft_limit

Soft limit for content condensation.

Type:

int

condenser_hard_limit

Hard limit for content condensation.

Type:

int

streaming_checkpoint_chars

Chars between streaming checkpoints.

Type:

int

streaming_checkpoint_fragments

Emit checkpoint every N fragments.

Type:

int

streaming_dedup_enabled

Whether to deduplicate consecutive identical fragments.

Type:

bool

streaming_checkpoints_enabled

Whether to emit streaming checkpoints.

Type:

bool

thinking_preview_min_chars

Min chars for thinking preview.

Type:

int

tool_result_headline_min_chars

Min chars for tool result headline.

Type:

int

glyph_for(name)[source]

Return the glyph string for the given logical name.

Parameters:

name (str) – Logical glyph name (e.g., ‘success’, ‘error’, ‘milestone’, ‘arrow’).

Returns:

Unicode glyph when glyphs_enabled is True, ASCII fallback otherwise.

Raises:

KeyError – If name is not a known glyph key.

Return type:

str

refreshed()[source]

Return a new DisplayContext with refreshed terminal width.

Re-resolves width using the same precedence rules as make_display_context(), preserving any active overrides (COLUMNS, force_width) stored at construction time. The console identity, theme, color_enabled, glyphs_enabled, and adaptive limits are unchanged. Mode is always 'default'.

Returns:

New DisplayContext with updated width.

Return type:

DisplayContext

ralph.display.context.install_poll_refresher(ctx_holder, interval_seconds=2.0, on_refresh=None)[source]

Start a daemon thread that periodically refreshes DisplayContext.

This provides a fallback for non-POSIX platforms (Windows) where SIGWINCH is not available, or when called from a non-main thread.

Parameters:
  • ctx_holder (list[DisplayContext]) – A single-element list whose 0th element is the DisplayContext to refresh periodically. The thread replaces ctx_holder[0] with ctx_holder[0].refreshed() every interval_seconds.

  • interval_seconds (float) – How often to refresh (default 2.0s).

  • on_refresh (Callable[[DisplayContext], None] | None) – Optional callback invoked with the refreshed context after ctx_holder[0] is replaced.

Returns:

A stop() callable that signals the thread to exit and joins it (1s timeout).

Return type:

Callable[[], None]

ralph.display.context.install_sigwinch_refresher(ctx_holder, on_refresh=None)[source]

Install a SIGWINCH handler that refreshes DisplayContext on terminal resize.

On POSIX systems, this installs a signal handler that replaces the DisplayContext in ctx_holder[0] with a refreshed version that reflects the new terminal size. An optional callback can keep any long-lived display objects synced with that refreshed context.

On non-POSIX systems (Windows), this function is a no-op.

Parameters:
  • ctx_holder (list[DisplayContext]) – A single-element list whose 0th element is the DisplayContext to refresh on SIGWINCH. The handler replaces ctx_holder[0] with ctx_holder[0].refreshed().

  • on_refresh (Callable[[DisplayContext], None] | None) – Optional callback invoked with the refreshed context after ctx_holder[0] is replaced.

Return type:

None

Note

This function must be called from the main thread, as signal.signal only works in the main thread. If called from a non-main thread, the function returns silently without installing the handler.

ralph.display.context.install_width_refresher(ctx_holder, on_refresh=None)[source]

Install a width refresher using the best available strategy.

On POSIX main thread: uses SIGWINCH signal handler (install_sigwinch_refresher). On Windows or non-main thread: falls back to poll-based refresher (install_poll_refresher).

Parameters:
  • ctx_holder (list[DisplayContext]) – A single-element list whose 0th element is the DisplayContext to refresh on resize.

  • on_refresh (Callable[[DisplayContext], None] | None) – Optional callback invoked with the refreshed context after ctx_holder[0] is replaced.

Returns:

A stop() callable (for poll-based refresher; SIGWINCH handler has no cleanup).

Return type:

Callable[[], None]

ralph.display.context.make_display_context(*, env=None, console=None, force_width=None, force_glyphs=None)[source]

Create a DisplayContext with resolved terminal metrics and adaptive limits.

Parameters:
  • env (Mapping[str, str] | None) – Environment mapping (defaults to os.environ).

  • console (Console | None) – Console to use (defaults to make_console() with env-aware color policy).

  • force_width (int | None) – Override terminal width detection.

  • force_glyphs (bool | None) – Override glyph detection (True=Unicode, False=ASCII, None=auto-detect).

Returns:

Fully initialised DisplayContext.

Return type:

DisplayContext

ralph.display.completion_summary

End-of-run completion summary rendering for log-first output.

class ralph.display.completion_summary.CompletionSummaryOptions(workspace_root=None, dropped_count=0, content_block_count=0, thinking_block_count=0, tool_call_count=0, error_count=0, elapsed_seconds=None, overflow_path=None, include_context_sections=True, pipeline_policy=None)[source]

Bases: object

Optional statistics and formatting parameters for completion summary rendering.

Parameters:
  • workspace_root (Path | None)

  • dropped_count (int)

  • content_block_count (int)

  • thinking_block_count (int)

  • tool_call_count (int)

  • error_count (int)

  • elapsed_seconds (float | None)

  • overflow_path (str | None)

  • include_context_sections (bool)

  • pipeline_policy (PipelinePolicy | None)

ralph.display.completion_summary.emit_completion_summary(snapshot, *, display_context, options=None)[source]

Emit the completion summary to the console.

Thin forwarder that delegates to ralph.display.parallel_display.ParallelDisplay.emit_completion_summary_panel(), the 37th consolidated emit_* method. The free-function surface is preserved for backward compatibility with existing call sites and tests; production callers in ralph/pipeline/ route through ParallelDisplay directly.

Parameters:
Return type:

None

ralph.display.completion_summary.make_badge_text(badge, rest)[source]

Build a Text object with a themed badge label followed by muted rest text.

Parameters:
  • badge (str)

  • rest (str)

Return type:

Text

ralph.display.completion_summary.render_completion_summary(snapshot, *, options=None)[source]

Build a rich Text object summarising pipeline completion for the terminal.

Parameters:
Return type:

Text

ralph.display.completion_summary.render_completion_summary_group(snapshot, *, display_context, options=None)[source]

Render the completion summary as a Rich Group with rule-delimited sections.

Parameters:
Return type:

Group

ralph.display.completion_summary.style_for_role(role, pipeline_policy)[source]

Return the style for the first phase with the given role, or muted when none matches.

Parameters:
Return type:

str

ralph.display.completion_summary.style_for_terminal_failure(pipeline_policy)[source]

Return the style for the terminal failure phase, or the failed theme default.

Parameters:

pipeline_policy (PipelinePolicy | None)

Return type:

str

ralph.display.content_condenser

Predictable head+tail condensation for oversized content lines.

class ralph.display.content_condenser.CondenseOptions(soft_limit=400, hard_limit=4000, overflow_ref=None, summary=False, env=None)[source]

Bases: object

Options for content condensation.

Parameters:
  • soft_limit (int)

  • hard_limit (int)

  • overflow_ref (str | None)

  • summary (bool)

  • env (Mapping[str, str] | None)

soft_limit

Display cell width before showing truncation hint.

Type:

int

hard_limit

Display cell width before switching to head+tail mode.

Type:

int

overflow_ref

Reference string embedded in truncation suffix.

Type:

str | None

summary

Whether to generate summary lines.

Type:

bool

env

Environment variables mapping for AI summary hooks.

Type:

Mapping[str, str] | None

ralph.display.content_condenser.condense_content(text, *, options=None)[source]

Condense text so it fits within display limits.

Pass a CondenseOptions to configure limits, overflow ref, and summarization. Omit to use defaults.

Returns (visible, condensed_flag) when options.summary is False. Returns (visible, condensed_flag, summary_line, ai_summary_line) when options.summary is True.

Rules: - If cell_len(text) <= soft_limit: return (text, False[, None, None]) - If cell_len(text) <= hard_limit: head-only truncation with suffix - If cell_len(text) > hard_limit: head + tail with middle elided

Parameters:
Return type:

tuple[str, bool] | tuple[str, bool, str | None, str | None]

ralph.display.lifecycle_filter

Shared lifecycle-line filter used by all display intake paths.

BARE_LIFECYCLE_TOKENS is the union of parser-only lifecycle markers from Claude, Codex, Gemini, OpenCode, and the generic parser. These markers carry no user payload and are suppressed before they can surface as display content.

ralph.display.lifecycle_filter.is_bare_lifecycle(line)[source]

Return True when line is a bare lifecycle token with no user payload.

Strips an optional “<provider>/<model>: “ prefix before checking. Only exact token matches are suppressed; longer content strings pass through. Lines containing “✗:” are never suppressed (they are real error lines).

Parameters:

line (str)

Return type:

bool

ralph.display.line_sanitizer

Line sanitization for safe display in terminal UI.

Handles oversize lines (truncated at max_chars with ‘…’ suffix), binary bytes (decoded with errors=’replace’), CRLF normalization, control character stripping (tab preserved), and emoji preservation.

ralph.display.line_sanitizer.sanitize_display_line(raw, max_chars=200)[source]

Sanitize a raw agent output line for safe terminal display.

Parameters:
  • raw (bytes | str)

  • max_chars (int)

Return type:

str

ralph.display.vt_normalizer

Utilities for normalizing VT/TUI output into stable semantic text.

The goal is not pixel-perfect replay. It is to collapse common terminal repaint noise into a transcript surface that downstream Claude-interactive parsers can reason about without being tightly coupled to one specific TUI paint pattern.

ralph.display.vt_normalizer.normalize_vt_text(raw)[source]

Strip ANSI control noise and collapse carriage-return repaints.

Carriage returns are treated as “rewrite the current line” markers instead of semantic newlines so spinner updates and partial repaints do not create duplicate transcript entries.

A lone \r clears the current accumulated line (rewrite). However, \r\r\n or \r\n at content boundaries is treated as a line break, not a double rewrite, to avoid discarding menu prompts and other multi-line TUI content that uses CR as a cheap line separator.

Parameters:

raw (str)

Return type:

str

ralph.display.long_content_summary

Default-on summary layer for oversized agent content blocks.

When content exceeds 4000 display cells a deterministic headline summary is extracted from the already-AI-produced text. This is additive: head+tail condensation remains the trusted default; the summary is an extra layer for quick context.

Set RALPH_LONG_CONTENT_SUMMARY=0 (or ‘false’/’no’/’off’) to disable. When unset or empty the summary is enabled for content above the threshold. The summary is derived from the first sentence of the content (markdown headings stripped), capped at 120 characters. No external AI call is made.

Note: the sentence splitter is intentionally simple — it may truncate on abbreviations (‘e.g.’) or URLs. The summary is additive and labelled ‘↳ summary:’ so a wrong headline never obscures the raw content.

Optional AI-generated summary layer (default-OFF): Set RALPH_LONG_CONTENT_AI_SUMMARY=1 AND register a hook via set_ai_summary_hook() to enable. The hook receives the raw text and must return a string or None. Exceptions are swallowed. Output is capped at 400 characters. Labelled ‘↳ ai-summary:’ to distinguish from the deterministic headline.

ralph.display.long_content_summary.build_ai_summary(text, env)[source]

Return an AI-generated summary string, or None when disabled/unavailable.

Requires RALPH_LONG_CONTENT_AI_SUMMARY=1 in env AND a registered hook AND text above the threshold. Hook exceptions are swallowed. Output is capped at 400 chars with an ellipsis suffix.

Parameters:
  • text (str)

  • env (Mapping[str, str])

Return type:

str | None

ralph.display.long_content_summary.build_content_summary(text, max_chars=200)[source]

Extract the first sentence, strip markdown prefixes, truncate to max_chars.

Falls back to the first non-empty line when no sentence terminator is found. Returns ‘’ when no non-empty line exists.

Parameters:
  • text (str)

  • max_chars (int)

Return type:

str

ralph.display.long_content_summary.build_headline_or_placeholder(text, max_chars=120)[source]

Extract headline; return placeholder when no headline can be extracted.

Parameters:
  • text (str)

  • max_chars (int)

Return type:

str

ralph.display.long_content_summary.build_headline_summary(text, max_chars=120)[source]

Extract a short headline from text, capped at max_chars.

Parameters:
  • text (str)

  • max_chars (int)

Return type:

str

ralph.display.long_content_summary.get_ai_summary_hook()[source]

Return the current AI summary hook. Thread-safe atomic read.

Return type:

Callable[[str], str | None] | None

ralph.display.long_content_summary.set_ai_summary_hook(hook)[source]

Register (or clear) the AI summary hook. Thread-safe.

Parameters:

hook (Callable[[str], str | None] | None)

Return type:

None

ralph.display.long_content_summary.should_summarize(text, env)[source]

Return True when text exceeds the threshold and the kill-switch is not set.

Parameters:
  • text (str)

  • env (Mapping[str, str])

Return type:

bool

ralph.display.mode

Display mode constants for Ralph’s terminal output.

After the wt-028-display consolidation, Ralph Workflow exposes exactly ONE display mode: default. There are no narrow / medium / wide tiers, no width-based adaptive limits, and no width-derived dispatch. The persistent bottom Status Bar is the single owner of run-level layout, color, spacing, truncation, and live-update behavior. Width-driven degradation happens in a documented order (see ralph.display.status_bar):

  1. Long paths middle-truncate to absorb excess length on long paths.

  2. Long phase labels tail-truncate to absorb excess length on labels.

  3. Iteration label form degrades canonical (Dev 1/3 / Analysis 2/5) -> compact (D1/3 / A2/5) -> minimal (1/3 / 2/5) below the canonical-fit threshold (40 cols).

  4. The phase marker is dropped below the marker-fit threshold.

  5. Per-iteration glyphs are dropped below the glyph-fit threshold.

  6. Iteration segments drop one at a time (outer_dev first, then inner_analysis, then both) below the iteration-visibility threshold (14 cols). Below that threshold the bar degrades cleanly to whatever subset of phase + path fits, and the len(plain) <= ctx.width invariant holds at every width.

The historical width-tier threshold constants and the historical RALPH_FORCE_NARROW environment variable are removed; the env var is silently ignored.

ralph.display.parallel_display

Parallel display adapter: always emit log-first, copy-paste-safe transcript lines.

wt-007-consolidate-display: All display logic is consolidated onto this class. Forty-one instance methods (plus the module-level emit_activity_line) own every user-facing banner, table, panel, and status surface. Error messages route through the existing emit_warning method with theme.status.error styling; no separate emit_error method exists.

The 41 consolidated names (run lifecycle / phase banners / artifact renderers / tables and panels / status and warnings / first-run and welcome / helpers):

Run lifecycle

emit_run_start, emit_run_end, emit_parsed_event, emit_analysis_result, emit_completion_summary_panel

Phase banners

emit_phase_start, emit_phase_start_from_entry, emit_phase_transition, emit_phase_close, emit_phase_close_from_exit, emit_phase_close_banner

Artifact renderers

emit_plan_artifact, emit_development_artifact, emit_review_artifact, emit_fix_artifact, emit_analysis_decision, emit_commit_message, emit_missing_plan_hint

Tables and panels

emit_agents_table, emit_providers_table, emit_config_table, emit_metrics_table, emit_checkpoint_summary_table, emit_diagnose_inventory_table, emit_diagnose_probe_table, emit_diagnose_servers_table, emit_capability_summary, emit_info_panel

Status and warnings

emit_status, emit_warning, emit_skill_failure_warning, emit_fallback_next_steps

First-run and welcome

emit_welcome_banner, emit_first_run_panel

Helpers

emit_blank_line, emit_dry_run_summary

Plus the module-level emit_activity_line (1 name).

Migrated from (consolidation map)
ralph.display.phase_banner.show_phase_start

-> ParallelDisplay.emit_phase_start

ralph.display.phase_banner.show_phase_start_from_entry

-> ParallelDisplay.emit_phase_start_from_entry

ralph.display.phase_banner.show_phase_transition

-> ParallelDisplay.emit_phase_transition

ralph.display.phase_banner.show_phase_close_banner

-> ParallelDisplay.emit_phase_close_banner

ralph.display.phase_banner.phase_style

-> ParallelDisplay.phase_style_for_phase (public accessor)

ralph.display.artifact_renderer.render_plan_artifact

-> ParallelDisplay.emit_plan_artifact

ralph.display.artifact_renderer.render_development_artifact

-> ParallelDisplay.emit_development_artifact

ralph.display.artifact_renderer.render_review_artifact

-> ParallelDisplay.emit_review_artifact

ralph.display.artifact_renderer.render_fix_artifact

-> ParallelDisplay.emit_fix_artifact

ralph.display.artifact_renderer.render_analysis_decision

-> ParallelDisplay.emit_analysis_decision

ralph.display.artifact_renderer.render_commit_message

-> ParallelDisplay.emit_commit_message

ralph.display.artifact_renderer.render_missing_plan_hint

-> ParallelDisplay.emit_missing_plan_hint

ralph.display.first_run_panel.render_first_run_panel

-> ParallelDisplay.emit_first_run_panel

ralph.display.tables.show_metrics

-> ParallelDisplay.emit_metrics_table

ralph.display.tables.show_checkpoint_summary

-> ParallelDisplay.emit_checkpoint_summary_table

ralph.display.tables.show_agents

-> ParallelDisplay.emit_agents_table

ralph.display.tables.show_providers

-> ParallelDisplay.emit_providers_table

ralph.display.tables.show_config

-> ParallelDisplay.emit_config_table

ralph.banner.show_banner

-> ParallelDisplay.emit_welcome_banner

ralph.cli.options.display_agents_table

-> ParallelDisplay.emit_agents_table

ralph.cli.options.display_providers_table

-> ParallelDisplay.emit_providers_table

ralph.display.plain_renderer.PlainLogRenderer

-> ParallelDisplay (inlined as private methods and instance state)

class ralph.display.parallel_display.ParallelDisplay(display_context, *, subscriber=None, workspace_root=None, run_id=None, pipeline_policy=None, is_quiet=False, clock=None, monotonic=None)[source]

Bases: object

Multiplexed terminal display for parallel pipeline workers.

Maintains per-worker RingBuffer instances through an ActivityRouter and renders them as a live Rich table while agents are running.

All display logic lives on this class; the previously separate PlainLogRenderer in ralph.display.plain_renderer has been inlined as private methods and instance state. The 22 state attributes that used to live on _PlainLogRendererBase (run counters, phase counters, active streaming block map, last-emitted tool signatures, last-broadcast signature caches) are documented in __slots__ so the existing __slots__ discipline is preserved.

Parameters:
  • display_context (DisplayContext)

  • subscriber (PipelineSubscriber | None)

  • workspace_root (Path | None)

  • run_id (str | None)

  • pipeline_policy (PipelinePolicy | None)

  • is_quiet (bool)

  • clock (Callable[[], datetime] | None)

  • monotonic (Callable[[], float] | None)

begin_phase(phase)[source]

Start timing a new phase and reset its counters.

Parameters:

phase (str)

Return type:

None

property console: Console

Expose console for external renderers.

property display_context: DisplayContext

Return the DisplayContext this display renders against.

drop_unit(unit_id)[source]

Release per-unit state so long parallel sessions don’t accumulate state across waves.

Removes the unit’s overflow log, overflow-warning flag, drop-warning timestamp, last-emitted tool signature, last worker-state snapshot, active streaming block, last checkpoint char count, and propagates the drop to the embedded ActivityRouter. Safe to call for a unit that was never added; missing entries are silently skipped.

Parameters:

unit_id (str)

Return type:

None

emit(unit_id, line)[source]

Emit a raw line directly to the consolidated log renderer.

Bare lifecycle tokens (e.g. prefixed transcript noise) are silently dropped before reaching the renderer. If unit_id is None, defaults to “run”.

Parameters:
  • unit_id (str | None)

  • line (str)

Return type:

None

emit_activity_line(unit_id, kind, content, *, options=None, condensed_ref=None, condensed_flag=False, summary_line=None, ai_summary_line=None, tool_signature=None)[source]

Emit a kind-tagged, level-badged content line.

Parameters:
  • unit_id (str)

  • kind (str)

  • content (str)

  • options (ActivityLineOptions | None)

  • condensed_ref (str | None)

  • condensed_flag (bool)

  • summary_line (str | None)

  • ai_summary_line (str | None)

  • tool_signature (tuple[str, str] | None)

Return type:

None

emit_agents_table(agents)[source]

Render the agent table for –list-agents.

Port of ralph.cli.options.display_agents_table().

Parameters:

agents (Mapping[str, object])

Return type:

None

emit_analysis_decision(workspace_root, drain)[source]

Render an analysis decision artifact as a titled block.

Port of ralph.display.artifact_renderer.render_analysis_decision().

Parameters:
  • workspace_root (Path)

  • drain (str)

Return type:

None

emit_analysis_result(phase, decision, reason=None)[source]

Emit the analysis-cycle result line.

Composed of an INFO/META header and a body that names the phase, decision, and optional reason; the style is decided by the phase_style_for_phase helper.

Parameters:
  • phase (str)

  • decision (str)

  • reason (str | None)

Return type:

None

emit_blank_line()[source]

Print a single blank line for visual spacing.

Return type:

None

emit_capability_summary(state, *, workspace_root=None)[source]

Print the baseline capabilities summary table.

Port of ralph.cli._capability_summary.print_capability_summary(). The base table and skill-root coverage table are built by the standalone helper module (collected via lazy import to avoid a circular import). The print side goes through self._console.print so the entire transcript is consolidated on ParallelDisplay.

Parameters:
  • state (CapabilityState)

  • workspace_root (Path | None)

Return type:

None

emit_checkpoint_summary_table(options)[source]

Render the checkpoint summary table.

Port of ralph.display.tables.show_checkpoint_summary(). options is a CheckpointSummaryOptions-like object with phase (str) and budget_progress (Mapping[str, tuple[int, int]]).

Parameters:

options (object)

Return type:

None

emit_commit_message(workspace_root)[source]

Render the commit message artifact as a titled block.

Port of ralph.display.artifact_renderer.render_commit_message().

Parameters:

workspace_root (Path)

Return type:

None

emit_completion_summary_panel(snapshot, *, options=None)[source]

Emit the end-of-run completion summary panel.

This is one of the consolidated emit_* methods on the class; the canonical set lives in tests/display/test_parallel_display_drift_prevention.py. The 2-segment [run-completion] section tag is intentionally a companion to [run-end]: [run-end] is the one-line run-stop recap emitted before this method; [run-completion] is the full completion panel emitted at the very end of the run.

Visual-hierarchy contract:

  • Section rule ([run-completion]) is emitted unconditionally (single default-mode layout).

  • The body is delegated to ralph.display.completion_summary.render_completion_summary_group() and printed via self._console.print(group, ...).

  • The body itself begins with a titled Rule (Pipeline Complete / Pipeline Failed); the adjacent section rule and body title Rule are intentional visual punctuation and match the layering pattern used by emit_phase_transition() (section rule + transition banner) and emit_phase_close_banner() (section rule + body that contains titled Rules).

  • The section rule is the stable log-line tag for downstream parsers; the body title Rule is the human-readable title.

Quiet-mode contract:

Unlike every other emit_* method, this method intentionally does NOT short-circuit on self._is_quiet. The completion summary is the only dashboard surface that must remain visible in --quiet mode so the user can see the final pipeline result without re-running with non-quiet verbosity. test_runner_quiet_mode.py::test_quiet_mode_suppresses_dashboard_header_and_phase_banners and tests/integration/test_transcript_end_to_end.py::test_quiet_mode_suppresses_run_start_and_phase_close pin this contract.

Parameters:
  • snapshot (PipelineSnapshot) – The pipeline snapshot to render.

  • options (CompletionSummaryOptions | None) – Optional CompletionSummaryOptions instance. When None (the default), a fresh CompletionSummaryOptions() is constructed.

Return type:

None

emit_config_table(config)[source]

Render the effective config panel for –check-config.

Port of ralph.display.tables.show_config().

Parameters:

config (UnifiedConfig)

Return type:

None

emit_development_artifact(workspace_root)[source]

Render development results using the authoritative Markdown handoff.

Port of ralph.display.artifact_renderer.render_development_artifact().

Parameters:

workspace_root (Path)

Return type:

None

emit_diagnose_inventory_table(rows)[source]

Render the diagnose inventory table.

rows is a list of tuples; each tuple is one row whose items become the cells of that row in column order. The first column is the Server (theme.cat.meta), the second is the Origin, the third is the Transport and the fourth is the Exposure. If a row has fewer than 4 cells the missing cells are filled with "-".

Parameters:

rows (Sequence[tuple[object, ...]])

Return type:

None

emit_diagnose_probe_table(rows)[source]

Render the diagnose probe (transport compatibility) table.

Each row is a 5-tuple: (server, claude, codex, opencode, agy). Missing cells default to "-".

Parameters:

rows (Sequence[tuple[object, ...]])

Return type:

None

emit_diagnose_servers_table(rows)[source]

Render the diagnose MCP servers (custom health) table.

Each row is a 5-tuple: (server, transport, status, tools, detail). Missing cells default to "-".

Parameters:

rows (Sequence[tuple[object, ...]])

Return type:

None

emit_dry_run_summary(*, phase, iterations, details=None)[source]

Render the dry-run summary block for the run command.

details is an optional mapping of extra key/value lines to print after the standard phase / iteration lines.

Parameters:
  • phase (str)

  • iterations (int)

  • details (Mapping[str, object] | None)

Return type:

None

emit_fallback_next_steps(next_steps)[source]

Emit the fallback next-steps list.

Ports ralph.cli.commands.init._print_fallback_next_steps().

Parameters:

next_steps (list[str])

Return type:

None

emit_first_run_panel(content)[source]

Print the first-run welcome Panel to self._ctx.console.

Port of ralph.display.first_run_panel.render_first_run_panel().

Parameters:

content (list[RenderableType])

Return type:

None

emit_fix_artifact(workspace_root)[source]

Render fix result artifacts as a titled block.

Port of ralph.display.artifact_renderer.render_fix_artifact().

Parameters:

workspace_root (Path)

Return type:

None

emit_info_panel(*, title, content)[source]

Render a theme.phase.planning bordered info Panel.

Used by diagnose to surface the “Next steps” panel and any free-form info block. Replaces the inline Panel(...) call in diagnose.py.

Parameters:
  • title (str)

  • content (str)

Return type:

None

emit_log_line(unit_id, line)[source]

Emit a per-unit raw-log line routed through emit_activity_line with kind=raw.

The line is sanitized, timestamped with the configured clock, and rendered with the standard INFO/META badge contract. No-op when is_quiet is true so machine-friendly runs stay clean.

Parameters:
  • unit_id (str)

  • line (str)

Return type:

None

emit_metrics_table(metrics)[source]

Render the metrics table for pipeline summary stats.

Port of ralph.display.tables.show_metrics().

Parameters:

metrics (dict[str, int])

Return type:

None

emit_missing_plan_hint()[source]

Emit a plain INFO line when the plan artifact is absent at phase completion.

Port of ralph.display.artifact_renderer.render_missing_plan_hint().

Return type:

None

emit_parsed_event(unit_id, kind, content, metadata)[source]

Route a pre-parsed agent event through the structured activity path.

Parameters:
  • unit_id (str)

  • kind (ActivityEventKind)

  • content (str | None)

  • metadata (dict[str, object])

Return type:

None

emit_phase_close(phase, produced, *, options=None, phase_role=None, iteration_context=None, exit_trigger=None)[source]

Emit a single-line recap at the end of a phase.

Parameters:
  • phase (str)

  • produced (str)

  • options (PhaseCloseOptions | None)

  • phase_role (str | None)

  • iteration_context (TypeAliasForwardRef('ralph.display.phase_status.PhaseIterationContext') | None)

  • exit_trigger (str | None)

Return type:

None

emit_phase_close_banner(exit_model, *, pipeline_policy=None)[source]

Display the close of a pipeline phase from a lifecycle exit model.

Port of ralph.display.phase_banner.show_phase_close_banner(). The rich, model-based phase-close banner (full stats line, review outcome, debug breadcrumb, and trailing titled Rule).

Note

This method is semantically distinct from the existing emit_phase_close() (one-line recap) and emit_phase_close_from_exit() (one-line recap from a PhaseExitModel). The two recap methods stay unchanged; this banner method is the rich, model-based close banner. Do not collapse the three methods.

Parameters:
Return type:

None

emit_phase_close_from_exit(exit_model)[source]

Emit a phase-close recap from a PhaseExitModel.

Parameters:

exit_model (PhaseExitModel)

Return type:

None

emit_phase_start(phase, *, agent_name=None, pipeline_policy=None)[source]

Display the start of a pipeline phase (no iteration context).

Port of ralph.display.phase_banner.show_phase_start().

Parameters:
  • phase (str)

  • agent_name (str | None)

  • pipeline_policy (PipelinePolicy | None)

Return type:

None

emit_phase_start_from_entry(entry, *, pipeline_policy=None)[source]

Display the start of a pipeline phase from a lifecycle entry model.

Port of ralph.display.phase_banner.show_phase_start_from_entry(). Canonical model-based path (single default-mode layout): emits a titled Rule with phase label, outer development iteration, inner analysis iteration, and an optional agent line.

Parameters:
Return type:

None

emit_phase_transition(from_phase, to_phase, *, context=None, pipeline_policy=None)[source]

Display a visual transition between pipeline phases.

Port of ralph.display.phase_banner.show_phase_transition(). Major transitions get a prominent Rule banner; minor transitions get a simple titled Rule. The leading section rule is always emitted in the single default mode (no per-mode gating remains).

Parameters:
  • from_phase (str)

  • to_phase (str)

  • context (dict[str, object] | None)

  • pipeline_policy (PipelinePolicy | None)

Return type:

None

emit_plan_artifact(workspace_root)[source]

Render the agent-facing plan handoff, falling back to the JSON summary.

Port of ralph.display.artifact_renderer.render_plan_artifact().

Parameters:

workspace_root (Path)

Return type:

None

emit_providers_table(providers)[source]

Render the providers table for –list-providers.

Port of ralph.cli.options.display_providers_table().

Parameters:

providers (list[str])

Return type:

None

emit_renderable(renderable)[source]

Print a pre-built rich Renderable (Table, Panel, Group, …) through the display.

Used by diagnose and smoke tables whose row shape does not match the dedicated emit_diagnose_* / emit_metrics_* helpers. The renderable is printed through self._console so the section-rule contract and quiet-mode suppression still apply.

Parameters:

renderable (object)

Return type:

None

emit_review_artifact(workspace_root)[source]

Render review findings using the authoritative Markdown handoff.

Port of ralph.display.artifact_renderer.render_review_artifact().

Parameters:

workspace_root (Path)

Return type:

None

emit_run_end(*, phase, total_agent_calls=0, pr_url=None, exit_trigger=None, outer_dev_iteration=None)[source]

Emit a one-time run-end orientation block at pipeline stop.

Parameters:
  • phase (str)

  • total_agent_calls (int)

  • pr_url (str | None)

  • exit_trigger (str | None)

  • outer_dev_iteration (int | None)

Return type:

None

emit_run_start(orientation)[source]

Emit a one-time run-start orientation block at pipeline start.

Parameters:

orientation (RunStartOrientation)

Return type:

None

emit_skill_failure_warning(failures)[source]

Emit a single warning line listing the skill-failure entries.

Ports ralph.cli.commands.init._print_skill_failure_warning().

Parameters:

failures (list[str])

Return type:

None

emit_snapshot(snapshot)[source]

Sink for PipelineSubscriber snapshot events.

The constructor wires on_snapshot=self.emit_snapshot. A snapshot becomes a series of INFO/META lines tagged with the snapshot’s unit_id and the originating worker’s metadata.

Parameters:

snapshot (PipelineSnapshot)

Return type:

None

emit_status(message)[source]

Emit a status line through the consolidated display.

Ports the prior _status_text helper in ralph.cli.commands.init (one of the 13+ direct console.print call sites).

Parameters:

message (str)

Return type:

None

emit_status_line(unit_id, status)[source]

Emit a status line with the same TIMESTAMP LEVEL CAT badge as other lines.

No-op when is_quiet is true; quiet-mode machine-friendly runs must not surface per-unit status banners.

Parameters:
  • unit_id (str)

  • status (str)

Return type:

None

emit_warn_line(unit_id, tag, message)[source]

Emit a WARN META line for a specific tag.

Both tag and message are display-bound user-controlled strings. They are sanitized for control characters, embedded newlines, and ANSI escapes before being interpolated into the fixed-format line so a malformed or hostile caller cannot break the transcript line layout or inject control sequences into the user’s scrollback.

Parameters:
  • unit_id (str)

  • tag (str)

  • message (str)

Return type:

None

emit_warning(message)[source]

Emit a warning line through the consolidated display.

Ports the prior warning console.print calls in ralph.cli.commands.init.

Parameters:

message (str)

Return type:

None

emit_welcome_banner(*, version)[source]

Print the Ralph Workflow welcome banner.

Port of ralph.banner.show_banner().

Parameters:

version (str)

Return type:

None

flush_blocks()[source]

Close all open streaming blocks and refresh display context.

Return type:

None

property last_phase_artifact_outcome: str

Return the artifact outcome from the most recently closed phase.

property last_phase_counters: PhaseCounters | None

Return the counters from the most recently closed phase, if available.

Returns None when no phase has been closed yet.

property last_phase_elapsed_seconds: float

Return elapsed time of the most recently closed phase in seconds.

property phase_close_emitted: bool

Return True when emit_phase_close_from_exit was called for the current phase.

record_artifact_outcome(outcome)[source]

Record artifact outcome without emitting a log line.

Parameters:

outcome (str)

Return type:

None

property status_bar: object

Return the composed StatusBar (owner of the persistent footer).

classmethod strip_markup(line)[source]

Strip Rich markup and ANSI escapes from a line, returning plain text.

Parameters:

line (str)

Return type:

str

update_status_bar(model)[source]

Push a new StatusBarModel to the composed StatusBar.

Outside the one-shot emit_* surface; reachable through ParallelDisplay. No-op when the bar is inactive (the model is still stored so the next render can pick it up).

Parameters:

model (object)

Return type:

None

ralph.display.parallel_display.build_default_display_legacy_bridge(workspace_root, display_context, pipeline_policy=None, *, is_quiet=False)[source]

Construct the default ParallelDisplay.

Single source of truth that replaces the legacy build_default_display helper from ralph.pipeline.legacy_console_display. Rich is a verified required dependency (declared in pyproject.toml line 22: rich>=13.0) so the construction cannot fail.

Parameters:
Return type:

ParallelDisplay

ralph.display.parallel_display.emit_activity_line(display, unit_id, line, display_context=None)[source]

Emit a raw activity line through the given display, or no-op if None.

Replaces the legacy emit_display_line helper from ralph.pipeline.legacy_console_display. Bare lifecycle lines are dropped by ParallelDisplay itself; this helper just routes the line to the correct unit_id. When display is None but a display_context is provided, the line is written to the context’s console for legacy compatibility.

Parameters:
Return type:

None

ralph.display.parallel_display.get_display_context(display, display_context=None)[source]

Return the DisplayContext a caller should render against.

Single source of truth for the legacy get_display_context helper. The display’s own context is preferred when present (tries display_context first, then _ctx for back-compat with fakes that store it privately); otherwise the caller-provided context is used.

Parameters:
Return type:

DisplayContext

ralph.display.parallel_display.phase_label(phase)

Pure helper: return a human-readable label for a phase name.

Parameters:

phase (str)

Return type:

str

ralph.display.parallel_display.phase_style(phase, pipeline_policy=None)

Pure helper: return the rich style string for a phase name or role.

Parameters:
Return type:

str

ralph.display.parallel_display.resolve_active_display(display, display_context=None)[source]

Return the given display, constructing a ParallelDisplay from the context if needed.

The context is required when display is None. Rich is a required dependency (declared in pyproject.toml line 22: rich>=13.0), so ParallelDisplay always initialises successfully here.

A DisplayContext passed as display is unwrapped to its display_context slot and a fresh ParallelDisplay is constructed, so callers that only have a context still get a real display.

Parameters:
Return type:

ParallelDisplay

ralph.display.parallel_display.resolve_display(display, display_context=None, *, is_quiet=False)[source]

Return the given display or construct one from the context.

Single source of truth that replaces the legacy resolve_display helper from ralph.pipeline.legacy_console_display. Pass-through for non-None inputs; constructs a ParallelDisplay from the supplied context when display is None. When is_quiet=True, the constructed display short-circuits all banner and log-line emissions (see ParallelDisplay quiet-mode contract).

Parameters:
Return type:

ParallelDisplay

ralph.display.parallel_display.status_text(label, value, style)[source]

Build a styled status line as a plain string.

Replaces the legacy status_text helper from ralph.pipeline.legacy_console_display. Returns plain text — the caller passes it through emit_activity_line which uses ParallelDisplay.emit (plain log routing) for rendering.

Parameters:
  • label (str)

  • value (str)

  • style (str)

Return type:

str

ralph.display.parallel_display.strip_markup(line)[source]

Strip Rich markup tags from a line, returning plain text.

Parameters:

line (str)

Return type:

str

ralph.display.parallel_display.subscriber_for_display(display)[source]

Return the pipeline subscriber attached to the given display, when present.

Parameters:

display (ParallelDisplay | None)

Return type:

PipelineSubscriber | None

ralph.display.phase_lifecycle

Lifecycle view-model dataclasses for phase rendering.

class ralph.display.phase_lifecycle.ExitContext(elapsed_seconds=0.0, exit_trigger=None, content_blocks=0, thinking_blocks=0, tool_calls=0, errors=0, artifact_outcome='', review_issues_found=None, routing_note=None, waiting_status_line=None, last_failure_category=None)[source]

Bases: object

Optional context for building a PhaseExitModel from a PhaseEntryModel.

Parameters:
  • elapsed_seconds (float)

  • exit_trigger (str | None)

  • content_blocks (int)

  • thinking_blocks (int)

  • tool_calls (int)

  • errors (int)

  • artifact_outcome (str)

  • review_issues_found (bool | None)

  • routing_note (str | None)

  • waiting_status_line (str | None)

  • last_failure_category (str | None)

class ralph.display.phase_lifecycle.PhaseActivityCounts(content_blocks=0, thinking_blocks=0, tool_calls=0, errors=0)[source]

Bases: object

Activity counter snapshot for a pipeline phase.

Parameters:
  • content_blocks (int)

  • thinking_blocks (int)

  • tool_calls (int)

  • errors (int)

class ralph.display.phase_lifecycle.PhaseEntryModel(phase_name, phase_role=None, agent_name=None, outer_dev_iteration=None, outer_dev_cap=None, inner_analysis=None, inner_analysis_cap=None)[source]

Bases: object

Immutable view-model for phase-start banner data.

Parameters:
  • phase_name (str)

  • phase_role (str | None)

  • agent_name (str | None)

  • outer_dev_iteration (int | None)

  • outer_dev_cap (int | None)

  • inner_analysis (int | None)

  • inner_analysis_cap (int | None)

human_label()[source]

Return the human-readable phase label.

Return type:

str

iteration_label_parts()[source]

Return ordered canonical label strings for the iteration context.

Return type:

list[str]

to_iteration_context()[source]

Return a PhaseIterationContext for canonical label rendering.

Return type:

ralph.display.phase_status.PhaseIterationContext

class ralph.display.phase_lifecycle.PhaseExitModel(phase_name, phase_role=None, agent_name=None, outer_dev_iteration=None, outer_dev_cap=None, inner_analysis=None, inner_analysis_cap=None, elapsed_seconds=0.0, exit_trigger=None, content_blocks=0, thinking_blocks=0, tool_calls=0, errors=0, artifact_outcome='', review_issues_found=None, routing_note=None, waiting_status_line=None, last_failure_category=None)[source]

Bases: object

Immutable view-model for phase-close after-banner data.

Parameters:
  • phase_name (str)

  • phase_role (str | None)

  • agent_name (str | None)

  • outer_dev_iteration (int | None)

  • outer_dev_cap (int | None)

  • inner_analysis (int | None)

  • inner_analysis_cap (int | None)

  • elapsed_seconds (float)

  • exit_trigger (str | None)

  • content_blocks (int)

  • thinking_blocks (int)

  • tool_calls (int)

  • errors (int)

  • artifact_outcome (str)

  • review_issues_found (bool | None)

  • routing_note (str | None)

  • waiting_status_line (str | None)

  • last_failure_category (str | None)

classmethod from_entry_model(entry, context=None)[source]

Construct a PhaseExitModel by extending a PhaseEntryModel.

Parameters:
Return type:

PhaseExitModel

to_iteration_context()[source]

Return a PhaseIterationContext for canonical label rendering.

Return type:

ralph.display.phase_status.PhaseIterationContext

class ralph.display.phase_lifecycle.RunCompletionModel(final_phase, is_failure, exit_trigger='exited', elapsed_seconds=None, outer_dev_iteration=None, total_agent_calls=0, content_blocks=0, thinking_blocks=0, tool_calls=0, errors=0, review_issues_found=False, last_error=None, budget_progress=<factory>, analysis_decisions=(), last_activity_line=None, waiting_status_line=None, last_failure_category=None, mcp_restart_count=0)[source]

Bases: object

Immutable view-model for final run-completion summary data.

Parameters:
  • final_phase (str)

  • is_failure (bool)

  • exit_trigger (str)

  • elapsed_seconds (float | None)

  • outer_dev_iteration (int | None)

  • total_agent_calls (int)

  • content_blocks (int)

  • thinking_blocks (int)

  • tool_calls (int)

  • errors (int)

  • review_issues_found (bool)

  • last_error (str | None)

  • budget_progress (dict[str, tuple[int, int]])

  • analysis_decisions (tuple[tuple[str, str, str], ...])

  • last_activity_line (str | None)

  • waiting_status_line (str | None)

  • last_failure_category (str | None)

  • mcp_restart_count (int)

classmethod from_snapshot(snapshot, *, exit_trigger, elapsed_seconds=None, activity=None)[source]

Build a RunCompletionModel from a PipelineSnapshot.

Parameters:
Return type:

RunCompletionModel

ralph.display.phase_status

Canonical presentation formatters for phase lifecycle rendering.

This is the single source of truth for how iteration context (dev cycles, analysis cycles) and phase outcomes are labeled across phase-start banners, phase-close lines, and run-end summaries.

All formatters are pure: they accept simple values and return strings. No Console construction, no env reads, no pipeline logic.

class ralph.display.phase_status.PhaseIterationContext(outer_dev=None, outer_dev_cap=None, inner_analysis=None, inner_analysis_cap=None)[source]

Bases: object

Canonical iteration context for phase start/close rendering.

Parameters:
  • outer_dev (int | None)

  • outer_dev_cap (int | None)

  • inner_analysis (int | None)

  • inner_analysis_cap (int | None)

outer_dev

Outer development cycle number (None if not in outer loop).

Type:

int | None

outer_dev_cap

Budget cap for outer dev cycles (shows Dev N/cap when set).

Type:

int | None

inner_analysis

Inner analysis cycle number (None if not in analysis).

Type:

int | None

inner_analysis_cap

Max inner analysis cycles (None if unknown).

Type:

int | None

context_labels()[source]

Return (label, style_key) pairs for rendering, in display priority order.

Order: outer dev (highest visibility) → inner analysis.

Return type:

list[tuple[str, str]]

has_context()[source]

Return True if any iteration context is set.

Return type:

bool

ralph.display.phase_status.format_analysis_cycle(n, cap=None)[source]

Return canonical label for inner analysis cycle (1-indexed).

Parameters:
  • n (int)

  • cap (int | None)

Return type:

str

ralph.display.phase_status.format_analysis_cycle_compact(n, cap=None)[source]

Return compact analysis cycle label for narrow-terminal rendering.

Shortens Analysis N/cap to A1/3 (4 chars) and Analysis #N to A#1 (3 chars). The A prefix keeps the label distinct from the dev-cycle compact form.

Parameters:
  • n (int)

  • cap (int | None)

Return type:

str

ralph.display.phase_status.format_analysis_cycle_minimal(n, cap=None)[source]

Return minimal analysis cycle label for very narrow terminals.

Returns N/cap (no prefix) when a cap is provided, #N otherwise. The compact and minimal forms of the analysis cycle share the same shape (the A prefix is dropped); at very narrow widths the operator still sees the count vs cap and a glyph prefix distinguishes dev vs analysis.

Parameters:
  • n (int)

  • cap (int | None)

Return type:

str

ralph.display.phase_status.format_dev_cycle(n, cap=None)[source]

Return canonical label for outer development cycle number (1-indexed).

When cap is provided (and positive), shows Dev N/cap to make the remaining budget immediately visible. Without a cap, shows Dev #N.

Parameters:
  • n (int)

  • cap (int | None)

Return type:

str

ralph.display.phase_status.format_dev_cycle_compact(n, cap=None)[source]

Return compact dev cycle label for narrow-terminal rendering.

The compact form shortens the canonical Dev N/cap to D1/3 (4 chars) so the persistent Status Bar fits a constrained terminal without dropping the iteration field. Without a cap, returns D#1 (3 chars).

The compact form keeps the disambiguating D prefix so an operator can still tell dev cycles from analysis cycles at a glance. Used by ralph.display.status_bar when the canonical label exceeds the per-iteration label budget derived from ctx.width.

Parameters:
  • n (int)

  • cap (int | None)

Return type:

str

ralph.display.phase_status.format_dev_cycle_minimal(n, cap=None)[source]

Return minimal dev cycle label for very narrow terminals.

Returns N/cap (no prefix) when a cap is provided, #N otherwise. Used by ralph.display.status_bar when even the compact form (D1/3) cannot fit.

Parameters:
  • n (int)

  • cap (int | None)

Return type:

str

ralph.display.phase_status.format_elapsed_seconds(s)[source]

Return canonical elapsed-time label.

Parameters:

s (float)

Return type:

str

ralph.display.phase_status.format_exit_trigger(snapshot)[source]

Return canonical exit-trigger label from a PipelineSnapshot-like object.

Parameters:

snapshot (_ExitState)

Return type:

str

ralph.display.phase_status.format_transition_context_items(context)[source]

Return formatted display strings for a phase transition context dict.

Normalizes context items from generic key=value to canonical display format: - ‘analysis_status’ key: rendered as the bare value (no key prefix) - ‘decision’ key: rendered as ‘→ {value}’ (arrow notation) - multi-word keys (containing spaces): rendered as ‘[key value]’ bracket notation - all other keys: rendered as ‘key=value’

Parameters:

context (dict[str, object])

Return type:

list[str]

ralph.display.prompt_reader

Safe PROMPT.md reader with size cap, encoding safety, and markup escape.

ralph.display.prompt_reader.find_prompt_path(workspace_root, env=None)[source]

Return the path to the workspace prompt file, or None if absent.

The path is resolved through ralph.pro_support.prompt.resolve_effective_prompt_path() so the PROMPT_PATH env var is honoured in Pro mode. The engine-owned .agent/CURRENT_PROMPT.md is still checked as a fallback because it is the materialised prompt the agent actually reads.

The caller supplies the env mapping. Reading os.environ directly inside the display module would violate the DI invariant enforced by tests/display/test_di_invariants.py. Callers that do not have a pre-resolved env should pass context.make_display_context().env or fall back to os.environ at their own call site.

Parameters:
  • workspace_root (Path)

  • env (Mapping[str, str] | None)

Return type:

Path | None

ralph.display.prompt_reader.read_prompt_preview(prompt_path)[source]

Read at most MAX_PROMPT_BYTES, decode with errors=’replace’, return PREVIEW_LINES escaped.

Parameters:

prompt_path (Path)

Return type:

tuple[str, …]

ralph.display.raw_overflow

Per-unit raw NDJSON overflow log writer.

ralph.display.raw_overflow.DEFAULT_FLUSH_INTERVAL_SECONDS = 5.0

Default seconds between forced flushes. MUST stay well below ralph.timeout_defaults.LOG_GROWTH_SECONDS (30.0): operators tail this file and the on-disk copy must never look wedged while the unit is live.

class ralph.display.raw_overflow.RawOverflowLog(workspace_root, unit_id, *, max_bytes=52428800, flush_interval_seconds=5.0, now=<built-in function monotonic>)[source]

Bases: object

Append-mode raw log for a single work unit.

Thread-safe. Holds one buffered file handle open for the unit’s lifetime instead of opening/closing per line (the per-line pattern generated an fsevent storm on long runs). Silently no-ops on filesystem errors so the display path never crashes due to a read-only workspace.

Parameters:
  • workspace_root (Path)

  • unit_id (str)

  • max_bytes (int)

  • flush_interval_seconds (float)

  • now (Callable[[], float])

append(line)[source]

Write line to the overflow log.

Returns True when the line was written. Returns False when the log is disabled, the byte cap has been reached, or an I/O error occurs.

Parameters:

line (str)

Return type:

bool

close()[source]

Flush and release the file handle. Idempotent; appends may reopen.

Return type:

None

disable()[source]

Permanently disable this log so future appends are no-ops.

Return type:

None

flush()[source]

Force buffered bytes to disk. Never raises.

Return type:

None

property is_disabled: bool

True when the log has been permanently disabled (byte cap reached or I/O error).

relative_reference(workspace_root)[source]

Return POSIX path relative to workspace_root, or absolute on error.

Parameters:

workspace_root (Path)

Return type:

str

property size_bytes: int

Bytes appended so far (buffered bytes included).

The idle watchdog’s log-growth corroborator reads this to prove the unit is alive; it must advance on every append, not only on flush. Returns 0 before the first write. Never raises.

The in-memory _bytes_written counter is the authoritative liveness signal — an on-disk stat() probe is intentionally avoided because a missing or unfetchable file (operator unlink, watcher quarantine, transient I/O error) must NOT silence the watchdog while the unit itself is still appending.

ralph.display.ring_buffer

Bounded ring buffer with drop-oldest policy and dropped-item counter.

Used by ParallelDisplay to absorb burst output from agents without OOM risk. Thread-safe via threading.Lock — NOT asyncio-safe.

class ralph.display.ring_buffer.RingBuffer(maxsize)[source]

Bases: object

Thread-safe bounded ring buffer with drop-oldest overflow policy.

When the buffer is full and a new item arrives, the oldest item is dropped and dropped_count is incremented.

Parameters:

maxsize (int) – Maximum number of items to retain.

consume_drop_delta()[source]

Return and zero the dropped_count atomically.

Each call returns how many items were dropped since the last call (or since construction). Thread-safe.

Return type:

int

drain()[source]

Destructively return and clear buffered items.

Return type:

list[str]

snapshot(n=None)[source]

Return buffered items without clearing them.

drain() is destructive; snapshot() is the non-destructive, read-only view for callers that need to inspect buffered output.

Parameters:

n (int | None)

Return type:

list[str]

ralph.display.snapshot

Immutable pipeline snapshot models.

This module projects pipeline state into a presentation-agnostic data shape consumed by display panels and subscribers.

class ralph.display.snapshot.BudgetProgress(completed, cap, description, tracks_budget)[source]

Bases: object

Immutable progress record for a single policy-declared budget counter.

Parameters:
  • completed (int)

  • cap (int)

  • description (str)

  • tracks_budget (bool)

class ralph.display.snapshot.PipelineSnapshot(phase, previous_phase, review_issues_found, interrupted_by_user, last_error, pr_url, push_count, total_agent_calls, total_continuations, total_fallbacks, total_retries, workers, prompt_path, prompt_preview, run_id, created_at, plan_summary=None, plan_scope_items=(), plan_total_steps=0, plan_current_step=None, plan_risks=(), active_agent=None, active_tool=None, active_path=None, active_unit_id=None, active_workdir=None, active_command=None, active_pattern=None, active_tool_repeat=0, last_activity_line=None, waiting_status_line=None, analysis_phase=None, analysis_decision=None, analysis_reason=None, decision_log=<factory>, recovery_cycle_count=0, recovery_cycle_cap=200, fallover_history=<factory>, last_failure_category=None, last_connectivity_state='unknown', is_terminal_success=False, is_terminal_failure=False, current_phase_role=None, previous_phase_role=None, terminal_failure_route=None, budget_progress=<factory>, outer_dev_iteration=None, mcp_restart_count=0, active_process_labels=())[source]

Bases: object

Immutable pipeline state snapshot for transcript rendering.

Parameters:
  • phase (str)

  • previous_phase (str | None)

  • review_issues_found (bool)

  • interrupted_by_user (bool)

  • last_error (str | None)

  • pr_url (str | None)

  • push_count (int)

  • total_agent_calls (int)

  • total_continuations (int)

  • total_fallbacks (int)

  • total_retries (int)

  • workers (tuple[WorkerSnapshot, ...])

  • prompt_path (str | None)

  • prompt_preview (tuple[str, ...])

  • run_id (str | None)

  • created_at (datetime)

  • plan_summary (str | None)

  • plan_scope_items (tuple[str, ...])

  • plan_total_steps (int)

  • plan_current_step (int | None)

  • plan_risks (tuple[str, ...])

  • active_agent (str | None)

  • active_tool (str | None)

  • active_path (str | None)

  • active_unit_id (str | None)

  • active_workdir (str | None)

  • active_command (str | None)

  • active_pattern (str | None)

  • active_tool_repeat (int)

  • last_activity_line (str | None)

  • waiting_status_line (str | None)

  • analysis_phase (str | None)

  • analysis_decision (str | None)

  • analysis_reason (str | None)

  • decision_log (tuple[tuple[str, str, str, str], ...])

  • recovery_cycle_count (int)

  • recovery_cycle_cap (int)

  • fallover_history (tuple[tuple[str, str, str, str], ...])

  • last_failure_category (str | None)

  • last_connectivity_state (str)

  • is_terminal_success (bool)

  • is_terminal_failure (bool)

  • current_phase_role (str | None)

  • previous_phase_role (str | None)

  • terminal_failure_route (str | None)

  • budget_progress (dict[str, BudgetProgress])

  • outer_dev_iteration (int | None)

  • mcp_restart_count (int)

  • active_process_labels (tuple[str, ...])

active_tool_repeat: int

Consecutive-repeat count for the current (tool, path, …) activity. 1 on the first call, incremented each time the SAME tool activity recurs back-to-back. Lets the renderer keep the live status fresh (e.g. “exec (x3)”) instead of freezing when an agent repeats the same tool call.

class ralph.display.snapshot.SnapshotContext(prompt_path=None, prompt_preview=(), run_id=None, pipeline_policy=None, plan_summary=None, plan_scope_items=(), plan_total_steps=0, plan_current_step=None, plan_risks=(), active_agent=None, active_tool=None, active_path=None, active_unit_id=None, active_workdir=None, active_command=None, active_pattern=None, active_tool_repeat=0, last_activity_line=None, waiting_status_line=None, analysis_phase=None, analysis_decision=None, analysis_reason=None, decision_log=(), mcp_restart_count=0, active_process_labels=())[source]

Bases: object

Display context for building a PipelineSnapshot from a PipelineState.

All fields are optional so callers can populate only what they know.

Parameters:
  • prompt_path (str | None)

  • prompt_preview (tuple[str, ...])

  • run_id (str | None)

  • pipeline_policy (PipelinePolicy | None)

  • plan_summary (str | None)

  • plan_scope_items (tuple[str, ...])

  • plan_total_steps (int)

  • plan_current_step (int | None)

  • plan_risks (tuple[str, ...])

  • active_agent (str | None)

  • active_tool (str | None)

  • active_path (str | None)

  • active_unit_id (str | None)

  • active_workdir (str | None)

  • active_command (str | None)

  • active_pattern (str | None)

  • active_tool_repeat (int)

  • last_activity_line (str | None)

  • waiting_status_line (str | None)

  • analysis_phase (str | None)

  • analysis_decision (str | None)

  • analysis_reason (str | None)

  • decision_log (tuple[tuple[str, str, str, str], ...])

  • mcp_restart_count (int)

  • active_process_labels (tuple[str, ...])

class ralph.display.snapshot.WorkerSnapshot(unit_id, description, status, status_semantic, started_at, finished_at, elapsed_s, exit_code, error_message, dropped_lines=0)[source]

Bases: object

Immutable projection of a single worker’s execution state.

Parameters:
  • unit_id (str)

  • description (str)

  • status (str)

  • status_semantic (str)

  • started_at (datetime | None)

  • finished_at (datetime | None)

  • elapsed_s (float)

  • exit_code (int | None)

  • error_message (str | None)

  • dropped_lines (int)

ralph.display.snapshot.snapshot_from_state(state, context=None)[source]

Project PipelineState into an immutable pipeline snapshot.

Parameters:
Return type:

PipelineSnapshot

ralph.display.status_bar

Persistent Status Bar at the bottom of the interactive terminal display.

The Status Bar shows working directory, active phase, and any applicable outer development iteration and inner analysis iteration during interactive runs. It is the single owner of run-level layout, color, spacing, truncation, and live-update behavior; the per-unit emit_status_line and the transient waiting_status_line are orthogonal surfaces left intact for one-shot transcript lines.

After the wt-028-display consolidation, Ralph Workflow exposes exactly ONE display mode (default). The persistent Status Bar always renders all applicable fields at every terminal width where they fit:

  • working directory (middle-truncated when long),

  • active phase label (tail-truncated when long),

  • outer development iteration (when non-None AND ctx.width can accommodate it),

  • inner analysis iteration (when non-None AND ctx.width can accommodate it).

Width-driven degradation (in order) so len(text.plain) <= ctx.width holds at every width:

  1. Path middle-truncation absorbs excess length on long paths.

  2. Phase label tail-truncation absorbs excess length on long labels.

  3. Iteration label form degrades canonical -> compact -> minimal.

  4. Phase marker is dropped below the marker-fit threshold.

  5. Per-iteration glyphs are dropped below the glyph-fit threshold.

  6. Iteration segments drop one at a time (outer_dev first, then inner_analysis, then both) below the iteration-visibility threshold (14 cols). The bar always fits ctx.width even when iteration segments drop entirely — phase + path remain visible at every applicable width.

The bar is gated on a real-TTY check (console.is_terminal AND console.file.isatty()) so it stays out of non-interactive runs (redirects, pipes, CI logs, StringIO test consoles, and force_terminal+StringIO consoles).

DI / purity invariants:

  • render_status_bar is a pure function: no I/O, no env reads, no Console construction, no Path.home() calls (home is a parameter so the function can be tested deterministically).

  • status_bar.py does not construct a rich.Console and does not read os.environ / os.getenv; the DI invariants test asserts this.

  • The StatusBar lifecycle class lazily constructs a single rich.live.Live region only when the real-TTY gate passes; it never reads env at module import.

Cadence constants:

  • _STATUS_BAR_REFRESH_PER_SECOND (default 4.0): refresh rate for the Live region. Pinned by test_status_bar_pins_steady_cadence_config.

  • _STATUS_BAR_TRANSIENT (default True): frames are erased on stop, preserving clean scrollback, copy/paste, terminal search, and post-run log review.

Default rendering

The single default layout renders (in order):

[phase_marker] {phase_label} [milestone] {workspace_root}
                          [milestone] {outer_dev} Dev N/cap
                          [milestone] {inner_analysis} Analysis N/cap

A field is omitted entirely (no -- placeholder) when its iteration field is None on the model. The phase marker glyph is omitted when ctx.glyphs_enabled is False so ASCII consoles render a clean prefix.

class ralph.display.status_bar.StatusBar(display)[source]

Bases: object

Lifecycle owner for the persistent bottom Status Bar.

The StatusBar is composed by ralph.display.parallel_display.ParallelDisplay and reachable via pd.status_bar. The public push-side surface is ralph.display.parallel_display.ParallelDisplay.update_status_bar() (callers invoke display.update_status_bar(model)); StatusBar.update(model) is the internal storage seam the public method forwards into so the Live region picks the model up on its next refresh tick. The start() and stop() methods are wired through ParallelDisplay’s own start() / stop() lifecycle. Reads happen via last_model.

Parameters:

display (ParallelDisplay)

_display

Same-package reference to the owning ParallelDisplay instance. Reads display._ctx (live DisplayContext that the runner keeps fresh via SIGWINCH / poll refreshers) and display._is_quiet.

_home

Home directory resolved once at construction; passed to render_status_bar so render stays pure.

_model

Last model supplied via update(); None until first update.

_live

Lazily-constructed rich.live.Live instance (or None).

_lock

Threading lock guarding _model assignment.

property is_active: bool

Return True when a Live region is currently active for this StatusBar.

property last_model: StatusBarModel | None

Return the most recent StatusBarModel supplied via update().

start()[source]

Begin rendering the Status Bar inside a transient Rich Live region.

No-op when the real-TTY gate is closed (non-tty console, redirected output, StringIO test console, quiet mode), or when a Live region is already active. Idempotent.

The Live region is constructed with get_renderable=self._renderable so each refresh tick re-reads the latest model — the initial renderable argument is only the first-frame content.

Correctness: _live is committed to self._live ONLY after Live.start() succeeds. If Live.start() raises (e.g. on a console whose Live.start() path is broken, or a parent that suppresses the underlying terminal), the exception is swallowed but self._live stays None. This keeps is_active honest (is_active is defined as self._live is not None) so a later start() retry still succeeds and stop() on an unstarted bar remains a no-op.

Return type:

None

stop()[source]

Tear down the Live region. Idempotent and safe to call without start().

Return type:

None

update(model)[source]

Store model for the Live region to pick up on its next refresh tick.

This is the internal storage seam the public push-side surface ralph.display.parallel_display.ParallelDisplay.update_status_bar() forwards into. Callers should NOT invoke status_bar.update(model) directly; the consolidated contract is display.update_status_bar(model).

On interactive consoles the update is intentionally a pure store: it does NOT force an immediate live.refresh(). The persistent footer is owned by the Live region’s _STATUS_BAR_REFRESH_PER_SECOND cadence (4.0 Hz / 250 ms by default), so update calls feed a fresh StatusBarModel and the next refresh tick renders it. On Rich “dumb terminal” consoles where Live.start() succeeds but Rich refuses to draw frames, the fallback renderer erases the previous fallback row and emits one bounded replacement row so is_active stays observable.

Safe to call before start(); in that case the model is stored and the subsequent start() constructs the Live region using the latest model as its initial renderable. Thread-safe under _lock.

Parameters:

model (StatusBarModel)

Return type:

None

class ralph.display.status_bar.StatusBarModel(workspace_root, phase_label, phase_style, outer_dev_iteration=None, outer_dev_cap=None, inner_analysis=None, inner_analysis_cap=None)[source]

Bases: object

Immutable view-model for the persistent Status Bar footer.

Parameters:
  • workspace_root (str)

  • phase_label (str)

  • phase_style (str)

  • outer_dev_iteration (int | None)

  • outer_dev_cap (int | None)

  • inner_analysis (int | None)

  • inner_analysis_cap (int | None)

workspace_root

Working-directory path to display.

Type:

str

phase_label

Human-readable phase label (e.g. Development).

Type:

str

phase_style

Rich style string applied to the phase label (e.g. theme.phase.development); also carries textual meaning so the bar is readable when color is disabled.

Type:

str

outer_dev_iteration

Current outer development cycle (1-indexed), or None when the active phase does not track outer progress.

Type:

int | None

outer_dev_cap

Outer development cap, or None when unknown.

Type:

int | None

inner_analysis

Current inner analysis iteration (1-indexed), or None when the active phase does not track analysis cycles.

Type:

int | None

inner_analysis_cap

Inner analysis iteration cap, or None when unknown.

Type:

int | None

ralph.display.status_bar.render_status_bar(model, ctx, *, home=None)[source]

Render the single-line Status Bar footer for the given model.

This function is PURE: no I/O, no env reads, no Console construction, no Path.home() calls. home is a parameter so callers can supply the resolved home directory once (the StatusBar lifecycle resolves it at construction; tests pass an explicit value).

The single default-mode layout renders phase + dir + (any applicable outer_dev) + (any applicable inner_analysis) at every width where the iteration segments fit. When ctx.width is too narrow to fit the canonical forms (Dev 1/3 / Analysis 2/5) the labels degrade through compact (D1/3 / A2/5) and minimal (1/3 / 2/5) forms, the phase marker and per-iteration glyphs are dropped at the marker-fit / glyph-fit thresholds, and finally the iteration segments drop one at a time at very narrow widths (below 14 cols) so the bar still fits ctx.width.

The phase and path labels are tail/middle truncated to fit the remaining budget. len(text.plain) <= ctx.width always holds (a final Text.truncate clamp covers the 1-2 col edge case where the phase|path separator alone exceeds the budget), and the rendered text never contains a newline.

Parameters:
  • model (StatusBarModel) – Immutable view-model describing the bar contents.

  • ctx (DisplayContext) – Display context providing mode, glyphs, and theme-aware style.

  • home (str | None) – Optional home directory; when supplied and model.workspace_root starts with it, the rendered path is home-relative.

Returns:

A single-line rich.text.Text carrying the bar contents. The rendered text never contains \n so the bar cannot wrap into the working area, and len(text.plain) <= ctx.width so the bar fits any terminal width (including widths below 14 cols where iteration segments drop entirely to honor the len(text.plain) <= ctx.width invariant).

Return type:

Text

ralph.display.subscriber

Queue-backed asyncio+threading-safe state→snapshot bridge.

class ralph.display.subscriber.PipelineSubscriber(*, queue, workspace_root, run_id, _prompt_reader=<function read_prompt_preview>, on_snapshot=None, pipeline_policy=None)[source]

Bases: object

Receives PipelineState after each reducer reduce and enqueues a PipelineSnapshot.

Thread and asyncio safe: notify() only calls put_nowait() which is documented as thread-safe and never blocks. Prompt preview and the plan artifact are read once at construction and cached for the lifetime of the subscriber.

The subscriber additionally exposes record_activity and record_analysis to receive lightweight presentation events that should flow into the same snapshot queue without breaking the notify(state) contract.

Parameters:
build_snapshot(state)[source]

Project the subscriber’s accumulated state into a snapshot.

Read-only: does not mutate any internal state. Safe to call from external code (such as the end-of-run summary path) without breaking the notify(state) contract.

Parameters:

state (PipelineState)

Return type:

PipelineSnapshot | None

property last_tool_name: str | None

The most recently recorded tool name.

property last_tool_path: str | None

The most recently recorded tool path argument.

notify(state)[source]

Build a PipelineSnapshot from state and enqueue it non-blocking.

Never blocks. On queue.Full, increments dropped_count silently. Safe to call from both sync (runner.py) and async (coordinator.py) contexts.

Parameters:

state (PipelineState)

Return type:

None

record_analysis(phase, decision, reason=None)[source]

Record an analysis result; updates the analysis panel and decision log.

Parameters:
  • phase (str)

  • decision (str)

  • reason (str | None)

Return type:

None

record_mcp_restart(restart_count)[source]

Record the current MCP server restart count and push a fresh snapshot.

Parameters:

restart_count (int)

Return type:

None

record_permission_prompt_action(*, agent_name, prompt_summary, selected_option)[source]

Record an auto-answered permission prompt for visibility and auditing.

Parameters:
  • agent_name (str)

  • prompt_summary (str)

  • selected_option (str)

Return type:

None

record_waiting_status(event, *, unit_id=None, agent_name=None)[source]

Record a waiting-status event from IdleWatchdog and push a fresh snapshot.

Parameters:
  • event (object)

  • unit_id (str | None)

  • agent_name (str | None)

Return type:

None

property waiting_status_line: str | None

The current waiting-status line for debug breadcrumbs.

ralph.display.theme

Okabe-Ito theme helpers for Ralph CLI display.

ralph.display.theme.detect_glyph_capability(stream, env)[source]

Return False when glyphs should fall back to ASCII, True for Unicode.

Heuristic order (highest to lowest precedence): 1. RALPH_FORCE_ASCII env var (any truthy value) → ASCII 2. stream.encoding exists and ‘utf’ not in encoding.lower() → ASCII 3. TERM=dumb → ASCII 4. Otherwise → Unicode

Parameters:
  • stream (object)

  • env (Mapping[str, str])

Return type:

bool

ralph.display.theme.format_status(status_name)[source]

Return Rich markup for a semantic status name.

Parameters:

status_name (str)

Return type:

str

ralph.display.theme.make_console(*, no_color=None, force_terminal=None, width=None)[source]

Create a Console using Ralph’s shared theme and predictable rendering.

This is a pure constructor - no environment reads. All decisions about no_color and force_terminal must be passed explicitly via the corresponding arguments. The caller is responsible for resolving environment variables before calling this function.

Parameters:
  • no_color (bool | None) – If True, disables color output. If False, enables color. If None, defaults to False (color enabled).

  • force_terminal (bool | None) – If True, forces terminal detection on. If False, forces it off. If None, defaults to None so Rich auto-detects terminal support via sys.stdout.isatty() (this is the production default: forcing force_terminal=False would hard-code Console.is_terminal=False and break the StatusBar real-TTY gate in real PTY sessions).

  • width (int | None) – Optional terminal width override.

Returns:

Configured Console instance with Ralph’s theme.

Return type:

Console

ralph.display.tool_args

Utilities for formatting tool_use input arguments as a compact display string.

ralph.display.tool_args.format_tool_input(input_obj, *, max_value_chars=120)[source]

Format a tool_use input dict as a compact key=value string.

Returns empty string for non-dict inputs. Formats as: (k=v k=v …) with known keys first (path, command, workdir, pattern), then remaining keys alphabetically. Values are truncated at max_value_chars.

Parameters:
  • input_obj (object)

  • max_value_chars (int)

Return type:

str

ralph.display.tool_args.friendly_tool_name(name)[source]

Return a shorter display name for well-known MCP tool prefixes.

mcp__ralph__read_file becomes ralph.read_file. All other names are returned unchanged. Only the rendered display string is affected; metadata is untouched.

Parameters:

name (str)

Return type:

str

ralph.executor

Low-level process execution helpers for Ralph Workflow.

This package wraps subprocess execution with structured result and error types, providing a consistent interface for running external commands from phase handlers and MCP tool implementations.

Main entry points:

  • run_process(cmd, ...) — synchronous subprocess execution; returns a ProcessResult with stdout, stderr, and return code.

  • run_process_async(cmd, ...) — async variant for use in asyncio contexts.

  • ProcessResult — holds stdout, stderr, returncode, and a convenience check() method that raises ProcessExecutionError on non-zero exit.

  • ProcessExecutionError — raised when a process exits with a non-zero code; carries the full ProcessResult for diagnostics.

For agent subprocess management (streaming, watchdogs, parser integration) see ralph.agents.invoke and ralph.agents.subprocess_executor.

ralph.executor.process

Helpers for executing external processes with captured output.

exception ralph.executor.process.ProcessExecutionError(command, message, details=None)[source]

Bases: RuntimeError

Raised when a process cannot be started or exceeds its timeout.

Parameters:
  • command (tuple[str, ...])

  • message (str)

  • details (ProcessErrorDetails | None)

Return type:

None

classmethod from_os_error(command, error)[source]

Build an execution error from an OS-level failure.

Parameters:
  • command (tuple[str, ...])

  • error (OSError)

Return type:

ProcessExecutionError

classmethod from_timeout(command, *, timeout, stdout, stderr)[source]

Build a timeout error with captured partial output.

Parameters:
  • command (tuple[str, ...])

  • timeout (float | None)

  • stdout (str)

  • stderr (str)

Return type:

ProcessExecutionError

class ralph.executor.process.ProcessResult(command, returncode, stdout, stderr)[source]

Bases: object

Captured result from a completed process.

Parameters:
  • command (tuple[str, ...])

  • returncode (int)

  • stdout (str)

  • stderr (str)

property succeeded: bool

Return True when the process exited successfully.

class ralph.executor.process.ProcessRunOptions(cwd=None, env=None, timeout=None, capture_output=True, label=None)[source]

Bases: object

Execution options for run_process and run_process_async.

Parameters:
  • cwd (str | Path | None)

  • env (Mapping[str, str] | None)

  • timeout (float | None)

  • capture_output (bool)

  • label (str | None)

cwd

Working directory for the child (None = inherit).

Type:

str | Path | None

env

Extra environment variables (merged on top of os.environ).

Type:

Mapping[str, str] | None

timeout

Wall-clock bound on handle.communicate() (None = no bound).

Type:

float | None

capture_output

When False, inherit stdout/stderr to the terminal.

Type:

bool

label

Per-call observability label recorded on the ProcessRecord. Defaults to "executor:run-process" inside run_process / run_process_async when None, so every child spawned through the executor is label-groupable in diagnostics (pm.list_records(label_prefix=...) / pm.cleanup_orphans(label_prefix=...)). The label does NOT change teardown — the child is still synchronously reaped on every code path (success / timeout / BaseException at process.py:175-194 and :108-127).

Type:

str | None

ralph.executor.process.run_process(command, args=(), *, options=None, _pm=None)[source]

Run a process synchronously, optionally capturing output.

When options.capture_output is False the child process inherits the parent’s stdout/stderr so output streams directly to the terminal. The returned ProcessResult will have empty stdout and stderr strings.

Parameters:
  • command (str)

  • args (Sequence[str])

  • options (ProcessRunOptions | None)

  • _pm (ProcessManager | None)

Return type:

ProcessResult

async ralph.executor.process.run_process_async(command, args=(), *, cwd=None, env=None, timeout=None, label=None, _pm=None)[source]

Run a process asynchronously and capture its output.

Parameters:
  • command (str)

  • args (Sequence[str])

  • cwd (str | Path | None)

  • env (Mapping[str, str] | None)

  • timeout (float | None)

  • label (str | None)

  • _pm (ProcessManager | None)

Return type:

ProcessResult

ralph.exit_pause

Exit pause — decide whether to hold the terminal open before process exit.

Ported from ralph-workflow/src/exit_pause/io.rs.

class ralph.exit_pause.ExitOutcome(*values)[source]

Bases: StrEnum

Possible outcomes that affect pause behavior.

class ralph.exit_pause.LaunchContext(is_windows, has_terminal_session_marker, parent_process_name)[source]

Bases: object

Context about how Ralph was launched.

Parameters:
  • is_windows (bool)

  • has_terminal_session_marker (bool)

  • parent_process_name (str | None)

is_windows

Whether running on Windows.

Type:

bool

has_terminal_session_marker

Whether a terminal session marker is present.

Type:

bool

parent_process_name

Name of the parent process if detectable.

Type:

str | None

class ralph.exit_pause.PauseOnExitMode(*values)[source]

Bases: StrEnum

When to pause before exiting.

ralph.exit_pause.detect_launch_context(*, env=None)[source]

Detect the launch context for the current process.

Parameters:

env (Mapping[str, str] | None) – Optional environment mapping. Uses os.environ if None.

Returns:

LaunchContext with information about how Ralph was launched.

Return type:

LaunchContext

ralph.exit_pause.exit_pause(mode=PauseOnExitMode.AUTO)[source]

Pause before exit if conditions require it.

On Windows standalone launches (e.g., double-clicked .exe), it’s helpful to pause so the user can read any error messages before the window closes.

Parameters:

mode (PauseOnExitMode) – The pause mode setting (default: AUTO).

Return type:

None

ralph.exit_pause.exit_with_sigint_code()[source]

Exit with SIGINT exit code (130).

Called when the pipeline was interrupted by Ctrl+C and all cleanup has completed.

Return type:

None

ralph.exit_pause.should_pause_before_exit(mode, outcome, launch_context)[source]

Determine if we should pause before exiting.

Parameters:
  • mode (PauseOnExitMode) – The pause mode setting.

  • outcome (ExitOutcome) – The exit outcome (success/failure/interrupted).

  • launch_context (LaunchContext) – Information about how Ralph was launched.

Returns:

True if we should pause, False otherwise.

Return type:

bool

ralph.interrupt

Interrupt-handling helpers for unattended runs.

The orchestrator uses this module to install a process-wide SIGINT handler and record whether a user interruption has been requested. The state is intentionally simple so both CLI code and long-running loops can check it safely.

ralph.interrupt.asyncio_bridge

Asyncio signal bridge for graceful-then-forced SIGINT handling.

Uses loop.add_signal_handler() — not signal.signal() — to stay compatible with the asyncio event loop.

Signal handling contract:

  • First SIGINT synchronously cancels root_task and swaps in the second-SIGINT handler. The slow body (begin_interrupt plus the early-escalation poll) is dispatched off the event loop via loop.run_in_executor with a done callback that logs any executor-body exception. This makes the cancel + handler-swap fast even when begin_interrupt would block.

  • Second SIGINT force-kills tracked child processes via pm.list_active() (PGIDs) and exits with code 130.

The single source of truth for live processes is process_manager.list_active(); the bridge does NOT maintain a parallel pids set.

The interrupt dispatch is routed through InterruptDispatcher so the same wiring lives in both the sync handle_keyboard_interrupt path and this asyncio path. The controller parameter is type-broadened to accept either an InterruptController or an already-built InterruptDispatcher; install_signal_handlers discriminates by isinstance inside the function body. The parameter name is preserved for backward compatibility — the broadening is type-only.

install_signal_handlers returns an idempotent teardown callable that removes the second-SIGINT handler installed by the first handler. The teardown is safe to invoke twice.

class ralph.interrupt.asyncio_bridge.SignalBridge[source]

Bases: object

Bridge that routes OS signals to asyncio task cancellation and process cleanup.

The bridge is intentionally minimal: a counter for the interrupt count and an optional connectivity-stop hook. The single source of truth for live processes is the ProcessManager; the bridge never maintains its own PID set.

ralph.interrupt.asyncio_bridge.install_signal_handlers(loop, root_task, bridge, controller=None)[source]

Register SIGINT handlers that cancel root_task and forward to child PIDs.

The fourth argument is type-broadened to accept an InterruptController (legacy) OR an InterruptDispatcher (new). Discrimination is by isinstance inside the body. When a controller is passed, the implementation synthesizes a dispatcher that forwards the controller’s kill_process_group and hard_exit so the controller’s injected exit callable is the one invoked on _second_sigint (PA-019).

The returned callable is an idempotent teardown that removes the second-SIGINT handler installed by the first handler. Calling it twice is safe (a short-circuit flag is stored in the closure).

Parameters:
Return type:

Callable[[], None] | None

ralph.interrupt.controller

Dependency-injected interrupt orchestration helpers.

This module centralizes what should happen when Ralph receives a user interrupt: record it, stop optional connectivity waits, try a graceful shutdown first, and escalate to a forced kill plus hard exit on a second interrupt. Keeping these actions behind an injectable controller makes the behavior testable without real signals.

class ralph.interrupt.controller.InterruptController(shutdown_all, record_interrupt=<function request_user_interrupt>, stop_connectivity=None, kill_process_group=None, hard_exit=None, shutdown_all_for_label=None)[source]

Bases: object

Coordinate graceful and forced interrupt handling through injected seams.

Parameters:
  • shutdown_all (Callable[[float], None])

  • record_interrupt (Callable[[], None])

  • stop_connectivity (Callable[[], None] | None)

  • kill_process_group (Callable[[int, int], None] | None)

  • hard_exit (Callable[[int], None] | None)

  • shutdown_all_for_label (Callable[[str, float], None] | None)

begin_interrupt(*, grace_period_s, kill_label='')[source]

Record the interrupt and attempt graceful tracked-process shutdown.

When kill_label is non-empty AND shutdown_all_for_label is set, the controller calls the label-targeted closure INSTEAD of the generic shutdown_all. This lets the FIRST SIGINT route through a path that targets a specific agent process group rather than the generic tracked-process shutdown.

The empty-label fallback preserves the existing behavior for callers that don’t pass a label: self.shutdown_all(grace_period_s) is called exactly as before. This is the backward-compatible path; the new kill_label kwarg is optional and defaults to “”.

Parameters:
  • grace_period_s (float)

  • kill_label (str)

Return type:

None

force_exit(*, bridge_pgids=(), **kwargs)[source]

Force-kill tracked work and exit with the canonical interrupt code.

The bridge_pgids parameter is the new canonical name; the legacy bridge_pids keyword is accepted via **kwargs for backward compatibility and emits a single loguru warning when used.

Parameters:
  • bridge_pgids (Iterable[int])

  • kwargs (object)

Return type:

None

force_interrupt(*, bridge_pgids=(), **kwargs)[source]

Escalate to immediate tracked-process termination.

The bridge_pgids parameter is the new canonical name; it is forwarded to kill_process_group as PGIDs. The legacy bridge_pids keyword is accepted via **kwargs for backward compatibility and emits a single loguru warning when used. The per-pgid kill loop has been dropped: the real ProcessManager’s shutdown_all(0) already escalates to SIGKILL every active record, so the per-pgid loop was redundant. Callers MUST pass bridge_pgids in new code.

Parameters:
  • bridge_pgids (Iterable[int])

  • kwargs (object)

Return type:

None

record_interrupt()

Record that a user interrupt has been requested.

Return type:

None

ralph.interrupt.controller.controller_from_process_manager(*, process_manager=None, stop_connectivity=None, record_interrupt=<function request_user_interrupt>, kill_process_group=None, hard_exit=None)[source]

Build an InterruptController from a ProcessManager instance.

Wires both shutdown_all and shutdown_all_for_label closures so begin_interrupt(kill_label=...) can target a specific agent process group instead of the generic tracked-process shutdown. The label is the agent’s process label (e.g. "invoke:claude").

Parameters:
  • process_manager (ProcessManager | None)

  • stop_connectivity (Callable[[], None] | None)

  • record_interrupt (Callable[[], None])

  • kill_process_group (Callable[[int, int], None] | None)

  • hard_exit (Callable[[int], None] | None)

Return type:

InterruptController

ralph.interrupt.controller.install_force_kill_handler(on_force_interrupt, *, signal_getter=<function getsignal>, signal_setter=<function signal>, signum=Signals.SIGINT)[source]

Install a temporary signal handler that escalates to forced termination.

The optional signum kwarg defaults to signal.SIGINT so the existing call sites keep their prior behavior. Passing signal.SIGTERM installs a parallel handler so a SIGTERM delivered to the engine triggers the same on_force_interrupt closure (the run-loop finally still runs normal cleanup, then a repeated SIGTERM escalates to force_exit like the second SIGINT).

Parameters:
Return type:

Callable[[], None]

ralph.interrupt.dispatcher

InterruptDispatcher — the single seam that wires interrupt handling.

This module is the canonical home for the constants and helper logic that both the sync handle_keyboard_interrupt path (ralph.pipeline._runner_interrupt) and the asyncio path (ralph.interrupt.asyncio_bridge.install_signal_handlers) route through. The InterruptDispatcher is the single seam that binds an InterruptController to a ProcessManager, an optional connectivity-stop callback, and a hard-exit function. Every future change to the SIGINT wiring happens here; the legacy inline _force_exit helper in _runner_interrupt and the per-callsite kills in asyncio_bridge are now thin wrappers.

The dataclass is intentionally small: a controller, a process manager, a hard-exit field, a poll interval, a hard-kill budget, a kill-label default, and an internal _force_exit_called flag that gives the dispatcher idempotency on force_exit (the controller has none). The begin_interrupt method wraps the controller’s begin_interrupt to inject the dispatcher’s kill_label and to optionally block until the process manager’s active-record list is empty (closing the orphan-process gap when the CLI catches a KeyboardInterrupt).

class ralph.interrupt.dispatcher.InterruptDispatcher(controller, process_manager, hard_exit, poll_interval_s, hard_kill_budget_s, kill_label='invoke:', clock=<built-in function monotonic>, sleep=<built-in function sleep>)[source]

Bases: object

Single seam for SIGINT handling.

Wires an InterruptController to a ProcessManager, a connectivity-stop callback, and a hard-exit function. begin_interrupt forwards to the controller with the dispatcher’s kill_label default ('invoke:') and optionally blocks until the process manager’s active-record list is empty. force_exit is idempotent — repeated calls are no-ops — closing the double-invocation gap that the raw controller has.

Parameters:
  • controller (InterruptController)

  • process_manager (ProcessManager)

  • hard_exit (Callable[[int], None] | None)

  • poll_interval_s (float)

  • hard_kill_budget_s (float)

  • kill_label (str)

  • clock (Callable[[], float])

  • sleep (Callable[[float], None])

begin_interrupt(grace_period_s=None, *, block=False)[source]

Record the interrupt, route to the controller with kill_label.

Parameters:
  • grace_period_s (float | None)

  • block (bool)

Return type:

None

clock()

monotonic() -> float

Monotonic clock, cannot go backward.

force_exit(bridge_pgids=(), **kwargs)[source]

Escalate to immediate tracked-process termination and exit.

Idempotent: repeated calls are no-ops. The first call sets the internal _force_exit_called flag (via object.__setattr__, since the dataclass is frozen), routes through the controller’s force_interrupt for tracked-process shutdown, and then invokes the exit callable. The dispatcher’s own hard_exit field is preferred; if it is None, the controller’s force_exit is invoked so the controller’s injected exit callable is the one that runs (PA-019 thread-through).

The bridge_pids keyword is accepted for backward compatibility; it is deprecated and emits a single loguru warning when used. New callers MUST pass bridge_pgids.

Parameters:
  • bridge_pgids (Iterable[int])

  • kwargs (object)

Return type:

None

run_early_escalation_poll(*, progress_poll_interval_s=None, max_wait_s=None)[source]

Public utility: run the CPU-progress early-escalation poll.

This method is a public utility kept for backward compatibility but is NOT used by the production seam. The production seam in run_shutdown_block uses begin_interrupt(block=True) which routes through the dispatcher’s liveness-based _wait_for_list_active_empty (waiting for process_manager.list_active() to drain or the grace deadline to elapse). The liveness-based path does NOT use CPU-progress detection; an alive-but-zero-CPU long-running agent (writing a checkpoint, releasing a lock, draining a queue) is given the full grace_period_s to die naturally before the dispatcher escalates via force_exit.

This CPU-progress-based method is retained for callers that need it. The method polls the matched active records (whose label starts with the dispatcher’s kill_label) and SIGKILLs them on no-progress. Bounded by max_wait_s (defaults to self.hard_kill_budget_s). Mirrors the prior inline helper in _runner_interrupt._sigint_early_escalation_poll. The method’s dedicated tests in tests/test_interrupt_dispatcher.py (test_early_escalation_poll_kills_when_no_cpu_progress_within_budget, test_early_escalation_poll_does_not_kill_when_cpu_progresses, test_early_escalation_poll_exits_when_process_dies) still pass against the public method.

Parameters:
  • progress_poll_interval_s (float | None)

  • max_wait_s (float | None)

Return type:

None

sleep(seconds)

Delay execution for a given number of seconds. The argument may be a floating-point number for subsecond precision.

ralph.interrupt.dispatcher.dispatcher_from_process_manager(*, process_manager=None, stop_connectivity=None, record_interrupt=None, kill_process_group=None, hard_exit=None, poll_interval_s=0.2, hard_kill_budget_s=1.5, kill_label='invoke:', clock=<built-in function monotonic>, sleep=<built-in function sleep>)[source]

Build an InterruptDispatcher from a ProcessManager instance.

Threads hard_exit and kill_process_group into the controller factory so the controller’s own force_exit path uses the same injected exit callable. The hard_exit is also stored on the dispatcher (the dispatcher’s force_exit invokes the dispatcher’s own field, not the controller’s). The clock and sleep kwargs default to time.monotonic and time.sleep and are forwarded to the dispatcher so tests can inject fakes.

Parameters:
  • process_manager (ProcessManager | None)

  • stop_connectivity (Callable[[], None] | None)

  • record_interrupt (Callable[[], None] | None)

  • kill_process_group (Callable[[int, int], None] | None)

  • hard_exit (Callable[[int], None] | None)

  • poll_interval_s (float)

  • hard_kill_budget_s (float)

  • kill_label (str)

  • clock (Callable[[], float])

  • sleep (Callable[[float], None])

Return type:

InterruptDispatcher

ralph.interrupt.dispatcher.handle_keyboard_interrupt_at_cli(*, process_manager=None, record_interrupt=None, poll_interval_s=0.2, hard_kill_budget_s=1.5, kill_label='invoke:', exit_code=130)[source]

Canonical CLI-level entry point for handling KeyboardInterrupt.

Consolidates the near-duplicate inline catches in ralph.cli.main._run_pipeline and ralph.cli.commands.run.run behind a single helper. The helper:

  1. Builds an InterruptDispatcher via the factory.

  2. Calls begin_interrupt(grace_period_s=..., block=True) so the agent’s process group is SIGTERMed via shutdown_all_for_label('invoke:', grace) and the CLI catch blocks until the process manager’s active list drains (or escalates via force_exit on deadline expiration).

  3. Returns exit_code (default INTERRUPT_EXIT_CODE = 130).

Strategy A: this helper does NOT wrap the dispatcher call in try/except. It propagates any exception. The two CLI catches each wrap the helper call in their own try/except and emit the verbatim “Interrupt dispatcher failed during outer CLI catch” / “during CLI catch” log warning. This preserves bit-for-bit production output and lets the canonical block=True contract be black-box tested in isolation.

Parameters:
  • process_manager (ProcessManager | None)

  • record_interrupt (Callable[[], None] | None)

  • poll_interval_s (float)

  • hard_kill_budget_s (float)

  • kill_label (str)

  • exit_code (int)

Return type:

int

ralph.interrupt.dispatcher.install_force_kill_handler(on_force_interrupt, *, signal_getter=<function getsignal>, signal_setter=<function signal>, signum=Signals.SIGINT)[source]

Install a temporary signal handler that escalates to forced termination.

The optional signum kwarg defaults to signal.SIGINT so the existing call sites keep their prior behavior. Passing signal.SIGTERM installs a parallel handler so a SIGTERM delivered to the engine triggers the same on_force_interrupt closure (the run-loop finally still runs normal cleanup, then a repeated SIGTERM escalates to force_exit like the second SIGINT).

Parameters:
Return type:

Callable[[], None]

ralph.interrupt.dispatcher.run_shutdown_block(dispatcher, *, grace_period_s, join_timeout_s=1.6, error_log_message='Interrupt shutdown block raised')[source]

Canonical seam for the first-SIGINT shutdown block.

Both the SYNC handle_keyboard_interrupt entry point (ralph.pipeline._runner_interrupt._begin_interrupt) and the asyncio install_signal_handlers entry point (ralph.interrupt.asyncio_bridge._shutdown_block) route through this helper so the bodies cannot drift. The 7th architectural seam is error_log_message: the SYNC path passes "Interrupt controller raised during KeyboardInterrupt" (preserved for bit-for-bit production log output) and the asyncio path passes the existing "Interrupt shutdown block raised" (preserved for the same reason).

The body is a single call to dispatcher.begin_interrupt(grace_period_s=grace_period_s, block=True) only — no daemon thread, no threading.Thread.join. The dispatcher uses its liveness-based _wait_for_list_active_empty (via block=True) to wait for the process manager’s active-record list to drain, escalating via force_exit only when the grace deadline elapses with records still active. This replaces the prior CPU-progress-based run_early_escalation_poll daemon thread, which SIGKILLed alive-but-zero-CPU long-running agents (writing checkpoints, releasing locks, draining queues) prematurely. The run_early_escalation_poll method is kept on the dispatcher as a public utility NOT used by the production seam (see its docstring); the method’s dedicated tests still pass against the public method.

The join_timeout_s parameter is now unused and is kept for backward compatibility with the prior call shape. The two call sites (ralph/pipeline/_runner_interrupt.py and ralph/interrupt/asyncio_bridge.py) do not pass the kwarg and the helper is byte-for-byte equivalent at those sites.

The helper is added to __all__ so from ralph.interrupt.dispatcher import * exposes it. See ADR-0001 D7 and D8.

Parameters:
  • dispatcher (InterruptDispatcher)

  • grace_period_s (float)

  • join_timeout_s (float)

  • error_log_message (str)

Return type:

None

ralph.interrupt.state

Shared process-local interrupt state helpers.

ralph.interrupt.state.request_user_interrupt()[source]

Record that a user interrupt has been requested.

Return type:

None

ralph.interrupt.state.user_interrupted_occurred()[source]

Return True if any user interrupt has occurred in this process.

Return type:

bool

ralph.interrupt.signal_getter

Protocol for signal getter callables.

class ralph.interrupt.signal_getter.SignalGetter(*args, **kwargs)[source]

Bases: Protocol

Protocol for signal.getsignal-compatible callables.

ralph.interrupt.signal_handler

Shared signal handler typing helpers.

ralph.interrupt.signal_setter

Protocol for signal setter callables.

class ralph.interrupt.signal_setter.SignalSetter(*args, **kwargs)[source]

Bases: Protocol

Protocol for signal.signal-compatible callables.

ralph.files

Public file-state helpers used by checkpoint and resume flows.

These exports capture and validate the small set of Ralph-managed files whose state matters for resume safety and integrity checks.

ralph.files.operations

File capture and state-tracking helpers for Ralph checkpoints.

class ralph.files.operations.FileSnapshot(path, checksum, size, exists)[source]

Bases: object

Captured state for a single tracked file.

Parameters:
  • path (Path)

  • checksum (str)

  • size (int)

  • exists (bool)

class ralph.files.operations.FileStateIssue(kind, path)[source]

Bases: object

A mismatch between captured and current file state.

Parameters:
class ralph.files.operations.FileStateKind(*values)[source]

Bases: Enum

Kinds of file-state drift detected during checkpoint validation.

class ralph.files.operations.FileSystemState(root, files)[source]

Bases: object

Snapshots for tracked Ralph files rooted at a workspace path.

Parameters:
ralph.files.operations.calculate_checksum(path)[source]

Return the SHA-256 checksum for a file.

Parameters:

path (Path | str)

Return type:

str

ralph.files.operations.capture_file_snapshot(path, *, root=None)[source]

Capture the current state of a file relative to a workspace root.

Parameters:
  • path (Path | str)

  • root (Path | str | None)

Return type:

FileSnapshot

ralph.files.operations.capture_file_system_state(root, *, tracked_paths=(PosixPath('PROMPT.md'), PosixPath('.agent/PLAN.md'), PosixPath('.agent/ISSUES.md'), PosixPath('.agent/DEVELOPMENT_RESULT.md'), PosixPath('.agent/FIX_RESULT.md'), PosixPath('.agent/DEVELOPMENT_ANALYSIS_DECISION.md'), PosixPath('.agent/REVIEW_ANALYSIS_DECISION.md'), PosixPath('.agent/config.toml'), PosixPath('.agent/start_commit'), PosixPath('.agent/NOTES.md'), PosixPath('.agent/status')))[source]

Capture snapshots for tracked files under a workspace root.

Parameters:
  • root (Path | str)

  • tracked_paths (list[Path | str] | tuple[Path | str, ...])

Return type:

FileSystemState

ralph.files.operations.validate_file_system_state(state, root=None)[source]

Compare current tracked files with a captured checkpoint snapshot.

Parameters:
Return type:

list[FileStateIssue]

ralph.guidelines

Language-specific review guidelines for the Python port.

class ralph.guidelines.GoGuidelines(frameworks=())[source]

Bases: object

Review guidelines for Go codebases.

Parameters:

frameworks (tuple[str, ...]) – Optional framework names used to add stack-specific guidance.

summary()[source]

Return a short human-readable summary.

Return type:

str

total_checks()[source]

Return the total number of configured checks.

Return type:

int

class ralph.guidelines.JavaGuidelines(frameworks=())[source]

Bases: object

Java language-specific review checks.

Parameters:

frameworks (tuple[str, ...]) – Optional framework names used to add stack-specific guidance.

summary()[source]

Return a short human-readable summary.

Return type:

str

total_checks()[source]

Return the total number of configured checks.

Return type:

int

class ralph.guidelines.JavaScriptGuidelines(frameworks=(), typescript=False)[source]

Bases: object

JavaScript and TypeScript review checks.

Parameters:
  • frameworks (tuple[str, ...]) – Optional framework names used to add stack-specific guidance.

  • typescript (bool) – When true, include the TypeScript-specific checks from the Rust implementation alongside the JavaScript baseline.

summary()[source]

Return a short human-readable summary.

Return type:

str

total_checks()[source]

Return the total number of configured checks.

Return type:

int

class ralph.guidelines.PHPGuidelines(frameworks=())[source]

Bases: object

Review guidelines for PHP codebases.

Parameters:

frameworks (tuple[str, ...]) – Optional framework names that activate stack-specific checks.

summary()[source]

Return a short human-readable summary.

Return type:

str

total_checks()[source]

Return the total number of configured checks.

Return type:

int

class ralph.guidelines.PythonGuidelines(frameworks=())[source]

Bases: object

Review guidelines for Python codebases.

Parameters:

frameworks (tuple[str, ...]) – Optional framework names used to add stack-specific guidance.

summary()[source]

Return a short human-readable summary.

Return type:

str

total_checks()[source]

Return the total number of configured checks.

Return type:

int

class ralph.guidelines.RubyGuidelines(frameworks=())[source]

Bases: object

Ruby review checks.

Parameters:

frameworks (tuple[str, ...]) – Optional framework names used to add stack-specific guidance.

as_review_guidelines()[source]

Return this instance typed as a review guidelines bundle.

Return type:

ReviewGuidelines

summary()[source]

Return a short human-readable summary.

Return type:

str

total_checks()[source]

Return the total number of configured checks.

Return type:

int

class ralph.guidelines.RustGuidelines(quality_checks=<factory>, security_checks=<factory>, performance_checks=<factory>, testing_checks=<factory>, documentation_checks=<factory>, idioms=<factory>, anti_patterns=<factory>, concurrency_checks=<factory>, resource_checks=<factory>, observability_checks=<factory>, secrets_checks=<factory>, api_design_checks=<factory>)[source]

Bases: object

Rust language-specific review checks.

The categories mirror the review guideline structure used by the Rust implementation while including Rust-specific guidance around ownership, lifetimes, Clippy, panic-safety, and framework-oriented web handlers.

Parameters:
  • quality_checks (list[str])

  • security_checks (list[str])

  • performance_checks (list[str])

  • testing_checks (list[str])

  • documentation_checks (list[str])

  • idioms (list[str])

  • anti_patterns (list[str])

  • concurrency_checks (list[str])

  • resource_checks (list[str])

  • observability_checks (list[str])

  • secrets_checks (list[str])

  • api_design_checks (list[str])

summary()[source]

Return a short human-readable summary.

Return type:

str

total_checks()[source]

Return the total number of configured checks.

Return type:

int

class ralph.guidelines.StackGuidelines(quality_checks=<factory>, security_checks=<factory>, performance_checks=<factory>, testing_checks=<factory>, documentation_checks=<factory>, idioms=<factory>, anti_patterns=<factory>, concurrency_checks=<factory>, resource_checks=<factory>, observability_checks=<factory>, secrets_checks=<factory>, api_design_checks=<factory>)[source]

Bases: object

Merged review guidelines accumulated from all detected language handlers.

Parameters:
  • quality_checks (Sequence[str])

  • security_checks (Sequence[str])

  • performance_checks (Sequence[str])

  • testing_checks (Sequence[str])

  • documentation_checks (Sequence[str])

  • idioms (Sequence[str])

  • anti_patterns (Sequence[str])

  • concurrency_checks (Sequence[str])

  • resource_checks (Sequence[str])

  • observability_checks (Sequence[str])

  • secrets_checks (Sequence[str])

  • api_design_checks (Sequence[str])

ralph.guidelines.get_stack_guidelines(workspace, root='')[source]

Build merged review guidelines from the stack detected in workspace.

Parameters:
Return type:

StackGuidelines

ralph.guidelines.go

Go-specific review guidelines.

Ported from the canonical Rust implementation in ralph-workflow/src/guidelines/go.rs and adapted for the Python port. Includes core Go checks plus framework-specific additions for Gin, Chi, Fiber, and Echo projects.

class ralph.guidelines.go.GoGuidelines(frameworks=())[source]

Bases: object

Review guidelines for Go codebases.

Parameters:

frameworks (tuple[str, ...]) – Optional framework names used to add stack-specific guidance.

summary()[source]

Return a short human-readable summary.

Return type:

str

total_checks()[source]

Return the total number of configured checks.

Return type:

int

ralph.guidelines.java

Java-specific review guideline categories.

Ported from the canonical Rust implementation in ralph-workflow/src/guidelines/java.rs and adapted for the Python port. The module exposes a lightweight data container with optional Spring-specific extensions for Java codebases.

class ralph.guidelines.java.JavaGuidelines(frameworks=())[source]

Bases: object

Java language-specific review checks.

Parameters:

frameworks (tuple[str, ...]) – Optional framework names used to add stack-specific guidance.

summary()[source]

Return a short human-readable summary.

Return type:

str

total_checks()[source]

Return the total number of configured checks.

Return type:

int

ralph.guidelines.javascript

JavaScript-specific review guideline categories.

Ported from the canonical Rust implementation in ralph-workflow/src/guidelines/javascript.rs and adapted for the Python port. The module models core JavaScript guidance plus optional framework-specific extensions for React, Vue, Angular, Node backends, SSR stacks, and TypeScript.

class ralph.guidelines.javascript.JavaScriptGuidelines(frameworks=(), typescript=False)[source]

Bases: object

JavaScript and TypeScript review checks.

Parameters:
  • frameworks (tuple[str, ...]) – Optional framework names used to add stack-specific guidance.

  • typescript (bool) – When true, include the TypeScript-specific checks from the Rust implementation alongside the JavaScript baseline.

summary()[source]

Return a short human-readable summary.

Return type:

str

total_checks()[source]

Return the total number of configured checks.

Return type:

int

ralph.guidelines.php

PHP-specific review guideline categories.

Ported from the canonical Rust implementation in ralph-workflow/src/guidelines/php.rs and adapted for the Python port. The module models core PHP guidance plus optional Laravel and Symfony extensions.

class ralph.guidelines.php.PHPGuidelines(frameworks=())[source]

Bases: object

Review guidelines for PHP codebases.

Parameters:

frameworks (tuple[str, ...]) – Optional framework names that activate stack-specific checks.

summary()[source]

Return a short human-readable summary.

Return type:

str

total_checks()[source]

Return the total number of configured checks.

Return type:

int

ralph.guidelines.python

Python-specific review guidelines.

Port of the Rust review guidance for Python projects, with Python-native structure. Includes core Python checks plus framework-specific additions for Django, FastAPI, and Flask projects.

class ralph.guidelines.python.PythonGuidelines(frameworks=())[source]

Bases: object

Review guidelines for Python codebases.

Parameters:

frameworks (tuple[str, ...]) – Optional framework names used to add stack-specific guidance.

summary()[source]

Return a short human-readable summary.

Return type:

str

total_checks()[source]

Return the total number of configured checks.

Return type:

int

ralph.guidelines.ruby

Ruby-specific review guideline categories.

Ported from the canonical Rust implementation in ralph-workflow/src/guidelines/ruby.rs and adapted for the Python port. The module models core Ruby guidance plus optional Rails and Sinatra extensions.

class ralph.guidelines.ruby.RubyGuidelines(frameworks=())[source]

Bases: object

Ruby review checks.

Parameters:

frameworks (tuple[str, ...]) – Optional framework names used to add stack-specific guidance.

as_review_guidelines()[source]

Return this instance typed as a review guidelines bundle.

Return type:

ReviewGuidelines

summary()[source]

Return a short human-readable summary.

Return type:

str

total_checks()[source]

Return the total number of configured checks.

Return type:

int

ralph.guidelines.rust

Rust-specific review guideline categories.

Ported from the canonical Rust implementation in ralph-workflow/src/guidelines/rust.rs and adapted for the Python port. The module exposes a lightweight data container that review prompt builders can consume directly.

class ralph.guidelines.rust.RustGuidelines(quality_checks=<factory>, security_checks=<factory>, performance_checks=<factory>, testing_checks=<factory>, documentation_checks=<factory>, idioms=<factory>, anti_patterns=<factory>, concurrency_checks=<factory>, resource_checks=<factory>, observability_checks=<factory>, secrets_checks=<factory>, api_design_checks=<factory>)[source]

Bases: object

Rust language-specific review checks.

The categories mirror the review guideline structure used by the Rust implementation while including Rust-specific guidance around ownership, lifetimes, Clippy, panic-safety, and framework-oriented web handlers.

Parameters:
  • quality_checks (list[str])

  • security_checks (list[str])

  • performance_checks (list[str])

  • testing_checks (list[str])

  • documentation_checks (list[str])

  • idioms (list[str])

  • anti_patterns (list[str])

  • concurrency_checks (list[str])

  • resource_checks (list[str])

  • observability_checks (list[str])

  • secrets_checks (list[str])

  • api_design_checks (list[str])

summary()[source]

Return a short human-readable summary.

Return type:

str

total_checks()[source]

Return the total number of configured checks.

Return type:

int

ralph.guidelines.stack

Stack-guided review guidelines for the Python port.

class ralph.guidelines.stack.StackGuidelines(quality_checks=<factory>, security_checks=<factory>, performance_checks=<factory>, testing_checks=<factory>, documentation_checks=<factory>, idioms=<factory>, anti_patterns=<factory>, concurrency_checks=<factory>, resource_checks=<factory>, observability_checks=<factory>, secrets_checks=<factory>, api_design_checks=<factory>)[source]

Bases: object

Merged review guidelines accumulated from all detected language handlers.

Parameters:
  • quality_checks (Sequence[str])

  • security_checks (Sequence[str])

  • performance_checks (Sequence[str])

  • testing_checks (Sequence[str])

  • documentation_checks (Sequence[str])

  • idioms (Sequence[str])

  • anti_patterns (Sequence[str])

  • concurrency_checks (Sequence[str])

  • resource_checks (Sequence[str])

  • observability_checks (Sequence[str])

  • secrets_checks (Sequence[str])

  • api_design_checks (Sequence[str])

ralph.guidelines.stack.get_stack_guidelines(workspace, root='')[source]

Build merged review guidelines from the stack detected in workspace.

Parameters:
Return type:

StackGuidelines

ralph.language_detector

Language detection helpers for the Python port.

class ralph.language_detector.ProjectStack(primary_language='Unknown', secondary_languages=<factory>, frameworks=<factory>, has_tests=False, test_framework=None, package_manager=None)[source]

Bases: object

Detected project stack summary.

Parameters:
  • primary_language (str)

  • secondary_languages (list[str])

  • frameworks (list[str])

  • has_tests (bool)

  • test_framework (str | None)

  • package_manager (str | None)

ralph.language_detector.detect_languages(workspace_or_root, root='')[source]

Return detected language names ordered by source-file prevalence.

Parameters:
  • workspace_or_root (Workspace | str | Path)

  • root (str)

Return type:

list[str]

ralph.language_detector.get_project_stack(workspace_or_root, root='')[source]

Detect the full project stack including languages, frameworks, and test infrastructure.

Parameters:
  • workspace_or_root (Workspace | str | Path)

  • root (str)

Return type:

ProjectStack

ralph.language_detector.extensions

File extension to language mapping for project detection.

ralph.language_detector.extensions.extension_to_language(extension)[source]

Map a file extension to a language name.

Parameters:

extension (str)

Return type:

str | None

ralph.language_detector.extensions.is_non_primary_language(language)[source]

Return whether a language should not be preferred as primary.

Parameters:

language (str)

Return type:

bool

ralph.language_detector.models

Data models for detected project language stacks.

class ralph.language_detector.models.ProjectStack(primary_language='Unknown', secondary_languages=<factory>, frameworks=<factory>, has_tests=False, test_framework=None, package_manager=None)[source]

Bases: object

Detected project stack summary.

Parameters:
  • primary_language (str)

  • secondary_languages (list[str])

  • frameworks (list[str])

  • has_tests (bool)

  • test_framework (str | None)

  • package_manager (str | None)

ralph.language_detector.scanner

Workspace scanning utilities for language detection.

ralph.language_detector.scanner.collect_signature_files(workspace, root='')[source]

Return a map from lowercased signature file name to a list of matching paths.

Parameters:
Return type:

dict[str, list[str]]

ralph.language_detector.scanner.count_extensions(workspace, root='')[source]

Return a map from lowercase file extension to file count under root.

Parameters:
Return type:

dict[str, int]

ralph.language_detector.scanner.detect_tests(workspace, root='', primary_language='Unknown')[source]

Return True if the workspace contains any recognisable test directories or test files.

Parameters:
  • workspace (Workspace)

  • root (str)

  • primary_language (str)

Return type:

bool

ralph.language_detector.scanner.is_test_file_name(file_name, primary_language, path_components)[source]

Return True if file_name matches the test file convention for primary_language.

Parameters:
  • file_name (str)

  • primary_language (str)

  • path_components (list[str])

Return type:

bool

ralph.language_detector.scanner.iter_files(workspace, root='')[source]

Yield every file path under root up to MAX_FILES_TO_SCAN files.

Parameters:
Return type:

Iterator[str]

ralph.language_detector.scanner.join_path(parent, child)[source]

Join parent and child as a normalised POSIX path.

Parameters:
  • parent (str)

  • child (str)

Return type:

str

ralph.language_detector.scanner.normalize_path(path)[source]

Normalise path to a forward-slash form, returning empty string for the root.

Parameters:

path (str)

Return type:

str

ralph.language_detector.scanner.should_skip_dir_name(name)[source]

Return True if name is a hidden directory or a known build/cache directory.

Parameters:

name (str)

Return type:

bool

ralph.language_detector.signatures

Signature file heuristics for framework and package manager detection.

class ralph.language_detector.signatures.DetectionResults[source]

Bases: object

Accumulator for detected frameworks, test frameworks, and package managers.

ralph.language_detector.signatures.detect_signature_files(workspace, root='')[source]

Scan workspace signature files to detect frameworks, test frameworks, and package manager.

Parameters:
Return type:

tuple[list[str], str | None, str | None]

Prompts

ralph.prompts

Prompt template utilities: capability variables, flag sets, and template parsing.

This package provides the public surface for building prompt template variables and parsing prompt template files. It is used by phase handlers to materialise agent-facing prompts from Jinja2 templates stored under ralph/prompts/templates/.

Main entry points:

  • capability_template_variables(capabilities, flags) — builds the template variable dict for a given CapabilitySet and PolicyFlagSet. Used when rendering prompts that reference capability gates.

  • capability_template_variables_from_session(session) — convenience wrapper that extracts capabilities and flags from a live SessionCapabilities object.

  • default_caps_and_flags_for_drain(drain_class) — returns the default capability set and policy flags for a drain class; used for prompt preview and testing.

  • visible_mcp_tool_names(session) — returns the list of MCP tool names visible to the agent, based on its granted capabilities.

  • CapabilitySet, PolicyFlag, PolicyFlagSet — typed sets for capability and policy-flag resolution.

  • SessionCapabilities — the per-session capability snapshot passed in from the MCP server startup.

  • template_parsing — module-level re-export of ralph.prompts.template_parsing; provides parse_template_file and related helpers.

For full template rendering (Jinja2 engine, context building, payload materialisation), see ralph.prompts.materialize and ralph.prompts.template_engine.

class ralph.prompts.CapabilitySet(values=None)[source]

Bases: object

Lightweight set of Ralph capabilities.

Parameters:

values (Iterable[RalphCapability] | None)

class ralph.prompts.PolicyFlag(*values)[source]

Bases: StrEnum

Policy flags that may modify prompt rendering.

class ralph.prompts.PolicyFlagSet(values=None)[source]

Bases: object

Set of Ralph policy flags.

Parameters:

values (Iterable[PolicyFlag] | None)

class ralph.prompts.SessionCapabilities(capabilities, policy_flags, tool_name_prefix='')[source]

Bases: object

Helper bundling capabilities and policy flags for prompt rendering.

Parameters:
as_parts()[source]

Return the (capabilities, policy_flags) tuple this bundle holds.

Return type:

tuple[CapabilitySet, PolicyFlagSet]

classmethod defaults_for_drain(drain, *, tool_name_prefix='')[source]

Build a SessionCapabilities using the bundled defaults for the given drain.

Parameters:
Return type:

SessionCapabilities

classmethod from_drain(drain)[source]

Return the bundled default (CapabilitySet, PolicyFlagSet) pair for the given drain.

Parameters:

drain (SessionDrain)

Return type:

tuple[CapabilitySet, PolicyFlagSet]

classmethod from_session(session)[source]

Build a SessionCapabilities from the live session’s identifiers.

Parameters:

session (AgentSession)

Return type:

SessionCapabilities

classmethod new(capabilities, policy_flags, *, tool_name_prefix='')[source]

Build a SessionCapabilities from explicit capability and policy-flag sets.

Parameters:
Return type:

ralph.prompts.types.SessionCapabilities

ralph.prompts.capability_template_variables(capabilities, policy_flags, *, tool_name_prefix='')[source]

Render prompt template variables for the given capabilities, flags, and tool-name prefix.

Parameters:
Return type:

dict[str, str]

ralph.prompts.capability_template_variables_from_session(session, *, tool_name_prefix='')[source]

Render capability template variables from the live session’s identifiers.

Parameters:
Return type:

dict[str, str]

ralph.prompts.default_caps_and_flags_for_drain(drain)[source]

Return the bundled default (CapabilitySet, PolicyFlagSet) pair for the given drain.

Parameters:

drain (SessionDrain)

Return type:

tuple[CapabilitySet, PolicyFlagSet]

ralph.prompts.visible_mcp_tool_names(capabilities)[source]

Return canonical MCP tool names a session with the given capabilities may call.

Parameters:

capabilities (CapabilitySet)

Return type:

list[str]

ralph.prompts.commit

Commit prompt generation utilities.

class ralph.prompts.commit.CommitPromptPayloadConfig(output_dir=None, name_prefix='commit_message')[source]

Bases: object

Configuration for where commit prompt payload files are written.

Parameters:
  • output_dir (Path | None)

  • name_prefix (str)

ralph.prompts.commit.prompt_commit_message(diff, *, template_registry=None, partials=None, submit_artifact_tool_names=('ralph_submit_artifact',), payload_config=None)[source]

Return the commit message prompt for the provided diff.

Parameters:
Return type:

str

ralph.prompts.commit.prompt_commit_message_for_opencode(diff, *, submit_artifact_tool_name, payload_config=None)[source]

Return a simplified commit message prompt for OpenCode’s single-tool interface.

Parameters:
Return type:

str

ralph.prompts.debug_dump

Helpers for persisting rendered prompts for debugging.

ralph.prompts.debug_dump.clear_multimodal_sidecar(workspace, phase, *, worker_namespace=None)[source]

Remove the multimodal handoff sidecar for a shared or worker-local prompt.

Parameters:
  • workspace (Workspace)

  • phase (str)

  • worker_namespace (Path | None)

Return type:

None

ralph.prompts.debug_dump.collect_media_entries_for_phase(workspace, phase)[source]

Read media entries from the persistent session index for a phase.

Parameters:
Return type:

list[MultimodalSidecarEntry]

ralph.prompts.debug_dump.dump_rendered_prompt(workspace, phase, prompt, *, worker_namespace=None)[source]

Write the rendered prompt to the debug dump path and return the path.

Parameters:
  • workspace (Workspace)

  • phase (str)

  • prompt (str)

  • worker_namespace (Path | None)

Return type:

str

ralph.prompts.debug_dump.media_cache_artifact_path(artifact_id)[source]

Path for the durable byte cache of a media artifact.

Bytes written here survive the session and enable cross-session replay.

Parameters:

artifact_id (str)

Return type:

str

ralph.prompts.debug_dump.media_registry_path()[source]

Path for the centralized media artifact registry.

Maps artifact_id to full v2 metadata for cross-session replay lookup.

Return type:

str

ralph.prompts.debug_dump.media_session_path(phase)[source]

Path for the persistent media session index written by the MCP server.

This file accumulates artifact metadata for each media file loaded during a session via read_media. The runner reads it at the next prompt materialization to carry media context forward across sessions.

Parameters:

phase (str)

Return type:

str

ralph.prompts.debug_dump.multimodal_sidecar_path(phase)[source]

Return the workspace-relative path for a phase’s multimodal handoff sidecar.

Parameters:

phase (str)

Return type:

str

ralph.prompts.debug_dump.prompt_dump_path(phase)[source]

Return the workspace-relative path for a phase’s debug prompt dump.

Parameters:

phase (str)

Return type:

str

ralph.prompts.debug_dump.worker_multimodal_sidecar_path(worker_namespace, phase)[source]

Return the worker-local multimodal handoff sidecar path for a phase.

Parameters:
  • worker_namespace (Path)

  • phase (str)

Return type:

Path

ralph.prompts.debug_dump.worker_prompt_dump_path(worker_namespace, phase)[source]

Return the worker-local prompt dump path for a phase.

Parameters:
  • worker_namespace (Path)

  • phase (str)

Return type:

Path

ralph.prompts.debug_dump.write_multimodal_sidecar(workspace, phase, entries, *, worker_namespace=None)[source]

Persist the phase multimodal handoff sidecar for shared or worker-local prompts.

Parameters:
  • workspace (Workspace)

  • phase (str)

  • entries (list[MultimodalSidecarEntry])

  • worker_namespace (Path | None)

Return type:

None

ralph.prompts.developer

Developer prompt helpers for MCP RFC-009 templates.

class ralph.prompts.developer.DeveloperPromptInputs(prompt_content, plan_content, analysis_feedback_content=None, plan_path='', analysis_feedback_path='', artifact_history_path='', artifact_history_dir='', current_prompt_path='', payload_root='', prompt_name_prefix='development', last_retry_error='', skills_inline_content='', has_docs_mcp=False)[source]

Bases: object

Inputs for rendering a developer-iteration prompt.

Parameters:
  • prompt_content (str | None)

  • plan_content (str | None)

  • analysis_feedback_content (str | None)

  • plan_path (str)

  • analysis_feedback_path (str)

  • artifact_history_path (str)

  • artifact_history_dir (str)

  • current_prompt_path (str)

  • payload_root (str)

  • prompt_name_prefix (str)

  • last_retry_error (str)

  • skills_inline_content (str)

  • has_docs_mcp (bool)

class ralph.prompts.developer.PlanningPromptInputs(prompt_content, plan_content=None, analysis_feedback_content=None, plan_path='', analysis_feedback_path='', artifact_history_path='', artifact_history_dir='', current_prompt_path='', payload_root='', last_retry_error='', skills_inline_content='', has_docs_mcp=False)[source]

Bases: object

Inputs for rendering a planning-phase prompt.

Parameters:
  • prompt_content (str | None)

  • plan_content (str | None)

  • analysis_feedback_content (str | None)

  • plan_path (str)

  • analysis_feedback_path (str)

  • artifact_history_path (str)

  • artifact_history_dir (str)

  • current_prompt_path (str)

  • payload_root (str)

  • last_retry_error (str)

  • skills_inline_content (str)

  • has_docs_mcp (bool)

ralph.prompts.developer.prompt_developer_iteration_xml_with_context(context, inputs, workspace, session_caps, *, template_name='developer_iteration.jinja')[source]

Render the developer-iteration prompt, falling back to a static template on error.

Parameters:
Return type:

str

ralph.prompts.developer.prompt_planning_xml_with_context(context, inputs, workspace, session_caps, *, template_name='planning.jinja')[source]

Render the planning-phase prompt, falling back to a static template on error.

Parameters:
Return type:

str

ralph.prompts.materialize

Policy-selected prompt materialization.

exception ralph.prompts.materialize.MissingPlanHandoffError[source]

Bases: ValueError

Raised when a template requires an existing plan handoff that is absent.

class ralph.prompts.materialize.PromptPhaseContext(phase, workspace, pipeline_policy, session_caps, workspace_root)[source]

Bases: object

Required inputs for prompt materialization: the phase, workspace, and policy bindings.

Parameters:
class ralph.prompts.materialize.PromptPhaseOptions(artifacts_policy=None, worker_namespace=None, previous_phase=None, resume_existing_phase=False, multimodal_entries=None, work_unit=None)[source]

Bases: object

Optional inputs for prompt materialization with sensible defaults.

Parameters:
  • artifacts_policy (ArtifactsPolicy | None)

  • worker_namespace (Path | None)

  • previous_phase (str | None)

  • resume_existing_phase (bool)

  • multimodal_entries (list[MultimodalSidecarEntry] | None)

  • work_unit (WorkUnit | None)

ralph.prompts.materialize.collect_media_entries_for_phase(workspace, phase)[source]

Read media entries from the persistent session index for a phase.

Parameters:
Return type:

list[MultimodalSidecarEntry]

ralph.prompts.materialize.materialize_prompt_for_phase(context=None, options=None, **kwargs)[source]

Render and persist the prompt for a pipeline phase, returning its dump path.

Parameters:
Return type:

str

ralph.prompts.materialize.prompt_file_for_phase(phase)[source]

Return the workspace-relative path where a phase’s prompt is stored.

Parameters:

phase (str)

Return type:

str

ralph.prompts.materialize.submit_artifact_tool_name_for_transport(transport)[source]

Return the submit-artifact tool name for the given transport.

Parameters:

transport (AgentTransport | None)

Return type:

str

ralph.prompts.materialize.tool_name_prefix_for_transport(transport)[source]

Return the tool name prefix for the given agent transport.

Prompt templates must use the same MCP tool names the active transport sees, or the model calls a name that does not exist. OpenCode namespaces remote MCP tools as <server>_<tool> (Ralph’s server is ralph), so its prompts use the ralph_ prefix — matching the ralph_* permission Ralph already grants in the OpenCode config. Claude AND Codex use mcp__ralph__.

Parameters:

transport (AgentTransport | None)

Return type:

str

ralph.prompts.materialize_support

Shared helper utilities for prompt materialization.

ralph.prompts.materialize_support.current_prompt_variables(prompt_content, current_prompt_path)[source]

Return the prompt variables for the current prompt path.

Parameters:
  • prompt_content (str | None)

  • current_prompt_path (str)

Return type:

dict[str, str]

ralph.prompts.materialize_support.merged_variables(base, session_caps)[source]

Merge base template variables with session capability variables.

Parameters:
Return type:

dict[str, str]

ralph.prompts.materialize_support.persist_current_prompt(workspace_root, prompt_content, *, worker_namespace=None)[source]

Persist the active prompt content to the workspace prompt file.

Parameters:
  • workspace_root (Path)

  • prompt_content (str | None)

  • worker_namespace (Path | None)

Return type:

str

ralph.prompts.materialize_support.phase_payload_variables(*, phase, workspace_root, values, worker_namespace=None)[source]

Build prompt payload variables, writing oversized values to disk.

Parameters:
  • phase (str)

  • workspace_root (Path)

  • values (dict[str, str])

  • worker_namespace (Path | None)

Return type:

dict[str, str]

ralph.prompts.payload_refs

Helpers for replacing oversized prompt payloads with file references.

ralph.prompts.payload_refs.build_prompt_payload_variables(values, *, prompt_name_prefix, write_payload)[source]

Return template variables with oversized values replaced by file references.

Parameters:
  • values (Mapping[str, str])

  • prompt_name_prefix (str)

  • write_payload (PromptPayloadWriter)

Return type:

dict[str, str]

ralph.prompts.payload_refs.prompt_payload_relative_path(prompt_name_prefix, variable_name)[source]

Return the relative path for a prompt payload file given its prefix and variable name.

Parameters:
  • prompt_name_prefix (str)

  • variable_name (str)

Return type:

str

ralph.prompts.payload_refs.sanitize_surrogates(text)[source]

Replace lone surrogate code points so the result is strictly UTF-8 encodable.

Parameters:

text (str)

Return type:

str

ralph.prompts.payload_refs.write_payload_to_directory(output_dir, relative_path, content)[source]

Write payload content to a directory and return the absolute path.

Parameters:
  • output_dir (Path)

  • relative_path (str)

  • content (str)

Return type:

str

ralph.prompts.reviewer

Utility functions for rendering reviewer prompts.

ralph.prompts.reviewer.CHANGES_PLACEHOLDER = '(no diff available)'

Fallback text when reviewer change description is empty.

ralph.prompts.reviewer.PLAN_PLACEHOLDER = '(no plan available)'

Fallback text when reviewer plan content is empty.

ralph.prompts.reviewer.prompt_review(plan, changes, *, template_registry=None, template_name='review')

Backward-compatible alias matching the original reviewer prompt name.

Parameters:
  • plan (str)

  • changes (str)

  • template_registry (TemplateRegistry | None)

  • template_name (str)

Return type:

str

ralph.prompts.reviewer.render_review_prompt(plan, changes, *, template_registry=None, template_name='review')[source]

Render the reviewer prompt using the requested template.

If a template registry is provided, the named template is used. Missing placeholders or templates fall back to the built-in default template.

Parameters:
  • plan (str)

  • changes (str)

  • template_registry (TemplateRegistry | None)

  • template_name (str)

Return type:

str

ralph.prompts.system_prompt

System prompt materialization for supported agent transports.

ralph.prompts.system_prompt.build_system_prompt(*, phase_name, current_prompt_path, current_plan_path=None)[source]

Build the system prompt text that points the agent at durable task context files.

Parameters:
  • phase_name (str)

  • current_prompt_path (str)

  • current_plan_path (str | None)

Return type:

str

ralph.prompts.system_prompt.materialize_system_prompt(*, workspace_root, name, default_current_prompt=None, worker_namespace=None)[source]

Write a system prompt file for the named agent and return its path.

Parameters:
  • workspace_root (Path)

  • name (str)

  • default_current_prompt (str | None)

  • worker_namespace (Path | None)

Return type:

str

ralph.prompts.system_prompt.worker_current_prompt_path(worker_namespace)[source]

Return the worker-local mirror path for CURRENT_PROMPT.md.

Parameters:

worker_namespace (Path)

Return type:

Path

ralph.prompts.system_prompt.worker_system_prompt_path(worker_namespace, phase)[source]

Return the worker-local system prompt materialization path.

Parameters:
  • worker_namespace (Path)

  • phase (str)

Return type:

Path

ralph.prompts.template_context

Template registry/ context for prompt generation.

class ralph.prompts.template_context.TemplateContext(registry, partials)[source]

Bases: object

Bundled registry and partials for prompt template rendering.

Parameters:

ralph.prompts.template_engine

Minimal rendering engine for RFC-009 prompt templates.

exception ralph.prompts.template_engine.TemplateRenderingError[source]

Bases: Exception

Raised when a template cannot be rendered.

ralph.prompts.template_engine.render_template(template_text, variables, partials)[source]

Render the provided template text with partials and variables.

Parameters:
  • template_text (str)

  • variables (Mapping[str, str])

  • partials (Mapping[str, str])

Return type:

str

ralph.prompts.template_parsing

Template parsing helpers ported from the Rust prompt templates module.

class ralph.prompts.template_parsing.ConditionalNode(condition, truthy, falsy)[source]

Bases: TemplateNode

An {% if condition %} block with truthy and falsy branches.

Parameters:
class ralph.prompts.template_parsing.LoopNode(variable, iterable, body)[source]

Bases: TemplateNode

A {% for x in iterable %} loop with a body.

Parameters:
class ralph.prompts.template_parsing.PartialNode(name)[source]

Bases: TemplateNode

A {{> partial_name }} include directive.

Parameters:

name (str)

class ralph.prompts.template_parsing.TemplateNode[source]

Bases: object

Base class for parsed template nodes.

class ralph.prompts.template_parsing.TextNode(text)[source]

Bases: TemplateNode

A literal text segment in a parsed template.

Parameters:

text (str)

class ralph.prompts.template_parsing.VariableNode(name, default, placeholder)[source]

Bases: TemplateNode

A {{ VARIABLE }} substitution with an optional default value.

Parameters:
  • name (str)

  • default (str | None)

  • placeholder (str)

ralph.prompts.template_parsing.eval_conditional(condition, variables)[source]

Evaluate a template condition as truthy if the named variable is non-empty.

Parameters:
  • condition (str)

  • variables (Mapping[str, str])

Return type:

bool

ralph.prompts.template_parsing.is_metadata_comment(line)[source]

Return True if the line is a {# … #} metadata comment.

Parameters:

line (str)

Return type:

bool

ralph.prompts.template_parsing.parse_metadata_line(line)[source]

Parse a {# … #} metadata comment line into (version, purpose) or None.

Parameters:

line (str)

Return type:

tuple[str | None, str | None] | None

ralph.prompts.template_parsing.parse_template(content)[source]

Parse a template into a list of AST nodes.

Parameters:

content (str)

Return type:

TemplateAST

ralph.prompts.template_parsing.parse_variable_spec(var_spec)[source]

Parse a variable spec string into (name, default) or None if invalid.

Parameters:

var_spec (str)

Return type:

tuple[str, str | None] | None

ralph.prompts.template_parsing.split_loop_items(values)[source]

Split a comma- or newline-separated string into a list of trimmed items.

Parameters:

values (str)

Return type:

list[str]

ralph.prompts.template_parsing.strip_comments(content)[source]

Remove {# … #} comment blocks from a template.

Parameters:

content (str)

Return type:

str

ralph.prompts.template_registry

Simple registry for prompt templates.

exception ralph.prompts.template_registry.TemplateNotFoundError(template_name)[source]

Bases: Exception

Raised when a requested template is missing.

Parameters:

template_name (str)

Return type:

None

class ralph.prompts.template_registry.TemplateRegistry(*, template_dirs=(), _read_text=None)[source]

Bases: object

Registry that holds prompt templates by name.

Parameters:
  • template_dirs (tuple[Path, ...])

  • _read_text (Callable[[Path], str] | None)

get_template(name)[source]

Return the template associated with name or raise if missing.

Parameters:

name (str)

Return type:

str

register_template(name, content)[source]

Register or replace a prompt template.

Parameters:
  • name (str)

  • content (str)

Return type:

None

ralph.prompts.template_registry.default_template_dirs(workspace_root)[source]

Convention-over-configuration prompt template directories.

Parameters:

workspace_root (Path)

Return type:

tuple[Path, …]

ralph.prompts.template_registry.load_partial_templates(template_dirs)[source]

Load all Jinja/j2/txt templates from the given directories into a dict.

Parameters:

template_dirs (Iterable[Path])

Return type:

dict[str, str]

ralph.prompts.template_registry.packaged_template_root()[source]

Return the path to the bundled prompt templates directory.

Return type:

Path

ralph.prompts.template_variables

Template variable helpers ported from Ralph Workflow Rust.

ralph.prompts.types

Prompt-facing typed capability helpers.

This module is a thin facade over template_variables so prompt materialization and prompt tests share one capability/policy implementation instead of carrying a second parallel type system.

class ralph.prompts.types.Capability(*values)[source]

Bases: StrEnum

Internal Ralph capability vocabulary.

class ralph.prompts.types.SessionCapabilities(capabilities, policy_flags, tool_name_prefix='')[source]

Bases: object

Bundle of capability/policy sets plus transport-specific prompt decoration.

Parameters:
ralph.prompts.types.bool_to_template_value(value)[source]

Convert a boolean to the canonical template string representation.

Parameters:

value (bool)

Return type:

str

ralph.prompts.types.capability_template_variables(capabilities, policy_flags, *, tool_name_prefix='')[source]

Return template variable dict derived from the given capability and policy sets.

Parameters:
Return type:

dict[str, str]

ralph.prompts.types.format_capability_summary(capabilities, policy_flags)[source]

Render a multi-line summary of granted capabilities and active policy flags for prompts.

Parameters:
Return type:

str

ralph.prompts.types.format_mcp_tools_list(tool_names)[source]

Render a sequence of MCP tool names as a single comma-separated string for prompts.

Parameters:

tool_names (Sequence[str])

Return type:

str

ralph.prompts.types.visible_mcp_tool_names(capabilities)[source]

Return canonical MCP tool names a session with the given capabilities may call.

Parameters:

capabilities (CapabilitySet)

Return type:

list[str]

ralph.prompts.plan_format

Plan artifact formatting for human-readable execution context.

ralph.prompts.plan_format.format_plan_for_execution(content)[source]

Convert a plan artifact JSON string into a structured human-readable text block.

Parameters:

content (str)

Return type:

str

Testing

ralph.testing

Test helpers for Ralph Workflow.

This package exports fake subprocess and process-management helpers for unit tests, along with timeout management utilities for keeping the test suite within the 60-second wall-clock budget.

Main entry points:

  • FakeAsyncProcess, FakeControllableAsyncProcess, FakePopen, FakeStubbornPopen, FakeImmortalPopen, FakeTimeoutPopen — in-memory subprocess fakes for testing agent invocation without spawning real processes.

  • FakePsutil, FakePsutilProcess, make_psutil_factory — psutil stubs for testing process-liveness logic.

  • make_async_process_factory, make_sync_process_factory — factory helpers that inject fakes into callers under test.

  • run_command_with_timeout, timeout_seconds_from_env, build_timeout_env — subprocess execution with enforced wall-clock limits sourced from RALPH_TEST_TIMEOUT_SECONDS and RALPH_SUITE_TIMEOUT_SECONDS.

  • SuiteTimeoutError — raised when a test suite exceeds its timeout budget.

  • DEFAULT_TEST_TIMEOUT_SECONDS, DEFAULT_SUITE_TIMEOUT_SECONDS — default caps.

Import directly from this package rather than from sub-modules:

from ralph.testing import FakePopen, run_command_with_timeout

ralph.testing.fake_agent_executor

In-process fake executor for unit-testing parallel pipeline logic.

Provides FakeAgentExecutor and FakeRun. Seed a FakeAgentExecutor with a mapping of unit_id to FakeRun instances; the executor replays the seeded output lines and exit code, emitting the correct WorkerStatus transitions, without spawning any subprocess or real agent process.

class ralph.testing.fake_agent_executor.FakeAgentExecutor(runs)[source]

Bases: object

In-process agent executor that replays seeded FakeRun scripts without subprocesses.

Parameters:

runs (dict[str, FakeRun])

class ralph.testing.fake_agent_executor.FakeRun(outputs, exit_code, duration_ms, raise_on_start=None, side_effect=None)[source]

Bases: object

Seeded replay script for a single parallel work unit.

Parameters:
  • outputs (list[str])

  • exit_code (int)

  • duration_ms (int)

  • raise_on_start (Exception | None)

  • side_effect (Callable[[], None] | None)

ralph.testing.pytest_timeout_plugin

Pytest plugin enforcing Ralph’s hard suite wall-clock timeout.

This plugin complements the per-test timeout in tests/conftest.py by starting a session-wide watchdog in the controller process. Unlike cooperative session timeout plugins, the watchdog terminates descendant worker processes and exits the pytest process once the suite deadline is exceeded.

ralph.testing.pytest_timeout_plugin.pytest_sessionfinish(session, exitstatus)[source]

Cancel the suite watchdog when pytest finishes normally.

Parameters:
  • session (pytest.Session)

  • exitstatus (int)

Return type:

None

ralph.testing.pytest_timeout_plugin.pytest_sessionstart(session)[source]

Start the controller-only watchdog that enforces the suite wall-clock cap.

Parameters:

session (pytest.Session)

Return type:

None

ralph.testing.audit_artifact_submission_canonical_path

Artifact-submission canonical-path audit.

Enforces the single-writer contract for run-scoped completion receipts, completion sentinels, and canonical artifact files. Any code outside the allowlisted canonical sites that writes one of these files is a bypass and fails make verify.

Scans ralph/ (skipping the audit module itself, the canonical submit module, the marked executor block in tools/artifact.py, the type-specific artifact layout modules, and tests/). Uses AST analysis to find:

  • Direct writes to .agent/receipts/, .agent/completion_seen_*.json, .agent/artifacts/<canonical-type>.json, or .agent/tmp/<canonical-type>.json (via write_text, write_bytes, open(...), or equivalent file-copy helpers).

  • Calls to the lower-level store.submit_artifact outside allowlisted sites.

  • Calls to write_artifact_receipt / delete_artifact_receipt outside allowlisted sites.

Usage:

python -m ralph.testing.audit_artifact_submission_canonical_path [codebase_root]

Exit 0 = clean, 1 = bypass found, 2 = root not found.

class ralph.testing.audit_artifact_submission_canonical_path.BypassFinding(file_path, line, category, detail)[source]

Bases: object

A single canonical-path bypass finding.

Parameters:
  • file_path (str)

  • line (int)

  • category (str)

  • detail (str)

ralph.testing.audit_artifact_submission_canonical_path.audit(codebase_root=None)[source]

Audit the codebase for artifact-submission bypasses.

Parameters:

codebase_root (Path | None) – Root directory to scan. Defaults to the ralph-workflow package root (three directories above this module).

Returns:

A list of bypass findings; empty when clean.

Return type:

list[BypassFinding]

ralph.testing.audit_artifact_submission_canonical_path.audit_file(file_path, rel_path)[source]

Audit a single Python file for canonical-path bypasses.

Parameters:
  • file_path (Path)

  • rel_path (str)

Return type:

list[BypassFinding]

ralph.testing.audit_artifact_submission_canonical_path.main(argv=None)[source]

Run the canonical-path audit and return an exit code.

Parameters:

argv (list[str] | None)

Return type:

int

ralph.testing.audit_di_seam

Dependency-injection seam audit.

Enforces the Foundations dependency-injection contract from PROMPT.md: every component below the composition root must receive its collaborators through its constructor or call signature, and must not reach into ambient process state (os.environ, open()) or launder the session contract through typing.cast() at the session factory boundary.

This audit runs TWO AST passes (modeled on audit_mcp_timeout.py):

PASS 1 — env+open ambient reads

Walks ralph/mcp/, ralph/agents/, ralph/process/, ralph/recovery/, ralph/pipeline/, and ralph/git/ for direct ambient state reads that should be replaced with an injected accessor:

  • os.environ[...]

  • os.environ.get(...)

  • os.getenv(...)

  • open(...) (direct file I/O without an injected reader)

Allowlist (composition-root or unavoidable boundary code):
  • ralph/mcp/protocol/env.py — defines constants; no actual env read.

  • ralph/mcp/server/_timing_safety.py — imports constants; one-line justification.

  • ralph/mcp/server/runtime.py:120 and :162 — composition root that reads MCP_*_ENV / UPSTREAM_MCP_TOOL_CATALOG_ENV to wire the factory.

  • ralph/mcp/websearch/secrets.py:17os.getenv is used as a callable EnvGetter parameter, NOT an ambient read.

  • ralph/config/*, ralph/main.py, ralph/__main__.py — top-level entry points (composition root).

PASS 2 — cast() at the session factory boundary

Walks ralph/mcp/server/runtime_session.py and ralph/mcp/server/_fallback_http_handler.py (the modules named in PROMPT.md proof obligation B as the session factory boundary) and flags ANY cast(...) call. PROMPT.md proof obligation B says: “no cast() sits at the session factory boundary (the specific laundering that hid the storm), so the type checker cannot be told to look away there.” This is why PASS 2 has NO allowlist — the architecture’s stance is zero casts at the factory boundary.

Both passes are controlled by the AUDIT_DI_SEAM_DRY_RUN env var (default "true"). When true (the default), hits are REPORTED but the audit does not fail — this is the dry-run pattern, used so a fresh check surfaces hits without breaking the build. Set AUDIT_DI_SEAM_DRY_RUN=false to make any reported hit fail the audit. The composition-root env reads (ralph/mcp/server/runtime.py and similar) are also under the dry-run umbrella, so the audit can be turned into a hard gate once the allowlist and boundary code are confirmed correct.

Self-audit (per PA-009): this module uses only non-mutating operations — Path.rglob + read_text + ast.parse. It NEVER uses subprocess.run, time.sleep, or real file writes, so it passes audit_test_policy and audit_mcp_timeout on itself.

Usage:

python -m ralph.testing.audit_di_seam [root1 root2 …]

Exit codes:

0 = clean (in dry-run mode, always; in strict mode, only if no hits). 1 = violations found (strict mode only). 2 = root not found.

class ralph.testing.audit_di_seam.DiSeamViolation(file_path, line, category, detail)[source]

Bases: object

A single dependency-injection seam violation.

Parameters:
  • file_path (str)

  • line (int)

  • category (str)

  • detail (str)

ralph.testing.audit_di_seam.audit_pass1(package_root, roots=('mcp', 'agents', 'process', 'recovery', 'pipeline', 'git'))[source]

PASS 1 — direct env / open ambient reads.

Returns (violations, files_checked).

Parameters:
  • package_root (Path)

  • roots (tuple[str, ...])

Return type:

tuple[list[DiSeamViolation], int]

ralph.testing.audit_di_seam.audit_pass1_file(rel_path, file_path)[source]

Run PASS 1 (env+open) on a single Python file.

Parameters:
  • rel_path (str)

  • file_path (Path)

Return type:

list[DiSeamViolation]

ralph.testing.audit_di_seam.audit_pass2(package_root, modules=('mcp/server/runtime_session.py', 'mcp/server/_fallback_http_handler.py'))[source]

PASS 2 — cast() at the session factory boundary.

Returns (violations, modules_walked).

Parameters:
  • package_root (Path)

  • modules (tuple[str, ...])

Return type:

tuple[list[DiSeamViolation], int]

ralph.testing.audit_di_seam.audit_pass2_file(rel_path, file_path)[source]

Run PASS 2 (cast at session factory boundary) on a single Python file.

Parameters:
  • rel_path (str)

  • file_path (Path)

Return type:

list[DiSeamViolation]

ralph.testing.audit_di_seam.main(argv=None)[source]

Run both DI-seam audit passes and return an exit code.

Parameters:

argv (list[str] | None)

Return type:

int

ralph.testing.audit_parallelization_dormant

Audit that Ralph-managed fan-out is dormant and the agent-driven model is wired.

Enforces eight non-vacuous invariants across the planning prompt, the continuation template, the bundled plan format doc, the effect-router WARNING, the bundled pipeline.toml, the planning_analysis.jinja rubric, the user-facing configuration docs, and the advanced pipeline-configuration doc. Every check uses a real, current-state phrase so the audit cannot be vacuously satisfied by strings that never appear in the codebase.

Checks (the literals are the verified-real current strings):
  1. planning.jinja MUST contain ## Agent-Driven Parallel Execution

  2. planning.jinja MUST NOT contain ## Same-Workspace Parallel Worker Rules

  3. plan.md MUST contain agent-managed sub-agents AND fan-out is dormant

  4. effect_router.py MUST contain Ralph-managed fan-out is dormant in this build

  5. pipeline.toml MUST contain dispatch_mode = agent_subagents

  6. planning_analysis.jinja MUST contain ### 9. PARALLEL EXECUTION (AGENT-DRIVEN)

  7. developer_iteration_continuation.jinja MUST contain the new ## PARALLEL EXECUTION (when the plan declares heading AND MUST NOT contain the legacy fan-out (Ralph-managed) wording, so continuation runs cannot regress to Ralph-managed fan-out.

  8. configuration.md MUST contain subagent_capability (the [agents.*] default-resolution doc-pinned to prevent silent removal of the new H3 subsection that documents the bundled Claude sub-agent default)

  9. advanced-pipeline-configuration.md MUST contain dispatch_mode (the [phases.<name>.parallelization] H3 already covers it; this invariant pins the existing surface so it cannot drift away from the bundled default)

The existing ### 7. PARALLELIZATION SAFETY - MEDIUM heading in planning_analysis.jinja is part of the existing rubric and is NOT flagged here.

Usage:

python -m ralph.testing.audit_parallelization_dormant

Exit 0 = clean, 1 = at least one invariant violated.

class ralph.testing.audit_parallelization_dormant.Invariant(*, rel_path, present=(), absent=())[source]

Bases: object

One literal-string check the audit enforces.

Parameters:
  • rel_path (str)

  • present (tuple[str, ...])

  • absent (tuple[str, ...])

ralph.testing.audit_parallelization_dormant.main(argv=None)[source]

Run the parallelization-dormant audit and return the process exit code.

Iterates over the literal-string Invariant objects in _INVARIANTS, aggregates all violations across the planning prompt, the bundled plan format doc, the effect-router WARNING, the bundled pipeline.toml, and the planning_analysis.jinja rubric. Prints a one-line summary on success or a labeled, line-broken failure banner on violation. Has no side effects beyond stdout output and sys.exit semantics.

Parameters:

argv (list[str] | None) – Unused positional argument list (kept for CLI symmetry with other audit entry points). Values are ignored.

Returns:

0 when every invariant passes, 1 when at least one literal-string check fails.

Return type:

int

ralph.testing.audit_resource_lifecycle

Resource-lifecycle audit (AST-based).

Enforces the resource-lifecycle contract documented in ralph-workflow/docs/agents/memory-lifecycle.md:

  1. threading.Thread(...) / Thread(...) calls MUST have daemon=True — non-daemon threads can block process exit on the interpreter shutdown atexit join that concurrent.futures registers for its default executor.

  2. httpx.Client(...), httpx.AsyncClient(...), and requests.Session(...) constructions MUST be the context-manager expression of a with statement — bare assignment leaks the underlying HTTP connection pool and may not be closed at interpreter exit.

  3. os.open(...), os.openpty(...), and os.pipe(...) are allowed ONLY under ralph/process/ (the centralized process lifecycle layer). Outside that allowlist, raw fd creation is a leak: it bypasses the centralized fd ownership policy and is not tracked by the zombie reaper.

  4. Long-lived mutable accumulators (list, dict, set, deque) assigned to module-level names OR instance attributes (self.X) inside __init__ bodies MUST carry a FIFO/size cap (deque (maxlen=...), OrderedDict + count cap, or carry a # bounded-accumulator-ok: <reason> marker). A deque() / collections.deque() call WITHOUT maxlen= is treated as unbounded. Mutable collection LITERALS ([], {}, set()) assigned to a module-level name or self.X are flagged — they have no cap and grow monotonically across a long session.

The audit resolves import x as y / from x import y [as z] bindings so an aliased call cannot evade detection (import httpx as hx; hx.Client() and from httpx import Client; Client() are both caught). The same alias resolution applies to the accumulator contract (import collections as c; c.deque() is caught; from collections import deque; deque() is caught).

Escape hatch: an inline marker on the call’s line suppresses the violation. The single-string _ALLOW_MARKER has been generalized to a marker SET (_ALLOW_MARKERS) so the resource-lifecycle-ok (contracts 1-3) and bounded-accumulator-ok (contract 4) markers coexist and a future contract (contract 5+) can opt in without disrupting existing markers. Keep markers rare and justified.

Scope and exclusions (intentional, documented):

  • ThreadPoolExecutor is NOT covered by the daemon-Thread rule; it has its own .shutdown() lifecycle owned by the caller.

  • Bare open() is governed by audit_di_seam (composition-root env/open reads) and is OUT OF SCOPE here.

  • loop.run_in_executor(None, ...) in ralph/interrupt/asyncio_bridge.py is intentionally NOT covered — it is a bounded shutdown block owned by the asyncio bridge (different lifecycle), not a thread leak.

  • The accumulator contract covers module-level and self.X (in __init__ body) mutable literals + constructors WITHOUT maxlen. deque(maxlen=...) is clean by construction. Dataclass field defaults (field(default_factory=...)) and local-function variables are out of scope (higher false-positive rate; the BudgetState.failures leak class is closed by dropping the field + the tracemalloc test, not by this AST contract).

  • The audit is AST-based and can only flag literal-name calls. Deliberate-obfuscation indirection (getattr, importlib) is out of scope (would require dataflow tracking).

Usage:

python -m ralph.testing.audit_resource_lifecycle [root1 …]

Exit 0 = clean, 1 = violations, 2 = root not found.

class ralph.testing.audit_resource_lifecycle.ResourceLifecycleAuditor(file_path, source, rel_path, *, module_aliases=None, from_imports=None)[source]

Bases: NodeVisitor

AST visitor that detects resource-lifecycle contract violations.

Parameters:
  • file_path (str)

  • source (str)

  • rel_path (str)

  • module_aliases (dict[str, str] | None)

  • from_imports (dict[str, str] | None)

class ralph.testing.audit_resource_lifecycle.ResourceLifecycleViolation(file_path, line, category, detail)[source]

Bases: object

A single resource-lifecycle contract violation.

Parameters:
  • file_path (str)

  • line (int)

  • category (str)

  • detail (str)

ralph.testing.audit_resource_lifecycle.audit_resource_lifecycle_directory(root)[source]

Audit every Python file under root.

Returns (violations, files_checked).

Parameters:

root (Path)

Return type:

tuple[list[ResourceLifecycleViolation], int]

ralph.testing.audit_resource_lifecycle.audit_resource_lifecycle_file(file_path)[source]

Audit a single Python file for resource-lifecycle violations.

The file_path is resolved against the ralph package root to compute a relative path; the relative path is used to decide whether the file is in the raw-fd allowlist (ralph/process/).

Parameters:

file_path (Path)

Return type:

list[ResourceLifecycleViolation]

ralph.testing.audit_resource_lifecycle.main(argv=None)[source]

Run the resource-lifecycle audit and return an exit code.

When argv (or sys.argv[1:]) is empty, audit the default production roots. When explicit roots are provided, audit EVERY one of them — a missing root short-circuits to exit 2 before any audit work, so a partial-pass output cannot hide a violating root.

Parameters:

argv (list[str] | None)

Return type:

int

ralph.testing.audit_watchdog_drift

Watchdog-drift contract audit.

The watchdog subsystem is the single centralized source of truth for in-stream and post-exit fire decisions. This audit locks the consolidation so a future refactor cannot silently re-introduce drift. Four invariants are enforced:

  1. No legacy watchdog at the ralph-workflow root. The file whose basename matches the legacy sentinel (the dead 1389-line module that was removed during the wt-012 consolidation) MUST NOT exist at the ralph-workflow root. The legacy module has zero imports anywhere in the repo and was dead code at the time of the consolidation. The audit fails fast if the file reappears. The filename is constructed at import time from two private string fragments so the literal forbidden token never appears as a contiguous substring in this source file.

  2. Single canonical owner of ``IdleWatchdog`` class. A top-level class definition named IdleWatchdog is allowed ONLY at ralph/agents/idle_watchdog/idle_watchdog.py. Any other production file under ralph/ that defines a top-level class IdleWatchdog raises the duplicate_idle_watchdog violation. The match is exact-name, not substring — class IdleWatchdogSubclass is NOT flagged.

  3. Single canonical owner of ``PostExitWatchdog`` class. A top-level class definition named PostExitWatchdog is allowed ONLY at ralph/agents/idle_watchdog/_post_exit_watchdog.py. Any other production file under ralph/ that defines a top-level class PostExitWatchdog raises the duplicate_post_exit_watchdog violation.

  4. ``WatchdogFireReason`` construction only in canonical owners. The two canonical owner modules (idle_watchdog.py and _post_exit_watchdog.py) are the ONLY files in the production tree that may construct WatchdogFireReason values via WatchdogFireReason(...) or WatchdogFireReason.<NAME> attribute access. Any other production file that does so raises fire_reason_outside_canonical_owner.

This module uses ONLY the ast module and Path.read_text — no real subprocess, no time.sleep, no real file I/O outside reading source files. It is therefore clean under audit_test_policy and audit_mcp_timeout.

Usage:

python -m ralph.testing.audit_watchdog_drift [package_root]
Exit codes:

0 = clean 1 = violations found 2 = root not found

class ralph.testing.audit_watchdog_drift.WatchdogDriftViolation(kind, file_path, line, message)[source]

Bases: object

A single watchdog-drift audit violation.

Parameters:
  • kind (str)

  • file_path (str)

  • line (int)

  • message (str)

ralph.testing.audit_watchdog_drift.audit_watchdog_drift(package_root, repo_root=None)[source]

Walk the production source tree and return all violations.

Parameters:
  • package_root (Path) – The ralph-workflow/ralph/ directory containing the production source tree.

  • repo_root (Path | None) – The ralph-workflow/ directory. Used to check for the legacy root watchdog file. When omitted, defaults to package_root.parent.

Returns:

A list of WatchdogDriftViolation records. Empty list means the tree is clean.

Return type:

list[WatchdogDriftViolation]

ralph.testing.audit_watchdog_drift.main(argv=None)[source]

CLI entry point. Returns 0 when clean, 1 on violations, 2 on bad root.

Parameters:

argv (Sequence[str] | None)

Return type:

int

ralph.telemetry

Anonymous telemetry for Ralph Workflow — error reporting and performance monitoring.

No personally identifiable information is collected. See _user_identity.py and _sentry.py.

Integrations

This group is the engine-side half of the Ralph-Workflow-Pro contract. It exposes the package and its public API for sphinx cross-references, the workspace and prompt hooks, the heartbeat / watcher / marker helpers, and the state-query surface used by an attached Pro Support session. The detailed contract lives at Pro support (engine-side); this entry keeps the module surface discoverable so contributors can land changes against the Pro Support boundary without reading the full contract page.

ralph.pro_support

Pro support — engine-side integration with Ralph-Workflow-Pro.

Ralph-Workflow-Pro launches the engine as a subprocess and expects the engine to honor a small, Pro-owned contract. This package implements the engine’s half of that contract:

  • env — pure helpers that read the three env vars Pro is allowed to set on the engine (RALPH_WORKFLOW_PRO, RALPH_WORKSPACE, PROMPT_PATH). The contract limits Pro to exactly these three engine-facing env vars, so the engine MUST NOT require any additional variables.

  • workspace — resolves the workspace root, preferring RALPH_WORKSPACE over the current working directory.

  • prompt — resolves the operator-visible source prompt path, preferring PROMPT_PATH over <workspace>/PROMPT.md. Callers operating on the materialised CURRENT_PROMPT.md MUST NOT use this resolver.

  • marker — read-only reader for the Pro-owned <workspace>/.ralph/run.json marker file and an optional .ralph/heartbeat_token sidecar.

  • heartbeat — bounded /api/heartbeat client. The client runs in a daemon thread, uses bounded httpx timeouts on every call, and is idempotent on stop() without ever joining the worker thread.

Engine invariants preserved by this package:

  • The engine never writes to the marker file, the heartbeat sidecar, or any path under <workspace>/.ralph/.

  • The engine never modifies the operator-visible PROMPT.md during a Pro-mode run.

  • The engine returns exit code 0 on clean completion and non-zero on failure regardless of whether it is running under Pro.

  • All stdout/stderr output remains valid UTF-8 newline-terminated text.

The pro_support package is a thin, read-only, non-blocking layer. It does not introduce global mutable state, does not register a singleton, and does not perform I/O at import time.

ralph.pro_support.env

Pro contract env var readers.

The Pro↔Ralph contract (see Ralph-Workflow-Pro/docs/product-spec/CONTRACT_RALPH_INTEGRATION.md §3) limits Ralph-Workflow-Pro to setting exactly three engine-facing environment variables on the subprocess:

  • RALPH_WORKFLOW_PRO — non-empty truthy marker that the engine uses to detect “we are a Pro subprocess.”

  • RALPH_WORKSPACE — absolute or relative path to the workspace root. When set, the engine must prefer this over the current working directory when resolving the workspace scope.

  • PROMPT_PATH — absolute or relative path to the operator-visible source prompt file. When set, the engine must prefer this over <workspace>/PROMPT.md.

The contract explicitly states the engine MUST NOT require any additional variables. Run identifiers, heartbeat tokens, ports, and other Pro-owned metadata are delivered through a Pro-owned marker file at <workspace>/.ralph/run.json (see ralph.pro_support.marker).

Each helper in this module is a pure function. None of them perform I/O. Each accepts an optional env mapping so tests can inject values without monkeypatching os.environ.

ralph.pro_support.env.get_prompt_path(env=None)[source]

Return the raw PROMPT_PATH value, or None if unset/empty.

This is a thin accessor; the canonical Path-aware resolver is ralph.pro_support.prompt.resolve_effective_prompt_path().

Parameters:

env (Mapping[str, str] | None) – Optional env mapping. When None (default), os.environ is read at call time.

Return type:

str | None

ralph.pro_support.env.get_ralph_workspace(env=None)[source]

Return the raw RALPH_WORKSPACE value, or None if unset/empty.

This is a thin accessor; the canonical Path-aware resolver is ralph.pro_support.workspace.resolve_pro_workspace().

Parameters:

env (Mapping[str, str] | None) – Optional env mapping. When None (default), os.environ is read at call time.

Return type:

str | None

ralph.pro_support.env.is_pro_mode(env=None)[source]

Return True when RALPH_WORKFLOW_PRO is set to a truthy value.

“Truthy” here is a non-empty string. The contract does not specify a particular value; any non-empty value indicates Pro-mode.

Parameters:

env (Mapping[str, str] | None) – Optional env mapping. When None (default), os.environ is read at call time.

Return type:

bool

ralph.pro_support.workspace

Workspace-root resolution for Pro mode.

Pro sets the engine’s workspace root via the RALPH_WORKSPACE env var. When that variable is unset or empty, the engine falls back to pathlib.Path.cwd(), matching the existing single-checkout behaviour. The result is always resolved through Path.resolve() so the value is a canonical, absolute path regardless of the input form (relative, contains .., symlink, etc.).

ralph.pro_support.workspace.resolve_pro_workspace(env=None, fallback=None)[source]

Return the Pro-mode workspace root, falling back when RALPH_WORKSPACE is unset.

Parameters:
  • env (Mapping[str, str] | None) – Optional env mapping. When None (default), os.environ is read at call time.

  • fallback (Path | str | None) – Path to use when RALPH_WORKSPACE is unset or empty. Defaults to Path.cwd(). The fallback is also resolved via Path.resolve() so symlinks are normalised.

Returns:

Canonical, absolute pathlib.Path for the workspace.

Return type:

Path

ralph.pro_support.workspace.resolve_pro_workspace_from_environ(fallback=None)[source]

Convenience wrapper that always reads os.environ.

Equivalent to resolve_pro_workspace(env=os.environ, fallback=...). Kept separate from the pure resolver so call sites that genuinely want a fresh os.environ read can document that intent without importing the os module at every site.

Parameters:

fallback (Path | str | None)

Return type:

Path

ralph.pro_support.prompt

Operator-visible source-prompt path resolution for Pro mode.

Pro sets the engine’s source-prompt file via the PROMPT_PATH env var. When that variable is unset or empty, the engine falls back to <workspace>/PROMPT.md. This module is the single source of truth for the operator-visible source prompt path; every call site in the engine that reads PROMPT.md (rather than the materialised .agent/CURRENT_PROMPT.md) must go through resolve_effective_prompt_path().

Callers operating on the engine-owned materialised .agent/CURRENT_PROMPT.md MUST NOT use this resolver — that path is engine-owned and is never overridden by PROMPT_PATH.

ralph.pro_support.prompt.resolve_effective_prompt_path(workspace_root, env=None)[source]

Return the effective source-prompt path, honouring PROMPT_PATH.

Resolution order:

  1. If PROMPT_PATH is set and non-empty in the supplied env:

    • when absolute, return it resolved through Path.resolve();

    • when relative, resolve it relative to workspace_root and return the result through Path.resolve().

  2. Otherwise return <workspace_root>/PROMPT.md resolved through Path.resolve().

Parameters:
  • workspace_root (Path | str) – Workspace root directory. The returned path is always relative to this root when PROMPT_PATH is unset.

  • env (Mapping[str, str] | None) – Optional env mapping. When None (default), os.environ is read at call time.

Return type:

Path

ralph.pro_support.marker

Read-only Pro marker file helpers.

The Pro↔Ralph contract reserves <workspace>/.ralph/run.json as a Pro-owned file the engine MUST treat as read-only. The engine never writes to it, never creates it, and never modifies it; it only reads the file when it needs to learn the run id, the heartbeat port, or the heartbeat token.

This module is the single place the engine reads the marker. Drift detection (see make verify-drift) prevents any other module from referencing .ralph/run.json directly.

Marker schema (intentionally minimal — see contract §5):

  • runId (string, required) — the run identifier the engine must include in /api/heartbeat posts.

  • port (int, optional) — the local port Pro is listening on for /api/heartbeat. Defaults to 7432 when absent.

  • heartbeatToken (string, optional) — the bearer token to include in the heartbeat header / body. When absent the engine falls back to a sidecar file at <workspace>/.ralph/heartbeat_token.

All public helpers return None on any error (missing, unreadable, invalid JSON, OS errors) rather than raising. This keeps Pro-mode soft-degrade behaviour intact: a missing or broken marker must not crash a non-Pro invocation that happens to share a workspace layout.

ralph.pro_support.marker.read_heartbeat_port(marker)[source]

Return the heartbeat port from the marker, or the default 7432.

The default is part of the Pro↔Ralph contract — Pro listens on a deterministic port unless the operator explicitly overrides it via the marker.

Parameters:

marker (dict[str, object] | None)

Return type:

int

ralph.pro_support.marker.read_heartbeat_token(workspace_root)[source]

Return the heartbeat token, or None when unavailable.

Resolution order:

  1. marker['heartbeatToken'] if present and non-empty.

  2. The sidecar file at <workspace>/.ralph/heartbeat_token (its stripped contents).

  3. None when both are absent or empty.

The function never raises; it returns None on any error so a missing token cannot break the rest of the engine.

Parameters:

workspace_root (Path | str)

Return type:

str | None

ralph.pro_support.marker.read_marker_file(workspace_root)[source]

Read and parse the Pro-owned marker file, or return None on any error.

The engine MUST NOT write to this file. This function only opens the file for reading; on any failure (missing, OSError, invalid JSON, wrong shape) it logs at debug and returns None.

Parameters:

workspace_root (Path | str) – Absolute or relative workspace root.

Return type:

dict[str, object] | None

ralph.pro_support.marker.read_run_id(marker)[source]

Return marker['runId'] when present and a non-empty string.

Parameters:

marker (dict[str, object] | None)

Return type:

str | None

ralph.pro_support.heartbeat

Bounded Pro heartbeat client.

The engine, when running as a Pro subprocess, POSTs a small JSON heartbeat to <base_url>/api/heartbeat every interval_seconds seconds so Pro can monitor liveness. The heartbeat client is a self-contained class that:

  • runs the heartbeat loop in a daemon thread so the process can always exit even if Pro is hung;

  • uses an explicit bounded ``timeout=`` on every httpx call so the bounded-subprocess audit (ralph.testing.audit_mcp_timeout) catches any regression;

  • treats 401 and 404 responses as hard stops — once the heartbeat is rejected as unauthorized or unknown, the client logs a warning and stops looping;

  • treats every other error (connection refused, timeout, 5xx) as transient — log at debug level and continue, so a Pro restart or brief outage does not crash the pipeline;

  • exposes an idempotent ``stop()`` that only sets a threading.Event; it does NOT join the worker because daemon threads cannot be meaningfully joined and the process must never block on a slow Pro server.

The client does not perform I/O at construction time. start() launches the daemon thread. stop() is safe to call multiple times and from any thread.

class ralph.pro_support.heartbeat.ProHeartbeatClient(run_id, token, base_url, pid, *, interval_seconds=5.0, timeout_seconds=5.0, httpx_client_factory=None, clock=None, metadata=None)[source]

Bases: object

Bounded, daemon-threaded heartbeat client for the Pro subprocess contract.

Constructor parameters are explicit (no module-level mutable state) so every test can construct a client with a fake clock and a fake httpx factory.

Parameters:
  • run_id (str)

  • token (str)

  • base_url (str)

  • pid (int)

  • interval_seconds (float)

  • timeout_seconds (float)

  • httpx_client_factory (_HttpxClientFactory | None)

  • clock (Callable[[], float] | None)

  • metadata (Mapping[str, object] | None)

start()[source]

Spawn the daemon worker thread. Idempotent: a second call is a no-op.

Return type:

None

stop()[source]

Signal the worker to exit on its next loop iteration. Idempotent.

Deliberately does NOT join(): the worker is daemonic and therefore will be torn down with the process; joining a daemon thread can block on a slow Pro server which would defeat the entire point of the design.

Return type:

None

ralph.pro_support.watcher

Late-marker adoption watcher for the Pro subprocess.

The Pro↔Ralph contract has historically assumed the marker file <workspace>/.ralph/run.json is present before the engine starts. In practice, a Pro launch may begin BEFORE the engine has started (e.g. the user invokes Pro, which then spawns the engine). To make the engine adopt the marker even when the engine started first, the engine polls for the marker in a daemon thread and starts the heartbeat the first time the marker appears.

Design constraints (enforced by make verify):

  • No real I/O in tests. The watcher accepts an injectable marker_loader and heartbeat_factory so tests can drive the loop with fakes.

  • No ``time.sleep`` in production. The default sleeper is self._stop_event.wait(timeout=...) so a stop() call from the main thread interrupts the wait immediately.

  • Daemon thread. The thread is marked daemon=True so the process can always exit even if stop() is missed.

  • Read-only against the marker. The default marker_loader only calls read_marker_file; it never writes.

  • TODO(contract-amendment): Once the upstream contract is amended with a late-marker adoption clause, drop this note.

class ralph.pro_support.watcher.ProMarkerWatcher(*, workspace_root=None, poll_interval_seconds=2.0, marker_loader=None, heartbeat_factory=None, clock=None, sleeper=None)[source]

Bases: object

Daemon-threaded watcher that polls for the Pro marker and starts the heartbeat.

The watcher polls the marker via an injectable marker_loader every poll_interval_seconds. On the first successful read of a complete payload (run_id, token, port), it invokes heartbeat_factory and exits the poll loop.

The thread is a daemon so the process can always exit even if stop() is missed. stop() is idempotent and does NOT join the thread (mirroring ProHeartbeatClient.stop).

Parameters:
  • workspace_root (Path | None)

  • poll_interval_seconds (float)

  • marker_loader (_MarkerLoader | None)

  • heartbeat_factory (_HeartbeatFactory | None)

  • clock (_Clock | None)

  • sleeper (_Sleeper | None)

start()[source]

Spawn the daemon worker thread. Idempotent.

Return type:

None

stop()[source]

Signal the worker to exit on its next iteration. Idempotent.

Does NOT join the thread; the thread is a daemon and will be torn down with the process.

If the worker has adopted a heartbeat client (see _run_loop), cascade stop() to the client so the heartbeat drain thread is reaped. The cascade is wrapped in suppress(Exception) because teardown must never raise — a refusing heartbeat client cannot block the cleanup path.

Return type:

None

ralph.pro_support.hooks

Pro DI seam: ProPipelineHooks frozen dataclass.

Pro can inject custom pipeline collaborators into the run loop via ProPipelineHooks. The dataclass bundles 13 fields:

  • 5 factory callables that, when supplied, REPLACE the corresponding runner helpers:

    • policy_bundle_factory: (WorkspaceScope, UnifiedConfig) -> PolicyBundle

    • registry_factory: (UnifiedConfig) -> AgentRegistry

    • state_factory: (UnifiedConfig, AgentsPolicy, PipelinePolicy, dict[str, int] | None) -> PipelineState

    • recovery_controller_factory: (PipelineState, PolicyBundle, UnifiedConfig) -> tuple[RecoveryController, int]

    • marker_watcher_factory: (Path) -> ProMarkerWatcher

  • 1 override: policy_bundle_override: PolicyBundle | None; when set, the line policy_bundle = factory(workspace_scope, config) is replaced with policy_bundle = pro_hooks.policy_bundle_override.

  • 1 passthrough: snapshot_registry: SnapshotRegistry | None; when set, the inner loop publishes a PipelineStateSnapshot to this registry on each reduce step.

  • 6 collaborator overrides that are applied to PipelineDeps by build_default_pipeline_deps:

    • display_context: overrides the display context.

    • model_identity: overrides the multimodal model identity.

    • system_prompt_materializer: overrides the system-prompt materializer.

    • phase_prompt_materializer: overrides the phase-prompt materializer.

    • artifact_requirements_resolver: overrides the artifact- requirements resolver.

    • recovery_sleep: overrides the wall-clock sleep used during recovery backoff.

Invariant: every field is keyword-only with a default of None; the dataclass is frozen=True, slots=True so it cannot be mutated after construction.

class ralph.pro_support.hooks.ProPipelineHooks(policy_bundle_factory=None, registry_factory=None, state_factory=None, recovery_controller_factory=None, marker_watcher_factory=None, policy_bundle_override=None, snapshot_registry=None, display_context=None, model_identity=None, system_prompt_materializer=None, phase_prompt_materializer=None, artifact_requirements_resolver=None, recovery_sleep=None)[source]

Bases: object

DI seam: lets Pro inject custom pipeline collaborators into run().

All fields default to None. When a factory is None, run() uses the production helper (the existing behaviour). When policy_bundle_override is not None, policy_bundle_factory is short-circuited.

The five collaborator overrides on the modular surface (display_context, model_identity, system_prompt_materializer, phase_prompt_materializer, artifact_requirements_resolver) are the only fields a Pro plumbing consumer must know about when targeting ralph.pipeline.factory.PipelineCore; they are applied via apply_pro_hooks_to_core(). The extended surface (PipelineDeps) additionally consumes recovery_sleep and the main-pipeline factories; those are handled by ralph.pipeline.factory.apply_pro_hooks_to_deps().

Parameters:
to_runner_kwargs()[source]

Return the 6 kwargs to forward to run().

The 6 collaborator overrides (including recovery_sleep) are intentionally NOT included here because they are not run() kwargs; they are fields that build_default_pipeline_deps inspects separately when composing PipelineDeps. policy_bundle_override is also intentionally excluded because it is not a run() kwarg; it is a field that run() inspects separately to short-circuit policy_bundle_factory.

Return type:

dict[str, object]

ralph.pro_support.hooks.apply_pro_hooks_to_core(core, pro_hooks)[source]

Return a new PipelineCore with Pro collaborator overrides applied.

Only the five PROMPT-mandated collaborators are propagated: display_context, model_identity, system_prompt_materializer, phase_prompt_materializer, and artifact_requirements_resolver. Extended fields such as policy_bundle_override, registry_factory, state_factory, recovery_controller_factory, marker_watcher_factory, snapshot_registry, and recovery_sleep are ignored because they belong to the main-pipeline PipelineDeps surface.

Parameters:
Return type:

PipelineCore

ralph.pro_support.state_query

Read-only pipeline state observability for Pro.

Pro can monitor the engine’s progress by reading a structured snapshot of the live pipeline state on every reduce step. The snapshot is a frozen, read-only view: the live PipelineState MUST remain mutable for the engine, and a Pro consumer of the snapshot MUST NOT be able to mutate engine state through the snapshot.

Design constraints (enforced by make verify):

  • Frozen dataclass with primitive copies. Snapshot fields are str, int, bool, or shallow-copied dict fields; the live PipelineState is never referenced from the snapshot.

  • Plain ``dict`` for nested mapping fields. metrics is a pydantic RunMetrics.model_dump() (plain dict), and outer_progress / loop_iterations / budget_caps are shallow dict copies.

  • No ``time.sleep`` in production. The publish is a constant time operation.

The publish happens inside _run_inner_loop (after state = step_result) so the snapshot is always taken AFTER the runner has updated the state but BEFORE the next iteration of the loop. This matches the contract: Pro can poll the registry’s get_latest() at any time and see the most recent state.

class ralph.pro_support.state_query.PipelineStateSnapshot(phase, previous_phase, run_id, interrupted_by_user, last_error, metrics, budget_caps, outer_progress, loop_iterations, iteration, analysis_iteration)[source]

Bases: object

Frozen, read-only view of the live pipeline state.

All mapping fields are shallow copies of the corresponding state fields; the snapshot holds no reference to the live PipelineState. The live state remains mutable for the engine.

Parameters:
  • phase (str)

  • previous_phase (str | None)

  • run_id (str | None)

  • interrupted_by_user (bool)

  • last_error (str | None)

  • metrics (dict[str, int])

  • budget_caps (dict[str, int])

  • outer_progress (dict[str, int])

  • loop_iterations (dict[str, int])

  • iteration (int)

  • analysis_iteration (int)

class ralph.pro_support.state_query.SnapshotRegistry(latest=None)[source]

Bases: object

Mutable holder for the most-recent PipelineStateSnapshot.

The pipeline publishes to this registry on each reduce step. Pro consumers call get_latest() to read the current state.

Parameters:

latest (PipelineStateSnapshot | None)

get_latest()[source]

Return the most-recent snapshot, or None if none has been published.

Return type:

PipelineStateSnapshot | None

publish(snapshot)[source]

Store the most-recent snapshot. Idempotent: replaces prior value.

Stores a field-by-field copy of the supplied snapshot so that get_latest() returns an equal but NOT identical instance. This is a defensive copy: the publish call site is trusted, but a future regression that mutated the stored snapshot would not silently corrupt the registry.

Parameters:

snapshot (PipelineStateSnapshot)

Return type:

None

ralph.pro_support.state_query.build_pipeline_state_snapshot(state, workspace_root)[source]

Build a read-only snapshot of the live PipelineState.

Parameters:
  • state (PipelineState) – The live, mutable PipelineState.

  • workspace_root (Path | str) – The workspace root used to resolve the run_id from the marker file. When the marker is missing, run_id is None.

Return type:

PipelineStateSnapshot