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.clifor the Typer CLI applicationralph.configfor configuration models and loadingralph.pipelinefor orchestration state and reducer/orchestrator logicralph.phasesfor phase dispatchralph.session_runtimefor host-owned mini-pipeline / standalone session runtime helpersralph.mcpfor the MCP bridge and standalone server helpersralph.gitfor GitPython-backed repository operationsralph.workspacefor 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:
objectStructured 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
- class ralph.logging.WorkerSinkHandle(sink_id, log_path)[source]¶
Bases:
objectHandle returned by
bind_worker_sinkto 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_idand returns its handle.- Parameters:
unit_id (str)
log_dir (Path)
run_id (str)
- Return type:
- ralph.logging.configure_logging(verbosity=1, *, log_directory=None, run_id=None, structured=False, rotation='10 MB', console_sink=None, sink_adder=None)[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.
console_sink (Callable[[str], None] | None) – Optional callable that replaces the terminal
logger.addsink. WhenNone(default) the library/worker fallbackmake_stderr_log_sinkis used, which strips terminal-control constructs before writing to the process error stream. Passmake_sanitizing_log_sink(ctx)from the CLI to route through the richDisplayContextConsoleinstead (so the rich Live status bar is the single painter of the terminal).sink_adder (SinkAdder | None) – Optional callable that replaces
loguru.logger.addfor file-sink wiring. Defaults tologger.add(production). Tests inject a recording fake to assert that both engine file sinks receivebuffering=8192without doing any real loguru file I/O. The console/stderr callable sink is wired throughlogger.adddirectly and is NOT routed through this seam (callable sinks are not file sinks and so are not subject to the buffering invariant).
- Returns:
Logging session with resolved paths and bound logger helpers.
- Return type:
- 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.syntax_theme¶
Fixed Pygments token palettes for Ralph’s background-aware syntax previews.
ralph.onboarding¶
Shared onboarding copy for CLI, validation, and docs-facing messaging.
- ralph.onboarding.fallback_next_steps()[source]¶
Return concise re-run 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.resolve_starter_template(label)[source]¶
Return the requested starter PROMPT.md template.
bug-fixis an alias for the guardrail template. The bare init path keeps the established general-purpose template.- Parameters:
label (str | None)
- Return type:
str
- ralph.onboarding.starter_prompt_template()[source]¶
Return the canonical starter PROMPT.md template.
- Return type:
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 withuv run ralph; it registers no global command, so it cannot collide with a stableralph.Stable build — a pinned release installed as an isolated global
ralphcommand viauv tool(install_stable_release).
See CONTRIBUTING.md (§”Dev build vs stable build”) for the workflow.
- class ralph.install.LauncherWriter(*args, **kwargs)[source]¶
Bases:
ProtocolProtocol for writing the dev launcher script to disk.
- class ralph.install.RunCommand(*args, **kwargs)[source]¶
Bases:
ProtocolProtocol 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 thedevextra, then writes anrdevlauncher intolauncher_dirso the dev build has a stable command name.rdevruns the working tree withuv runand never shadows the stableralphinstalled viainstall_stable_release().- Parameters:
run (RunCommand)
uv_executable (str | None)
cwd (Path)
launcher_dir (Path)
write_launcher (LauncherWriter)
- 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
ralphcommand.Uses
uv tool installso the stable build lives in its own environment and stays independent of the working tree.Without
versionthe latest published release is installed, and an already-installed olderralphis upgraded:--forcere-runs the install even when the tool is already present (a plainuv tool installis a no-op in that case), and--upgrade(which implies--refresh) re-resolves against PyPI so a newer release is picked up. Passversionto 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
rdevlauncher forpackage_dir.The launcher runs the dev build via
uv runagainst the checkout while staying in the caller’s working directory (the workspaceralphoperates on); only the project environment is sourced frompackage_dir.- Parameters:
package_dir (Path)
- Return type:
str
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 aPlatformInfo.current_platform()— cached singletonPlatformInfofor 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:
TypedDictKeyword arguments accepted by detect_platform for dependency injection.
- ralph.platform.detection.current_platform()[source]¶
Return the detected platform for the current runtime.
- Return type:
- ralph.platform.detection.detect_architecture(machine_name=None)[source]¶
Normalize
platform.machine()into a stable architecture enum.- Parameters:
machine_name (str | None)
- Return type:
- 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:
- 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:
- 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:
ralph.platform.models¶
Data models for platform detection and platform-specific behavior.
- class ralph.platform.models.Architecture(*values)[source]¶
Bases:
StrEnumNormalized CPU architecture names.
- class ralph.platform.models.EnvironmentInfo(ci=False, container=False, wsl=False, codespaces=False, ssh=False)[source]¶
Bases:
objectDetected runtime environment traits.
- Parameters:
ci (bool)
container (bool)
wsl (bool)
codespaces (bool)
ssh (bool)
- class ralph.platform.models.OperatingSystem(*values)[source]¶
Bases:
StrEnumNormalized operating system names.
- class ralph.platform.models.PlatformInfo(os=OperatingSystem.UNKNOWN, architecture=Architecture.UNKNOWN, environment=<factory>, package_manager=None)[source]¶
Bases:
objectComplete platform profile used by Ralph’s Python implementation.
- Parameters:
os (OperatingSystem)
architecture (Architecture)
environment (EnvironmentInfo)
package_manager (str | None)
- 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.
ralph.platform.architecture¶
Normalized CPU architecture names.
ralph.platform.environment_info¶
Detected runtime environment traits.
ralph.platform.operating_system¶
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:
objectOutcome 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:
- 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:
- ralph.contrib.cla.evaluate_current_environment()[source]¶
Evaluate the current CI environment as GitHub Actions or Woodpecker.
- Return type:
ralph.update_check¶
Update nagger: tell the user when a newer ralph-workflow release exists.
This package informs; it never mutates. Public surface:
maybe_render_update_nag()— one-line nag at run start, cache-driven with a throttled background refresh so it never delays a run.update_status()— synchronous summary forralph --diagnose.
Every dependency is injected via UpdateCheckDeps so the whole flow is
unit-testable without threads, network, filesystem, or a real clock.
- class ralph.update_check.InstallInfo(kind, upgrade_command)[source]¶
Bases:
objectDetected install method and the command to upgrade it.
- Parameters:
kind (InstallKind)
upgrade_command (str)
- class ralph.update_check.InstallKind(*values)[source]¶
Bases:
StrEnumHow the running
ralphwas installed.
- class ralph.update_check.UpdateCheckDeps(current_version, environ, now, is_disabled, cache_path, load_state, save_state, is_refresh_due, is_newer, detect_install, fetch_latest, spawn)[source]¶
Bases:
objectInjected collaborators for the update-check flow (real impls in
default_deps()).- Parameters:
current_version (str)
environ (Mapping[str, str])
now (Callable[[], float])
is_disabled (Callable[[Mapping[str, str]], bool])
cache_path (Callable[[Mapping[str, str]], Path])
load_state (Callable[[Path], VersionCheckState | None])
save_state (Callable[[Path, VersionCheckState], None])
is_refresh_due (Callable[[VersionCheckState | None, float], bool])
is_newer (Callable[[str, str], bool])
detect_install (Callable[[], InstallInfo])
fetch_latest (Callable[[], str | None])
spawn (Callable[[Callable[[], None]], None])
- class ralph.update_check.UpdateStatus(current_version, latest_version, update_available, install, disabled)[source]¶
Bases:
objectDiagnose-friendly snapshot of the update situation.
- Parameters:
current_version (str)
latest_version (str | None)
update_available (bool)
install (InstallInfo)
disabled (bool)
- ralph.update_check.default_deps()[source]¶
Wire the production implementations of every collaborator.
- Return type:
- ralph.update_check.maybe_render_update_nag(display_context, *, deps=None)[source]¶
Show a one-line upgrade nag when the cache reports a newer release.
Reads only from cache for display (so run start is never blocked); when the cache is stale, a background refresh is scheduled to update it for next time.
- Parameters:
display_context (DisplayContext)
deps (UpdateCheckDeps | None)
- Return type:
None
- ralph.update_check.update_status(*, allow_network=True, deps=None)[source]¶
Return the current update situation for
ralph --diagnose.Reads the cached result; when
allow_networkis True (an interactive diagnose) and the cache is stale, it does a single synchronous, time-boxed PyPI refresh. Callers passallow_network=Falsefor non-interactive contexts (pipes, CI, tests) so the status is computed from cache alone.- Parameters:
allow_network (bool)
deps (UpdateCheckDeps | None)
- Return type:
ralph.update_check.compare¶
PEP 440 version comparison for the update nagger.
Kept deliberately tiny and total: any unparseable input yields False so a
malformed local or remote version can never crash a run or trigger a false nag.
- ralph.update_check.compare.is_newer(current, latest)[source]¶
Return True when
latestis a strictly newer release thancurrent.Comparison follows PEP 440 ordering (so a pre-release is not treated as newer than the matching final). Any parse failure returns
False.- Parameters:
current (str)
latest (str)
- Return type:
bool
ralph.update_check.environment¶
Detect how Ralph Workflow was installed and how to upgrade it.
All inputs are injected (the package file location, the environment mapping, a
path_exists probe, and the frozen flag) so detection is a pure function that
each install branch can be unit-tested without touching the real filesystem.
- class ralph.update_check.environment.InstallInfo(kind, upgrade_command)[source]¶
Bases:
objectDetected install method and the command to upgrade it.
- Parameters:
kind (InstallKind)
upgrade_command (str)
- ralph.update_check.environment.detect_install(*, package_file, environ, is_frozen, path_exists)[source]¶
Classify the install and return the matching upgrade command.
A frozen bundle and an undeterminable location are handled here; every other case is delegated to
_classify_located().- Parameters:
package_file (str | None)
environ (Mapping[str, str])
is_frozen (bool)
path_exists (Callable[[Path], bool])
- Return type:
ralph.update_check.gating¶
Opt-out gating for the update nagger.
A version check is an outbound call, so it honors the existing telemetry opt-out
(RALPH_DISABLE_TELEMETRY / telemetry_enabled = false). A dedicated
RALPH_DISABLE_UPDATE_CHECK env var and update_check_enabled = false
config key let a user silence the nag without also disabling telemetry.
ralph.update_check.pypi¶
Best-effort PyPI lookup for the latest ralph-workflow release.
Every failure path is swallowed and returns None: a version check must never
delay or break a user’s run. The HTTP client is injectable so unit tests never
touch the network.
- ralph.update_check.pypi.fetch_latest_version(client, *, url='https://pypi.org/pypi/ralph-workflow/json')[source]¶
Fetch the latest published version via
client, orNoneon any error.- Parameters:
client (_HttpClient)
url (str)
- Return type:
str | None
ralph.update_check.state¶
Persisted state and throttling for the update nagger.
A tiny JSON cache under $XDG_CACHE_HOME/ralph-workflow/version-check.json
records when PyPI was last consulted and what it reported, so the network is hit
at most once per TTL window. A missing or corrupt cache is treated as empty
state and simply overwritten on the next successful refresh.
- class ralph.update_check.state.VersionCheckState(last_checked, latest_version)[source]¶
Bases:
objectLast-known result of a PyPI version check.
- Parameters:
last_checked (float)
latest_version (str | None)
- ralph.update_check.state.cache_path(environ)[source]¶
Resolve the cache file path, honoring
XDG_CACHE_HOME.- Parameters:
environ (Mapping[str, str])
- Return type:
Path
- ralph.update_check.state.is_refresh_due(state, now, *, ttl_seconds=86400)[source]¶
Return True when the cache is absent or older than
ttl_seconds.- Parameters:
state (VersionCheckState | None)
now (float)
ttl_seconds (int)
- Return type:
bool
- ralph.update_check.state.load_state(path)[source]¶
Load persisted state, returning
Nonefor missing or corrupt files.- Parameters:
path (Path)
- Return type:
VersionCheckState | None
- ralph.update_check.state.save_state(path, state)[source]¶
Persist
statetopath; filesystem errors are swallowed.- Parameters:
path (Path)
state (VersionCheckState)
- Return type:
None
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 undermake verify. This is NOT a per-suite limit.run_verify()tracks cumulative wall-clock time withtime.monotonic()across every step whose index is in_BUDGET_TRACKED_STEPSand 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 undertests/integration/. Enforced by SIGALRM intests/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_LABELSand their indices to_BUDGET_TRACKED_STEPS(the labels/steps sync invariant).Emptying
_KNOWN_TEST_STEP_LABELSto hide test steps from budget tracking, emptying_BUDGET_TRACKED_STEPSto disable tracking, or removing"make test"from_KNOWN_TEST_STEP_LABELS.Raising
_TOTAL_TEST_BUDGET_SECONDSor any of the per-step timeouts (an epsilon check pins the 60-second value to 60.0).
The broad @pytest.mark.subprocess_e2e profile is excluded from the
main make test suite. Required real-git auto-integration files are
also marked required_auto_integrate_e2e, however, and the combined
verification marker expression includes them inside make test.
Their wall-clock time is therefore charged through that budget-tracked
step; there is no separate auto-integration step in _VERIFY_STEPS.
“It also carries a subprocess marker” is not a budget exemption. 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.verifycommand-line tool.The handler is the
__main__-style entry point forpython -m ralph.verify. It refuses positionalargv(the verify runner is parameter-only), resolves the working directory to<repo-root>/ralph-workflowby default, and delegates torun_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 toPath(__file__).parent.parent(theralph-workflowpackage root).
- Returns:
run_verify’s return code (0on 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 ismin(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-workflowpackage root; tests inject a temporary path.runner (VerifyRunner) – Subprocess runner implementing the
VerifyRunnerprotocol. 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:
0when every step exits 0, or the first non-zero exit code returned by a failed step. When the cumulative budget is exhausted,TIMEOUT_EXIT_CODEis returned and a high-visibility failure banner is printed tostderr.- Return type:
int
- Side effects:
Spawns subprocesses via
runner; prints step stdout / stderr as it goes; emits acumulative_test_elapsedsummary 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
Noneto 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_weightsand the[general] agent_workspace_change_weights = {...}key inralph-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 verifybudget 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-calltimeout_msoverrides 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 raisetimeout_msto 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 outstorm). 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_gitwhen a caller does not opt into a custom value viaGitRunOptions.output_limit_bytes. A massivegit log/git diff/git statusagainst a huge vendor submodule would otherwise buffer the entire payload in memory. 10 MiB matches the existingSPILL_OUTPUT_LIMIT_BYTESprecedent atralph/mcp/tools/_exec_output_spill.py:33and is well above any realistic single-file diff. Callers that need to opt OUT can passoutput_limit_bytes=Noneto 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_gitwhen 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
Noneto 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.MCP_DISPATCH_TIMEOUT_SECONDS: float = 305.0¶
Caller deadline for saturated MCP dispatch.
EXEC_MAX_TIMEOUT_MSis the hard ceiling for legitimate tool calls; adding the kill grace prevents a healthy clamped call from racing the dispatcher while preserving room for dispatch, drain, and kill below the 330-second client request timeout.
- ralph.timeout_defaults.NO_PROGRESS_QUIET_HEARTBEAT_CEILING_SECONDS: float | None = 240.0¶
fires
NO_PROGRESS_QUIETwhen 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 bypassNO_PROGRESS_QUIET(which requiresalive_by is None) ANDSTRICTLY_STUCK(which requires a stale alive_by) and only trip the cumulative 600sCHILDREN_PERSIST_TOO_LONGceiling – too late for a heartbeat-only subagent that emits heartbeats but no real work. The default equalsNO_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_SECONDSwhen both are set. Must be > 0 when set. WhenNone, the heartbeat-only ceiling is disabled and the watchdog falls back to the cumulativeCHILDREN_PERSIST_TOO_LONGceiling.- 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
Noneto 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 firesWatchdogFireReason.STRICTLY_STUCKwhen the corroborator reports a stuck-but-alive state (alive_byin{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 isNone(disabled) so operators opt in by setting a concrete value; setting it shorter than the existingmax_waiting_on_child_no_progress_secondsis the recommended pattern (e.g. 300 s vs the 600 s outer ceiling). Set toNoneto 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/PRODUCT_CRITERIA.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 toNoneto 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
Noneto 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
Noneto 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_SECONDSforce-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_SUBAGENTwhen 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 thanAGENT_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_LONGwhen the cumulativeWAITING_ON_CHILDtime exceeds the standard cumulative ceiling (max_waiting_on_child_seconds, default 1800s) AND corroboration shows the child is alive-but-not-progressing (alive_byin the stale set). The PROMPT trace showed cumulative waiting time climbing to 2365s without the gate firing becauseclassify_stucknever 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:949while 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 perWATCHDOG_LOG_THROTTLE_SECONDS. The 30s default matchesWAITING_STATUS_INTERVAL_SECONDSso 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.
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.checked_accessors¶
Checked accessors for production boundary code.
These helpers exist so that production code can convert an object read
from deserialized input, a configuration file, subprocess output, a
network response, or any other untyped mapping into a typed Python value
WITHOUT using typing.cast.
The shape of the value is unknown to the type checker: the value is
object precisely because it comes from outside the type system’s
control. A cast would be an unverified claim about data the project
does not control. The helpers here replace that claim with a runtime
check that either returns a typed value or raises an exception (for
raising variants) or returns an explicit absent value (for the
absent-returning variants used at deliberately lenient boundaries such
as third-party agent-output parsers).
Two families:
Raising variants (
as_str,as_int,as_bool,as_path,as_str_dict,as_object_list,as_str_list,as_mapping,as_sequence) — used at trust boundaries that MUST validate the payload (e.g. user-supplied configuration, structured artifact payloads).Absent-returning variants (
optional_str,optional_int,optional_bool,optional_str_list) — used at deliberately lenient boundaries where a malformed third-party record MUST be skipped, not raised on (e.g. parsers for third-party agent output).
The helpers are pure and side-effect free. They carry no cast and
no type: ignore; the implementation narrows with isinstance
checks whose truth value the type checker can verify.
- ralph.checked_accessors.as_bool(value, *, field=None)[source]¶
Return
valueif it is abool, else raise.- Parameters:
value (object) – The raw value read from an untyped boundary.
field (str | None) – Optional field name used to produce a clearer error message.
- Returns:
The validated boolean.
- Raises:
TypeError – When
valueis not a boolean.- Return type:
bool
- ralph.checked_accessors.as_int(value, *, field=None)[source]¶
Return
valueif it is a non-boolint, else raise.- Parameters:
value (object) – The raw value read from an untyped boundary.
field (str | None) – Optional field name used to produce a clearer error message.
- Returns:
The validated integer.
- Raises:
TypeError – When
valueis not an integer (booleans are rejected because Python’sboolis a subclass ofint).- Return type:
int
- ralph.checked_accessors.as_mapping(value, *, field=None)[source]¶
Return
valueif it is aMappingofstrtoobject.- Parameters:
value (object) – The raw value read from an untyped boundary.
field (str | None) – Optional field name used to produce a clearer error message.
- Returns:
A read-only view of the mapping. Returns the value unchanged when it already satisfies the static type.
- Raises:
TypeError – When
valueis not a mapping of strings.- Return type:
Mapping[str, object]
- ralph.checked_accessors.as_object_list(value, *, field=None)[source]¶
Return
valueif it is alist.- Parameters:
value (object) – The raw value read from an untyped boundary.
field (str | None) – Optional field name used to produce a clearer error message.
- Returns:
A list view. The element type is left as
objectso callers can narrow further withisinstancechecks or additional accessors.- Raises:
TypeError – When
valueis not a list.- Return type:
list[object]
- ralph.checked_accessors.as_path(value, *, field=None)[source]¶
Return
valueif it is aPath, else raise.- Parameters:
value (object) – The raw value read from an untyped boundary.
field (str | None) – Optional field name used to produce a clearer error message.
- Returns:
The validated path.
- Raises:
TypeError – When
valueis not a path-like object.- Return type:
Path
- ralph.checked_accessors.as_sequence(value, *, field=None)[source]¶
Return
valueif it is a non-stringSequence.- Parameters:
value (object) – The raw value read from an untyped boundary.
field (str | None) – Optional field name used to produce a clearer error message.
- Returns:
A read-only view of the sequence.
- Raises:
TypeError – When
valueis not a non-string sequence.- Return type:
Sequence[object]
- ralph.checked_accessors.as_str(value, *, field=None)[source]¶
Return
valueif it is a non-emptystr, else raise.- Parameters:
value (object) – The raw value read from an untyped boundary.
field (str | None) – Optional field name used to produce a clearer error message.
- Returns:
The validated string.
- Raises:
TypeError – When
valueis not a string.ValueError – When
valueis an empty string.
- Return type:
str
- ralph.checked_accessors.as_str_dict(value, *, field=None)[source]¶
Return
valueif it is adictofstrtostr.- Parameters:
value (object) – The raw value read from an untyped boundary.
field (str | None) – Optional field name used to produce a clearer error message.
- Returns:
A new
dict[str, str]view (a shallow copy when the input is alreadydict[str, str]).- Raises:
TypeError – When
valueis not a dict, or when one of its values is not a string.- Return type:
dict[str, str]
- ralph.checked_accessors.as_str_list(value, *, field=None)[source]¶
Return
valueif it is alistof strings.- Parameters:
value (object) – The raw value read from an untyped boundary.
field (str | None) – Optional field name used to produce a clearer error message.
- Returns:
A new
list[str]view.- Raises:
TypeError – When
valueis not a list, or when one of its elements is not a string.- Return type:
list[str]
- ralph.checked_accessors.optional_bool(value, *, field=None)[source]¶
Return
valueif it is abool, elseNone.- Parameters:
value (object) – The raw value read from an untyped boundary.
field (str | None) – Optional field name used for debugging messages.
- Returns:
The validated boolean, or
Nonewhen the value is not a boolean.- Return type:
bool | None
- ralph.checked_accessors.optional_int(value, *, field=None)[source]¶
Return
valueif it is a non-boolint, elseNone.- Parameters:
value (object) – The raw value read from an untyped boundary.
field (str | None) – Optional field name used for debugging messages.
- Returns:
The validated integer, or
Nonewhen the value is not an integer.- Return type:
int | None
- ralph.checked_accessors.optional_str(value, *, field=None)[source]¶
Return
valueif it is a non-emptystr, elseNone.Designed for deliberately lenient boundaries: the parser must skip the malformed record and continue, not raise. An optional field is silently treated as
None.- Parameters:
value (object) – The raw value read from an untyped boundary.
field (str | None) – Optional field name used for debugging messages.
- Returns:
The validated string, or
Nonewhen the value is not a non-empty string.- Return type:
str | None
- ralph.checked_accessors.optional_str_list(value, *, field=None)[source]¶
Return
valueif it is alistof strings, elseNone.Designed for lenient boundaries: a malformed list is treated as a missing value rather than an error.
- Parameters:
value (object) – The raw value read from an untyped boundary.
field (str | None) – Optional field name used for debugging messages.
- Returns:
The validated string list, or
Nonewhenvalueis not a list of strings.- Return type:
list[str] | None
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:
format_validation_error_messages()— top-level entry point that returns a list of"location: message"strings for every error attached to aValidationError.format_validation_error_detail()— format a single pydanticErrorDetailsmapping into one"location: message"string.format_validation_location()— convert a pydanticloctuple to a dotted path string (e.g."summary.intent").format_validation_message()— pull the human-readablemsgout of an error detail, substituting a placeholder if the message is missing.suggest_canonical_field()— when a model declares an unknown key, suggest the closest existing field name (viadifflib.get_close_matches()).
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.ValidationErrorraised bymodel_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
locfield from a pydantic error detail. May be a tuple, a list, a string, orNone.- 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 isNoneso callers always get a non-empty string back.- Parameters:
raw_msg (object | None) – The
msgfield 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_forbiddenerror to point the agent at the field they probably meant. The suggestion is computed viadifflib.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
Noneif 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:
objectLogging 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:
objectResolved 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:
objectConfigured logger bundle for a single Ralph Workflow run.
- Parameters:
config (LoggingConfig)
paths (LoggingPaths)
logger (Logger)
ralph (RalphLogger)
ralph.logging_worker_sink¶
Per-worker log sink helpers for Ralph Workflow.
- class ralph.logging_worker_sink.WorkerSinkHandle(sink_id, log_path)[source]¶
Bases:
objectHandle returned by
bind_worker_sinkto 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_idand returns its handle.- Parameters:
unit_id (str)
log_dir (Path)
run_id (str)
- Return type:
- 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 master-prompt materialization.
Contract:
The runtime is created via
ManagedAgentSessionRuntime.open(a classmethod) which constructs anAgentSession, starts a session bridge, optionally materializes a master 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, master prompt materializer, MCP bridge shutdown) are injectable through
ManagedAgentSessionDepsso the runtime is black-box testable without real subprocesses or filesystem operations.ManagedAgentSessionRequestis a frozen dataclass that carries the caller-supplied inputs (session id prefix, drain, optional capabilities, optional pre-resolvedSessionMcpPlan, optional master-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 invokingclose()(or usingwith) 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_master_prompt=<function _materialize_master_prompt>, workspace_factory=<function _workspace_factory>, shutdown_bridge=<function _shutdown_bridge>)[source]¶
Bases:
objectInjectable dependency bundle for the managed session runtime.
ManagedAgentSessionDepsis the single seam that lets tests replace the default production collaborators used byralph.session_runtime.ManagedAgentSessionRuntimewith fakes or stubs. The production default wraps the canonical implementations inralph.mcp.session_plan,ralph.mcp.server.lifecycle,ralph.agents.invoke,ralph.prompts.master_prompt, andralph.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=Truethe 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_master_prompt (MaterializeMasterPromptFn)
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_master_prompt¶
Resolve the prompt inputs for a named master prompt and, when the named prompt is supplied, write the materialized master-prompt file the agent will consume. Side effects: reads prompt inputs from
workspace_root(and the engine-owned current-prompt mirror under the.agentdirectory), and may write the materialized master-prompt file underworkspace_rootat.agent/tmp/<name>_master_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 master-prompt file as a string.
- workspace_factory¶
Build a
ralph.workspace.protocol.Workspacerooted at the given path. Side effects: instantiates the workspace implementation; the production default returnsralph.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
Exceptionis 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, master_prompt_name=None, default_product_criteria=None)[source]¶
Bases:
objectCaller-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)
master_prompt_name (str | None)
default_product_criteria (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", …).drainflows intoralph.mcp.protocol.session.AgentSession.drain, governs which capabilities are exposed through the MCP bridge, and is used byralph.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
Nonethe runtime resolves capabilities from the configuredAgentsPolicyviaralph.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
SessionMcpPlanthat fully describes the session’s MCP capabilities, model identity, and server-side environment. When supplied,capabilitiesandserver_envare 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
- master_prompt_name¶
Optional name of a master-prompt template to materialize for the session. When
Nonethe agent is invoked without an explicit master prompt. The materializer writes the resolved file under the workspace and returns its path.- Type:
str | None
- default_product_criteria¶
Optional fallback path used when the chosen master-prompt template references a
currentplaceholder that has no other source. Has no effect whenmaster_prompt_nameisNone.- Type:
str | None
- Invariants:
The dataclass is frozen; mutating an instance raises
dataclasses.FrozenInstanceError.Every field is optional except
session_id_prefixanddrain; the runtime treats the others as overrides or precomputed hints.Fields are not used directly by the runtime after
ManagedAgentSessionRuntime.open()returns; the resolvedAgentSessioncarries the immutable view of the session.
Example
>>> request = ManagedAgentSessionRequest( ... session_id_prefix="plan", ... drain="planning", ... capabilities=frozenset({"read_repo", "list_artifacts"}), ... master_prompt_name="planning/default", ... default_product_criteria="your-prompt-file.md", ... )
- class ralph.session_runtime.ManagedAgentSessionRuntime(*, config, workspace_root, agent_config, request, bridge, agent_session, master_prompt_file, deps)[source]¶
Bases:
objectHost-owned context for running prompt-like mini workflows through Ralph.
A
ManagedAgentSessionRuntimeis 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 master-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 viaclose()).- Attributes (set in
open(), treated as read-only afterwards): - config: The fully-merged
UnifiedConfigdriving 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
AgentConfigidentifying the agent CLI to invoke.
- request: The
ManagedAgentSessionRequestthat named the session; retained verbatim for diagnostics and checkpoint rehydration.
- bridge: The
SessionBridgeLikestarted byopen(). Owns the MCP server endpoint the agent reaches as
MCP_ENDPOINT.- agent_session: The
AgentSessioncarrying the unique session id, run id, declared capabilities, and model identity.
- master_prompt_file: Resolved path to a master-prompt file when
the request named one;
Nonewhen the agent runs without an explicit master prompt.- deps: The
ManagedAgentSessionDepsdependency bundle in use (production defaults, or the test stub passed to
open()).
- config: The fully-merged
- 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 invokeclose()(or use the context-manager protocol) regardless of how their host loop exits.
- Parameters:
config (UnifiedConfig)
workspace_root (Path)
agent_config (AgentConfig)
request (ManagedAgentSessionRequest)
bridge (SessionBridgeLike)
agent_session (AgentSession)
master_prompt_file (str | None)
deps (ManagedAgentSessionDeps)
- 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_fileresolves the per-turnInvokeOptionsfor 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 throughralph.agents.invoke.invoke_agent()). The MCP bridge started byopen()is reachable as theMCP_ENDPOINTenv 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_rootby 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
Nonethe agent runtime generates one; the actual id is reported throughsession_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.RequiredArtifactdeclaration used by the agent runtime to gate completion; the turn fails fast if no matching artifact is produced. Most host loops leave thisNone.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, andAGENT_LABEL_SCOPE(these are owned by the runtime and always set byopen).
- 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 toconfig.general.max_same_agent_retriesattempts. Retry events are emitted throughloguru.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 invokeclose()(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, andAGENT_LABEL_SCOPEinto the subprocess environment so the agent can reach the MCP bridge owned by this runtime.May invoke
session_id_sinkand 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().openallocates the session id, starts the MCP bridge that the agent will talk to, and (optionally) materializes a master-prompt file. It is the only sanctioned way to build aManagedAgentSessionRuntime; 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.UnifiedConfigthat drives general settings (verbosity, retry limits, JSON parser). Reused for everyinvoke_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
depsto produce aWorkspacethat the MCP bridge will hand to tools.agent_config – Selected
ralph.config.models.AgentConfigthat 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
ManagedAgentSessionRequestthat names the session id prefix,drain, capabilities, system prompt, and any pre-resolved session plan.deps – Optional
ManagedAgentSessionDepsbundle overriding one or more collaborator boundaries (workspace factory, MCP server starter, agent invoker, master-prompt materializer, bridge shutdown). Pass a stubbed bundle in tests to avoid real subprocesses and filesystem access. WhenNonethe production defaults fromManagedAgentSessionDepsare used.agents_policy – Optional
ralph.policy.models.AgentsPolicyused to resolve MCP capabilities and access modes whenrequest.capabilitiesandrequest.session_mcp_planare bothNone. Falls back to the policy embedded inconfigwhen omitted.
- Returns:
A fully wired
ManagedAgentSessionRuntimewhose MCP bridge is already listening on a private endpoint. The caller owns the returned runtime and must invokeclose()(or use it as a context manager) so the bridge shuts down on exit.- Raises:
Exception – Any failure during bridge start or master-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:
config (UnifiedConfig)
workspace_root (Path)
agent_config (AgentConfig)
request (ManagedAgentSessionRequest)
deps (ManagedAgentSessionDeps | None)
agents_policy (AgentsPolicy | None)
- Return type:
- Side Effects:
Starts an MCP server subprocess (or in-memory bridge, if the
deps.start_mcp_serveroverride returns one) bound to a private endpoint.Captures a fresh
run_id(UUID4) that is exposed throughMCP_RUN_ID_ENVandAGENT_LABEL_SCOPE_ENVto the agent subprocess.May write a master-prompt file under
workspace_rootviadeps.materialize_master_prompt.
- Attributes (set in
ralph.rich_protocols¶
Protocol shims for lazily imported Rich classes.
- class ralph.rich_protocols.RichGroupProto(*args, **kwargs)[source]¶
Bases:
ProtocolProtocol for rich.Group 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. This module additionally owns one parent
deadline across all concurrent file shards. Splitting tests into shards
does NOT increase either 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, emit_welcome=True)¶
Create user-global configs, optionally leaving onboarding to the caller.
- Parameters:
display_context (DisplayContext)
emit_welcome (bool)
- 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, *, console_sink=None)¶
Configure logging based on verbosity level.
- Parameters:
verbosity (Verbosity) – CLI verbosity branch. Each branch maps to a loguru level / format pair.
console_sink (Callable[[str], None] | None) – Optional callable that replaces the raw terminal sink. When
None(default) the library/worker fallbackmake_stderr_log_sinkis used, which strips terminal-control constructs before writing to the process error stream. The CLI’smain()call site passesmake_sanitizing_log_sink(_cli_ctx)so the rich Live status bar is the single painter of the terminal.
- 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-configshort-circuit; accepts legacyconsole=and newdisplay_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
consolekwarg 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:
options (CommitPlumbingOptions)
display_context (DisplayContext)
- 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:
list_providers (bool)
display_context (DisplayContext)
- 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, long_run=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, redo_policy=False, run_policy_agents=False, policy_only=False, prompt_helper=False)[source]¶
Run the Ralph Workflow multi-agent pipeline or execute a sub-operation.
The handler is the
ralphconsole script entry point declared inpyproject.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.mdin 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-commitapplies the commit. Always dogfood this for the AGENTS.md commit rule rather than hand-rollinggit commit.--quick/-Q,--long/-L, and--thorough/-T— mutually exclusive depth presets that map to developer-iteration counts (1, 5, and 10 respectively).--developer-iters/-D,--reviewer-reviews/-R— explicit iteration caps (overridden by the depth presets).--resume/-rand--no-resume— checkpoint handling.--counter NAME=VALUE(repeatable) — override a policy-declared budget counter; the name must be declared inpipeline.tomlor the run is rejected.
Pipeline-invocation side effect: when none of the early-exit branches fire, the handler builds a
CLIOverridesbundle, callsbootstrap_global_configs+configure_logging, resolves the effective developer-iteration count, and dispatches torun_pipeline. The run writes.agent/checkpoint.jsonand 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 0x10ed44050>]) –
--prompt/-Pinline prompt text (must be used with--quick).config (Annotated[str | None, <typer.models.OptionInfo object at 0x10ed44190>]) –
--config/-cpath to an explicit configuration file.developer_iters (Annotated[int | None, <typer.models.OptionInfo object at 0x10ed442d0>]) –
--developer-iters/-Ddeveloper-agent iteration cap.quick (Annotated[bool, <typer.models.OptionInfo object at 0x10ed44410>]) –
--quick/-Qsingle-iteration preset.long_run (Annotated[bool, <typer.models.OptionInfo object at 0x10ed44550>]) –
--long/-Lfive-iteration preset.thorough (Annotated[bool, <typer.models.OptionInfo object at 0x10ed44690>]) –
--thorough/-Tten-iteration preset.counter (Annotated[list[str] | None, <typer.models.OptionInfo object at 0x10ed447d0>]) –
--counterrepeatableNAME=VALUEoverrides.developer_agent (Annotated[str | None, <typer.models.OptionInfo object at 0x10ed44910>]) –
--developer-agent/-aagent name.developer_model (Annotated[str | None, <typer.models.OptionInfo object at 0x10ed44a50>]) –
--developer-modelmodel flag.verbosity (Annotated[Verbosity, <typer.models.OptionInfo object at 0x10ed44b90>]) –
--verbosity/-voutput verbosity (quiet / normal / verbose / full / debug).quiet (Annotated[bool, <typer.models.OptionInfo object at 0x10ed44cd0>]) –
--quiet/-qsuppress non-error output.debug (Annotated[bool, <typer.models.OptionInfo object at 0x10ed44e10>]) –
--debugenable debug output.resume (Annotated[bool, <typer.models.OptionInfo object at 0x10ed44f50>]) –
--resume/-rresume from checkpoint.no_resume (Annotated[bool, <typer.models.OptionInfo object at 0x10ed45090>]) –
--no-resumeignore any existing checkpoint.unsafe_mode (Annotated[bool | None, <typer.models.OptionInfo object at 0x10ed451d0>]) –
--unsafe-modemerge Ralph Workflow’s MCP config into the agent’s existing config instead of overwriting.inspect_checkpoint (Annotated[bool, <typer.models.OptionInfo object at 0x10ed45310>]) –
--inspect-checkpointprint checkpoint JSON and exit.dry_run (Annotated[bool, <typer.models.OptionInfo object at 0x10ed45450>]) –
--dry-runrun without invoking agents.list_agents (Annotated[bool, <typer.models.OptionInfo object at 0x10ed45590>]) –
--list-agentsprint configured agents and exit.list_providers (Annotated[bool, <typer.models.OptionInfo object at 0x10ed456d0>]) –
--list-providersprint providers and exit.diagnose (Annotated[bool, <typer.models.OptionInfo object at 0x10ed45810>]) –
--diagnose/-dpre-flight check.check_config (Annotated[bool, <typer.models.OptionInfo object at 0x10ed45950>]) –
--check-config/-Cvalidate config.check_mcp (Annotated[bool, <typer.models.OptionInfo object at 0x10ed45a90>]) –
--check-mcpvalidate custom MCP servers.init (Annotated[str | None, <typer.models.OptionInfo object at 0x10ed45bd0>]) –
--init [PATH]scaffold.agent/+PROMPT.md.regenerate_config (Annotated[bool, <typer.models.OptionInfo object at 0x10ed45d10>]) –
--regenerate-configrewrite config from bundled defaults (backs up to<name>.bak).force_init_skills (Annotated[bool, <typer.models.OptionInfo object at 0x10ed45e50>]) –
--force-init-skillsre-run baseline skill install.generate_local_config (Annotated[bool, <typer.models.OptionInfo object at 0x10ed45f90>]) –
--init-local-config/--generate-local-configwrite a project-localralph-workflow.toml.generate_commit_msg (Annotated[bool, <typer.models.OptionInfo object at 0x10ed460d0>]) –
--generate-commit-msgbuild commit message artifact.generate_commit (Annotated[bool, <typer.models.OptionInfo object at 0x10ed46210>]) –
--generate-commitbuild and apply commit.show_commit_msg (Annotated[bool, <typer.models.OptionInfo object at 0x10ed46350>]) –
--show-commit-mgshow the commit message.git_user_name (Annotated[str | None, <typer.models.OptionInfo object at 0x10ed46490>]) –
--git-user-namegit user name for commits.git_user_email (Annotated[str | None, <typer.models.OptionInfo object at 0x10ed465d0>]) –
--git-user-emailgit user email for commits.version (Annotated[bool, <typer.models.OptionInfo object at 0x10ed46710>]) –
--version/-Vprint version and exit.explain_policy (Annotated[bool, <typer.models.OptionInfo object at 0x10ed46850>]) –
--explain-policyprint human-readable policy and exit.explain_policy_dir (Annotated[str | None, <typer.models.OptionInfo object at 0x10ed46990>]) –
--explain-policy-dir(hidden) policy directory to explain.parallel_worker_manifest (Annotated[str | None, <typer.models.OptionInfo object at 0x10ed46ad0>]) –
--parallel-worker-manifest(hidden) internal worker bootstrap manifest path.check_policy (Annotated[bool, <typer.models.OptionInfo object at 0x10ed46c10>]) –
--check-policyvalidate active policy and exit.prompt_helper (Annotated[bool, <typer.models.OptionInfo object at 0x10ed47110>]) –
--prompt-helperlaunch the interactive prompt-refinement helper.redo_policy (Annotated[bool, <typer.models.OptionInfo object at 0x10ed46d50>])
run_policy_agents (Annotated[bool, <typer.models.OptionInfo object at 0x10ed46e90>])
policy_only (Annotated[bool, <typer.models.OptionInfo object at 0x10ed46fd0>])
- Returns:
None. The handler exits viatyper.Exitor via the underlyingrun_pipelinereturn 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; emitdeclare_completeon success. Bounded subprocesses are routed throughralph.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
commandtelemetry tag. The value is drawn from a closed vocabulary: eitherctx.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). Theif ctx.invoked_subcommand: returnguard 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.
--quietand--debugtake precedence. Absent those, the default isVerbosity.VERBOSEso Ralph Workflow is visibly active by default. The legacy--verbosity normalinput is mapped to VERBOSE to preserve wrapper scripts that passednormalexplicitly.
- ralph.cli.main.smoke_interactive_agy(agent=<typer.models.OptionInfo object>, subagents=<typer.models.OptionInfo object>, subagent_prompt_file=None)[source]¶
Run the manual PTY smoke test for Google Anti Gravity.
- Parameters:
agent (str)
subagents (bool)
subagent_prompt_file (Annotated[Path | None, <typer.models.OptionInfo object at 0x10ed47390>])
- Return type:
None
- ralph.cli.main.smoke_interactive_claude(subagents=<typer.models.OptionInfo object>, subagent_prompt_file=None)[source]¶
Run the manual PTY/TUI smoke test for interactive Claude using claude/haiku.
- Parameters:
subagents (bool)
subagent_prompt_file (Annotated[Path | None, <typer.models.OptionInfo object at 0x10ed47750>])
- Return type:
None
- ralph.cli.main.smoke_interactive_cursor(agent=<typer.models.OptionInfo object>, subagents=<typer.models.OptionInfo object>, subagent_prompt_file=None)[source]¶
Run the manual end-to-end smoke test for the Cursor Agent CLI.
- Parameters:
agent (str)
subagents (bool)
subagent_prompt_file (Annotated[Path | None, <typer.models.OptionInfo object at 0x10ed47250>])
- Return type:
None
- ralph.cli.main.smoke_interactive_nanocoder(agent=<typer.models.OptionInfo object>, subagents=<typer.models.OptionInfo object>, subagent_prompt_file=None)[source]¶
Run the manual PTY smoke test for Nanocoder interactive mode.
- Parameters:
agent (str)
subagents (bool)
subagent_prompt_file (Annotated[Path | None, <typer.models.OptionInfo object at 0x10ed47b10>])
- Return type:
None
- ralph.cli.main.smoke_interactive_opencode(agent=<typer.models.OptionInfo object>, provider=<typer.models.OptionInfo object>, model=<typer.models.OptionInfo object>, subagents=<typer.models.OptionInfo object>, subagent_prompt_file=None)[source]¶
Run the manual smoke test for OpenCode against a live provider/model.
- Parameters:
agent (str)
provider (str | None)
model (str | None)
subagents (bool)
subagent_prompt_file (Annotated[Path | None, <typer.models.OptionInfo object at 0x10ed47d90>])
- Return type:
None
- ralph.cli.main.version_callback(version, ctx=None)[source]¶
Route
--versionthrough the shared display welcome banner.- Parameters:
version (bool)
ctx (DisplayContext | None)
- 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- drivesralph --generate-commitdiagnose_command- drivesralph diagnoseinit_command- drivesralph initrun_pipeline- drivesralph run(the primary workflow entry point)smoke_interactive_agy_command- drives the manual AGY end-to-end smoke testsmoke_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.
P2 (wt-028-display S-14): all visible output routes through the shared
ParallelDisplay surface (emit_status / emit_warning /
emit_blank_line) so the drift-prevention suite can verify no
command reaches the terminal through its own path. The interactive
confirmation prompt (typer.confirm) is the only side-channel that
remains — the prompt is operator input, not display output.
- 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 0x10ee88cd0>])
force (Annotated[bool, <typer.models.OptionInfo object at 0x10ee88e10>])
- 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:
ExceptionRaised 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:
objectAggregated result returned after all commit-message agent attempts complete.
The CLI surface consumes this dataclass for output formatting and exit code derivation. The new
outputfield exposes the captured agent lines so the CLI can render transcript content whenverboseis 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:
objectRuntime context threaded into each commit agent invocation attempt.
- Parameters:
repo_root (Path)
verbose (bool)
extra_env (dict[str, str])
general_config (GeneralConfig | UnifiedConfig | None)
bridge (SessionBridgeLike | None)
- 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:
objectOptions 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:
options (CommitPlumbingOptions | None) – Commit plumbing options.
display_context (DisplayContext | None) – Display context for consistent rendering. If None, a context is created using make_display_context().
pro_hooks (ProPipelineHooks | None)
model_identity (MultimodalModelIdentity | None)
- 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_master_prompt,invoke_agent, delete_commit_message_artifacts,read_commit_message_artifact}. New code should callexecute_agent_effect()through_run_commit_agent_attempt_with_recovery().- Parameters:
agent (AgentConfig)
prompt_file (str)
attempt_context (CommitAttemptContext)
session_id (str | None)
display_context (DisplayContext)
session_id_sink (Callable[[str], None] | None)
materializer (MaterializeMasterPromptFn | None)
- Return type:
CommitAgentAttempt
- ralph.cli.commands.commit.materialize_master_prompt(*, workspace_root, name, default_product_criteria=None, worker_namespace=None, planning_style=False, backend=<ralph.mcp.artifacts._path_file_backend.PathFileBackend object>)[source]¶
Write a master prompt file for the named agent and return its path.
The injected
backendcontrols bothmkdirand the physical text writes so a byte-identical re-emit of the current or system prompt does not advance the file’s mtime or generate an additional fseventsd notification. The default backend is the real-Path backend; tests inject an in-memory counting backend to verify the idempotent skip. The post-condition “file contains the rendered prompt” always holds: any read uncertainty or content mismatch falls through to a real write.- Parameters:
workspace_root (Path)
name (str)
default_product_criteria (str | None)
worker_namespace (Path | None)
planning_style (bool)
backend (FileBackend)
- 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 GitHub repo to star and fork Ralph Workflow.
This is a lightweight community-support command. It opens the canonical GitHub repository by default; the Codeberg mirror remains available.
- ralph.cli.commands.contribute.contribute(source='github')[source]¶
Open the Ralph Workflow repo in your browser so you can star it.
Opens the GitHub project page (default) or Codeberg mirror so you can star, watch, or fork — then come back and keep working.
Examples
ralph contribute # Open GitHub ralph contribute –source codeberg # Open Codeberg mirror
- Parameters:
source (Annotated[str, <typer.models.OptionInfo object at 0x10ee896d0>])
- 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_contextand 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:
workspace_scope (WorkspaceScope)
display_context (DisplayContext | None)
- 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 prompt-template name.
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, policy_mode=PolicyMode.NORMAL)[source]¶
Bases:
NamedTupleParameters 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)
policy_mode (PolicyMode)
- 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
- policy_mode: PolicyMode¶
Alias for field number 10
- pro_hooks: ProPipelineHooks | None¶
Alias for field number 8
- resume: bool¶
Alias for field number 3
- ralph.cli.commands.run.print_dry_run(initial_state, config, policy_bundle, *, display_context)[source]¶
Print dry-run information.
- Parameters:
initial_state (PipelineState | None)
config (UnifiedConfig)
policy_bundle (PolicyBundle | None)
display_context (DisplayContext)
- 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, subagents_requested=False)[source]¶
Bases:
objectGrouped parameters for a smoke run.
- Parameters:
agent_name (str)
config (AgentConfig)
unified_config (UnifiedConfig)
workspace_root (Path)
prompt_file (Path)
output_file (Path)
options (InvokeOptions)
display_context (DisplayContext)
bridge (SessionBridgeLike | None)
pipeline_deps (PipelineDeps | None)
subagents_requested (bool)
- 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, subagents_requested=False, subagent_dispatch_count=0, subagent_dispatch_seen=False, subagent_result_seen=False, post_subagent_activity_seen=False)[source]¶
Bases:
objectObserved 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])
subagents_requested (bool)
subagent_dispatch_count (int)
subagent_dispatch_seen (bool)
subagent_result_seen (bool)
post_subagent_activity_seen (bool)
- ralph.cli.commands.smoke.build_smoke_prompt(output_relpath, *, submit_artifact_tool_name, transport=None, subagents=False, subagent_prompt=None)¶
Return the prompt used for the parity smoke test.
- Parameters:
output_relpath (str)
submit_artifact_tool_name (str)
transport (AgentTransport | None)
subagents (bool)
subagent_prompt (str | None)
- Return type:
str
- ralph.cli.commands.smoke.render_smoke_report(results, *, agent_name='claude')¶
Render a human-readable parity report.
- Parameters:
results (list[SmokeRunResult])
agent_name (str)
- Return type:
str
- ralph.cli.commands.smoke.smoke_interactive_agy_command(agent_name='agy/gemini-3.6-flash-low', *, display_context=None, pro_hooks=None, model_identity=None, subagents=False, subagent_prompt_file=None)[source]¶
Run the manual AGY end-to-end smoke harness via the PTY contract.
This drives the live
agybinary (or theRALPH_AGY_BINARYoverride when set). The default alias isagy/gemini-3.6-flash-low, a low-tier model ID published by the measured AGY v1.1.8 CLI. Its price ordering is unverified, so this remains a manual diagnostic rather than a routine verification path. Use--agentto pin another publishedagy/<model>alias.- Parameters:
agent_name (str)
display_context (DisplayContext | None)
pro_hooks (ProPipelineHooks | None)
model_identity (MultimodalModelIdentity | None)
subagents (bool)
subagent_prompt_file (Path | None)
- Return type:
int
- ralph.cli.commands.smoke.smoke_interactive_claude_command(*, display_context=None, pro_hooks=None, model_identity=None, subagents=False, subagent_prompt_file=None)[source]¶
Run a token-consuming manual parity smoke test for interactive Claude.
- Parameters:
display_context (DisplayContext | None)
pro_hooks (ProPipelineHooks | None)
model_identity (MultimodalModelIdentity | None)
subagents (bool)
subagent_prompt_file (Path | None)
- Return type:
int
- ralph.cli.commands.smoke.smoke_interactive_cursor_command(agent_name='cursor/auto', *, display_context=None, pro_hooks=None, model_identity=None, subagents=False, subagent_prompt_file=None)[source]¶
Run the manual end-to-end smoke harness via the Cursor headless contract.
This drives the live
agentbinary (or theRALPH_CURSOR_BINARYoverride when set). The default alias iscursor/autobecause that exercises the dynamic-alias path and the documented Auto default-routing model. The command is OUTSIDEmake verifyper the cursor non-goal of no live-token-consuming smoke tests in verify (the harness only runs when an operator explicitly invokes it).- Parameters:
agent_name (str)
display_context (DisplayContext | None)
pro_hooks (ProPipelineHooks | None)
model_identity (MultimodalModelIdentity | None)
subagents (bool)
subagent_prompt_file (Path | None)
- Return type:
int
- ralph.cli.commands.smoke.smoke_interactive_nanocoder_command(agent_name='nanocoder', *, display_context=None, pro_hooks=None, model_identity=None, subagents=False, subagent_prompt_file=None)[source]¶
Run the manual PTY smoke test for a Nanocoder interactive alias.
- Parameters:
agent_name (str)
display_context (DisplayContext | None)
pro_hooks (ProPipelineHooks | None)
model_identity (MultimodalModelIdentity | None)
subagents (bool)
subagent_prompt_file (Path | None)
- Return type:
int
ralph.cli.commands.star¶
Star command — open GitHub and print star CTA for Ralph Workflow.
P2 (wt-028-display S-14): all visible output routes through the shared
ParallelDisplay surface (emit_status / emit_info_panel /
emit_renderable / emit_warning / emit_blank_line).
ralph.cli.commands.check_policy¶
check_policy command — validate the active policy and report results.
The command routes its success and error output through the shared
display surface (display.emit_status / display.emit_warning)
so the same vocabulary and rhythm Ralph uses during a live run also
applies to ad-hoc ralph check-policy invocations. P0 (wt-028-display
S-14) folds the previously-private print(..., file=sys.stderr)
escape hatches into the consolidated display so the drift-prevention
suite can verify no command reaches the terminal through its own path.
- ralph.cli.commands.check_policy.check_policy_command(policy_dir=None, counter_overrides=None, *, display_context=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.
display_context (DisplayContext | None) – Optional
DisplayContextto use for the shared display emit surface. WhenNone(the default), a context is created viamake_display_context()so the command runs stand-alone (the contractralph check-policycallers expect).
- 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, *, display_context=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.
display_context (DisplayContext | None) – Optional
DisplayContextto use for the shared display emit surface. WhenNone(the default), a context is created viamake_display_context()so the command runs stand-alone (the contractralph explaincallers expect). P0 (wt-028-display S-14) folds the previously-privateprint(..., file=sys.stderr)escape hatches into the consolidated display so the drift-prevention suite can verify no command reaches the terminal through its own path.
- 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.
Seed the first turn from an existing PROMPT.md, or ask the user once for an idea when none exists.
Invoke the agent non-interactively for one turn to produce an artifact.
If no artifact is produced, report the failure and leave PROMPT.md alone.
Otherwise drive the refine/accept loop, writing PROMPT.md only on Accept.
- Parameters:
config (UnifiedConfig)
workspace_root (Path)
- Return type:
None
ralph.cli.commands.prompt_helper_prompt¶
Prompt helper master 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 master 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_md_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_configto build the merged runtime configAgentConfigandUnifiedConfigfor validated configuration objectsVerbosityand related enums for CLI/config valuesensure_global_configand 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-agents.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-agents.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:
objectResult 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/excludeon a normalralphrun.Mirrors
auto_seed_default_gitignorebut for the per-user.git/info/excludefile. Reads the existing file (if any), computes the patterns from_DEFAULT_GIT_EXCLUDE_PATTERNSthat 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_dirso the helper works for normal repositories, git worktrees, and separate-git-dir layouts. In a worktree the top-level.gitis a gitfile pointing at the real gitdir; blindly usingrepo_root / '.git' / 'info' / 'exclude'would callmkdiron a file and fail withNotADirectoryError. Falls back to the repo-root layout only whenrepo_rootis not a git repository.Idempotent: a second call with the same
repo_rootreturns[]when every default pattern is already present. Does NOT clobber user-added entries.Tolerates a missing git dir: when
repo_rootis not a git repository (e.g. first-run bootstrap beforegit init), the helper writes.git/info/excludedirectly 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_PATTERNSthat are not already present, appends them viaappend_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_rootreturns[]when every default pattern is already present (the underlyingappend_to_gitignoreshort-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
.gitignoreinrepo_root; it does not require a.gitdirectory 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
.gitignorealready covered every default pattern.- Return type:
list[str]
- ralph.config.bootstrap.ensure_global_agents_config(global_dir=None, *, force=False)[source]¶
Ensure ~/.config/ralph-workflow-agents.toml exists, from the bundled template.
Agent CLI definitions are transport plumbing (binary, flags, output parser), so they live apart from the main config, which opens on the
[agent_chains]operators actually edit.- 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:
- 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:
- 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:
- 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:
- 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.agent_detection¶
Enable bundled agent configuration blocks for CLIs found on PATH.
- ralph.config.agent_detection.autowire_chains_to_detected_agent(main_config_path, *, detected=None)[source]¶
Point untouched default chains at a detected CLI when Claude is unavailable.
- Parameters:
main_config_path (Path)
detected (list[str] | None)
- Return type:
list[str] | Literal[‘kept-default-agent’, ‘chains-customized’] | None
ralph.config.config_error_messages¶
Plain-language configuration validation messages.
- ralph.config.config_error_messages.format_config_validation_error(exc, source_path)[source]¶
Translate a Pydantic validation error into Ralph’s what/why/fix shape.
Reuses
ralph.pydantic_validation_errors.format_validation_error_messages()so the per-field line lists the rejected value and (for closed enums) the allowed values — the bare first-field-only message the old formatter produced hides both. The envelope still mirrors theConfigTomlErrorshape so callers can render either with the same UI plumbing.- Parameters:
exc (ValidationError)
source_path (Path)
- Return type:
str
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:
StrEnumInvocation/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-jsonand Ralph wires MCP through.cursor/mcp.json/~/.cursor/mcp.json(the documented Cursor config surface).
- class ralph.config.enums.JsonParserType(*values)[source]¶
Bases:
StrEnumJSON 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:
StrEnumPause 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
strloaded at runtime from thepipeline.tomlconfiguration 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 byralph.config.pipeline.load_pipeline_definition(). Built-in phase handlers expose their canonical names as module-level constants (e.g.DEFAULT_*_PHASEinralph.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.phasesships the canonical phase-name constants.ralph.config.pipeline.load_pipeline_definition()readspipeline.tomland returns the validated phase set for the active workspace.
ralph.config.loader¶
Layered TOML configuration loader.
- Merge order (lowest to highest priority):
Embedded defaults (Pydantic field defaults)
~/.config/ralph-workflow-agents.toml (agent CLI definitions)
~/.config/ralph-workflow.toml (or $XDG_CONFIG_HOME/ralph-workflow.toml)
.agent/ralph-workflow.toml (project-local)
CLI flag overrides
This module handles the layered configuration merge:
Embedded defaults provide the baseline for every field.
The agents file supplies
[agents.*]transport plumbing, kept out of the main config so that file opens on the[agent_chains]operators edit. It sits BELOW the main config so an[agents.*]table left in an existingralph-workflow.tomlkeeps winning and no upgrade loses a customization.Global config supplies user-wide preferences.
Project-local config supplies repo-specific overrides.
CLI overrides apply last via dict patch before Pydantic validation.
- exception ralph.config.loader.ConfigTomlError[source]¶
Bases:
ValueErrorA malformed main configuration file that needs user correction.
- ralph.config.loader.collect_unknown_config_fields(data, path)[source]¶
Return formatted what/why/fix lines for every unknown field under data.
This is the pure collector used by
ralph --diagnoseto surface configuration typos without requiring a side-effecting log sink. The returned lines are newline-free single strings; the caller decides how to render them.- Parameters:
data (dict[str, object]) – The parsed TOML data to inspect.
path (Path) – The TOML file path (used in the message body).
- Returns:
A list of formatted human-readable lines, one per unknown field. Returns an empty list when the config is fully clean.
- Return type:
list[str]
- 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, unknown_field_warning=None)[source]¶
Build merged UnifiedConfig from all layers.
- Merge order (lowest to highest priority):
Embedded defaults (Pydantic field defaults)
~/.config/ralph-workflow-agents.toml (agent CLI definitions)
~/.config/ralph-workflow.toml
.agent/ralph-workflow.toml (project-local)
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)
unknown_field_warning (Callable[[str], None] | None)
- Returns:
Validated UnifiedConfig instance.
- Raises:
SystemExit – If configuration validation fails.
- Return type:
- 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:
- 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.loader.warn_unknown_fields(data, path)[source]¶
Emit warning logs for every misspelled field discovered under data.
Walks the closed main-config schema (general, ccs, prompt_helper), the open user-keyed maps (agents, ccs_aliases, agent_chains, agent_drains) at one level of depth, and the leaf fields under each user-defined agent name. Top-level typos are caught, nested typos like
general.wrokflowandgeneral.workflow.checkpont_enabledare caught, and user-defined names like[agent_chains.planning]or[agents.claude]are NOT warned about.- Parameters:
data (dict[str, object]) – The parsed TOML data to inspect.
path (Path) – The TOML file path (used in the warning message).
- Return type:
None
ralph.config.mcp_loader¶
Three-layer mcp.toml loader.
- Merge order (lowest → highest priority):
Bundled default - ralph/policy/defaults/mcp.toml (ships in wheel)
- User-global - $XDG_CONFIG_HOME/ralph-workflow-mcp.toml
(default: ~/.config/ralph-workflow-mcp.toml)
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:
SystemExitTyped 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:
- 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:
BaseModelTop-level mcp.toml document.
- Parameters:
mcp_servers (dict[str, McpServerSpec])
web_search (WebSearchConfig)
web_visit (WebVisitConfig)
media (MediaConfig)
- 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:
BaseModelSchema 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:
BaseModelMultimodal media support config in mcp.toml.
Broad multimodal support (images, PDFs, audio, video, documents) is enabled by default. Disable with
[media] enabled = falseinmcp.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:
BaseModelBackend 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:
BaseModelTop-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:
BaseModelTop-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:
BaseModelConfiguration 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:
- 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 resolvedtransport: Claude / Claude-interactive runs default toTrue; every other transport defaults toNone(no inference, the agent decides at runtime). The bundledralph-workflow.tomlships with[agents.claude] subagent_capability = trueso 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].
- 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:
BaseModelHeadless-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-inclaudeagent 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=2, 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, auto_integrate_enabled=True, auto_integrate_target='main', auto_integrate_remote_enabled=False, auto_integrate_remote='origin', 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)])
auto_integrate_enabled (bool)
auto_integrate_target (Annotated[str, MinLen(min_length=1)])
auto_integrate_remote_enabled (bool)
auto_integrate_remote (Annotated[str, MinLen(min_length=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:
BaseModelTop-level merged configuration (global + local + CLI overrides).
- Parameters:
general (GeneralConfig)
ccs (CcsConfig)
agents (dict[str, AgentConfig])
ccs_aliases (dict[str, str | CcsAliasConfig])
agent_chains (dict[str, AgentChainConfig])
agent_drains (dict[str, AgentDrainConfig])
prompt_helper (PromptHelperConfig)
- 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, newly_enabled=None, rewired=None, autowire_outcome=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.
newly_enabled (list[str] | None) – Agents automatically enabled because their CLI was found on PATH.
rewired (list[str] | None) – Default chains replaced with a detected agent because Claude is absent.
autowire_outcome (str | None) – Why safe autowiring deliberately left chains unchanged.
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:
BaseModelConfiguration 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:
- 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 resolvedtransport: Claude / Claude-interactive runs default toTrue; every other transport defaults toNone(no inference, the agent decides at runtime). The bundledralph-workflow.tomlships with[agents.claude] subagent_capability = trueso 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].
ralph.config.agent_transport¶
Agent transport enum for runtime invocation/MCP support.
- class ralph.config.agent_transport.AgentTransport(*values)[source]¶
Bases:
StrEnumInvocation/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-jsonand 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:
BaseModelPer-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:
BaseModelHeadless-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-inclaudeagent 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=2, 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, auto_integrate_enabled=True, auto_integrate_target='main', auto_integrate_remote_enabled=False, auto_integrate_remote='origin', 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)])
auto_integrate_enabled (bool)
auto_integrate_target (Annotated[str, MinLen(min_length=1)])
auto_integrate_remote_enabled (bool)
auto_integrate_remote (Annotated[str, MinLen(min_length=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:
BaseModelGeneral 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
TrueRalph 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. WhenFalse(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:
StrEnumJSON 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:
BaseModelSchema 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.
ralph.config.prompt_helper_config¶
Prompt helper configuration.
- class ralph.config.prompt_helper_config.PromptHelperConfig(*, agent=None)[source]¶
Bases:
BaseModelConfiguration 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.
ralph.config.verbosity¶
Verbosity level enum for Ralph output.
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:
BaseModelTop-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:
BaseModelTop-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:
config_dir (Path)
config (UnifiedConfig | None)
- Return type:
- 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:
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:
_FrozenPolicyModelDefinition 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:
_FrozenPolicyModelBinding 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:
_FrozenPolicyModelTop-level agents.toml policy document.
- Parameters:
agent_chains (dict[str, AgentChainConfig])
agent_drains (dict[str, AgentDrainConfig])
forbid_sibling_drain_inference (bool)
- 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, markdown_summary_path=None)[source]¶
Bases:
_FrozenPolicyModelContract 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)
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:
_FrozenPolicyModelPer-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:
_FrozenPolicyModelPer-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:
_FrozenPolicyModelTop-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:
_FrozenPolicyModelDeclaration 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:
_FrozenPolicyModelComposite 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:
_FrozenPolicyModelLeaf authoring block that compiles directly to one runtime phase.
- Parameters:
kind (Literal['individual'])
phase_name (str)
phase (PhaseDefinition)
- 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:
_FrozenPolicyModelLifecycle-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:
_FrozenPolicyModelDeclaration 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:
_FrozenPolicyModelCommit 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:
_FrozenPolicyModelRoute 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>, result_status_post_commit=<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:
_FrozenPolicyModelDefinition 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])
result_status_post_commit (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:
_FrozenPolicyModelLoop 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:
_FrozenPolicyModelTransition-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:
_FrozenPolicyModelPer-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:
_FrozenPolicyModelTransition 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:
_FrozenPolicyModelVerification 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:
_FrozenPolicyModelPolicy-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:
_FrozenPolicyModelTop-level pipeline.toml policy document.
- Parameters:
blocks (dict[str, PolicyBlock])
phases (dict[str, PhaseDefinition])
entry_block (str | None)
entry_phase (str)
terminal_phase (str)
loop_counters (dict[str, LoopCounterConfig])
budget_counters (dict[str, BudgetCounterConfig])
post_commit_routes (list[PostCommitRoute])
lifecycle_phases (dict[str, LifecyclePhasePolicy])
default_phase_retry_policy (PhaseRetryPolicy)
recovery (RecoveryPolicy)
- effective_retry_policy(phase_name)[source]¶
Resolve the effective retry policy for a phase.
- Parameters:
phase_name (str)
- Return type:
- model_config = {'frozen': True}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class ralph.policy.models.PolicyBundle(*, agents, pipeline, artifacts)[source]¶
Bases:
_FrozenPolicyModelAggregate of all three policy documents.
- Parameters:
agents (AgentsPolicy)
pipeline (PipelinePolicy)
artifacts (ArtifactsPolicy)
- 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:
_FrozenPolicyModelBudget-guarded route applied after commit success.
- Parameters:
when (PostCommitRouteWhen)
target (str)
- 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:
_FrozenPolicyModelCondition 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:
_FrozenPolicyModelPipeline-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:
ExceptionRaised 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:
ExceptionRaised 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:
bundle (PolicyBundle)
agent_registry (AgentRegistry)
- Return type:
None
- ralph.policy.validation.validate_chain_agents_on_path(agents, *, available=None, warn=None)[source]¶
Fail before spawning when a configured chain has no available CLI.
- Parameters:
agents (AgentsPolicy)
available (Callable[[str], bool] | None)
warn (Callable[[str], None] | None)
- Return type:
None
- ralph.policy.validation.validate_chain_exists(chain, bundle)[source]¶
Validate that an agent chain is defined.
- Parameters:
chain (str)
bundle (PolicyBundle)
- Return type:
None
- ralph.policy.validation.validate_checkpoint_against_policy(state, bundle)[source]¶
Validate a checkpoint state against the current policy bundle.
- Parameters:
state (PipelineState)
bundle (PolicyBundle)
- 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:
checkpoint_phase (str)
bundle (PolicyBundle)
- 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:
drain (str)
bundle (PolicyBundle)
- 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:
phase (str)
policy (PipelinePolicy)
- 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 thePROMPT_PATHenv var is honoured in Pro mode. Wheninline_promptis supplied the check is skipped entirely (the inline prompt supersedes the file on disk).- Parameters:
workspace_scope (WorkspaceScope)
inline_prompt (str | None)
- 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:
work_units (WorkUnitsPlan)
pipeline_policy (PipelinePolicy)
phase (str)
- 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.
ralph.policy.explain.commit_policy_explanation¶
Explanation of a phase’s commit policy.
ralph.policy.explain.loop_counter_explanation¶
Explanation of a loop counter.
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:
objectHuman-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.
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:
objectExplanation 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:
objectExplanation 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:
objectComplete structured explanation of a PolicyBundle.
- Parameters:
entry_phase (str)
terminal_phase (str)
entry_block (str | None)
authored_blocks (list[str])
lifecycle_explanations (list[LifecycleExplanation])
phases (list[PhaseExplanation])
loop_counters (list[LoopCounterExplanation])
budget_counters (list[BudgetCounterExplanation])
terminal_outcomes (list[TerminalOutcomeExplanation])
parallel_execution (ParallelExplanation | None)
parallel_executions (list[ParallelExplanation])
post_commit_routes (list[PostCommitRouteExplanation])
recovery (RecoveryExplanation | None)
ralph.policy.explain.post_commit_route_explanation¶
Explanation of a single post-commit route entry.
ralph.policy.explain.recovery_explanation¶
Explanation of the recovery policy.
ralph.policy.explain.terminal_outcome_explanation¶
Explanation of a terminal phase outcome.
ralph.policy.explain.verification_explanation¶
Explanation of a phase’s verification policy.
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):
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
ENTRY MARKER: =ENTRY=> appears on the line above the entry phase box.
HAPPY-PATH: A center-aligned “|” then “v” arrowhead connects consecutive phases on the success spine.
DECISION BRANCHES: For each decision whose target differs from on_success, render “ +–[decision_name]–> target_phase” (4-space indent).
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”
TERMINAL MARKERS: ==SUCCESS==> for terminal_outcome=”success”; ==FAILURE==> for terminal_outcome=”failure”. Only policy-declared terminal phases get markers.
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.
LOOP ANNOTATION: [loop: counter=NAME, max=N] appears above the phase box for phases with loop_policy.
GLYPHS: Pure ASCII only — allowed chars: + - | < > v ^ = [ ] _ . , plus alphanumerics. No Unicode box-drawing characters.
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]
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:
objectHandle 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()viaasyncio.to_threadso 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'), *, backend=<ralph.mcp.artifacts._path_file_backend.PathFileBackend object>)[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.
A byte-identical existing checkpoint short-circuits the temp write and the replace so per-iteration saves do not advance the file’s mtime or generate an additional fseventsd notification. The post-condition “checkpoint file contains the serialized state” always holds: any read uncertainty or content mismatch falls through to a real write.
- Parameters:
state (PipelineState) – The pipeline state to save.
path (Path) – Path to save the checkpoint. Defaults to .agent/checkpoint.json.
backend (FileBackend) – FileBackend used for the atomic write so the idempotent guard can be exercised by an in-memory backend under test without real filesystem I/O.
- 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()viaasyncio.to_threadso 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.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, backend=<ralph.mcp.artifacts._path_file_backend.PathFileBackend object>)[source]¶
Record
shaas the diff baseline for the current dev cycle.When
forceis False (the default), an existing baseline is preserved and this call is a no-op. Callers that open a fresh cycle must passforce=Trueto overwrite any stale baseline.The
backendseam defaults toDEFAULT_FILE_BACKENDso an in-memory backend can exercise the idempotent guard without touching real filesystem I/O.forceandbackendare keyword-only so existing positional callers andrunner_modulemonkeypatch spies are unaffected.A byte-identical rewrite of an existing baseline short-circuits the physical write so per-cycle baseline markers do not advance the file’s mtime or generate an additional fseventsd notification. The post-condition “baseline file contains
sha.strip()+'\n'” still holds because the fail-openwrite_text_if_changedguard falls through to a real write on any read uncertainty or content mismatch.- Parameters:
workspace_root (Path)
sha (str)
force (bool)
backend (FileBackend)
- 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.
ralph.pipeline.effects.early_skip_commit_effect¶
Early-skip-commit pipeline effect.
- class ralph.pipeline.effects.early_skip_commit_effect.EarlySkipCommitEffect[source]¶
Bases:
objectEffect 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:
objectEffect 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_ADVANCEso 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:
objectEffect 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.
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:
objectEffect 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_directoriesand 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='', requires_completion_evidence=True)[source]¶
Bases:
objectEffect to invoke an AI agent.
- Parameters:
agent_name (str)
phase (str)
prompt_file (str)
drain (str | None)
chain_name (str)
requires_completion_evidence (bool)
- 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
- requires_completion_evidence¶
Whether a clean exit must be corroborated by durable agent-side evidence. The
declare_completesentinel is always required; a required-artifact phase additionally needs its canonical submission receipt. The out-of-graphpolicy_remediationphase sets it False: it has no artifact contract, is not granted theartifact.submitcapability that exposesdeclare_complete, and is judged by a deterministic validator that re-runs after the agent exits — so there is no evidence for the agent to leave, and none is trusted.- Type:
bool
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:
objectEffect 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:
objectEffect 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.
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:
objectEvent emitted when an analysis phase resolves a decision from the agent artifact.
The reducer routes via
policy.phases[phase].decisions[decision].targetdirectly, 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.execution_result_event¶
Execution result event for the pipeline.
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:
objectEvent 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.
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:
objectEvent 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.
ralph.pipeline.events.worker_failed_event¶
Worker failed event for the pipeline.
ralph.pipeline.events.worker_started_event¶
Worker started event for the pipeline.
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:
objectResolved exhausted-analysis bypass outcome for a phase handoff.
- Parameters:
state (PipelineState)
target_phase (PipelinePhase)
skipped (tuple[ExhaustedAnalysisSkip, ...])
- class ralph.pipeline.handoffs.ExhaustedAnalysisSkip(phase, target_phase, iteration_field, iteration_value, max_iterations)[source]¶
Bases:
objectDetails for a single exhausted analysis phase that was bypassed.
- Parameters:
phase (PipelinePhase)
target_phase (PipelinePhase)
iteration_field (str)
iteration_value (int)
max_iterations (int)
- 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:
state (PipelineState)
candidate_phase (PipelinePhase)
pipeline_policy (PipelinePolicy)
- Return type:
- 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:
current_phase (PipelinePhase)
signal (str)
pipeline_policy (PipelinePolicy)
- Return type:
- ralph.pipeline.handoffs.resolve_phase_drain(phase, pipeline_policy)[source]¶
Return the configured drain for a phase.
- Parameters:
phase (PipelinePhase)
pipeline_policy (PipelinePolicy)
- 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:
state (PipelineState)
pipeline_policy (PipelinePolicy)
- Return type:
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 clears canonical Markdown artifacts, Markdown handoffs, and stale legacy JSON artifacts 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 is 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 canonical Markdown artifact and handoff for each drain listed in the phase’s clear_drains_on_fresh_entry field, but only when is_fresh_phase_entry returns True. Retained markdown drafts are cleared on the same signal but across every artifact type, not just declared drains — see
_clear_all_drafts().- 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:
ExceptionRaised 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). WhenNonethe legacy literal"PROMPT.md"is preserved on emittedInvokeAgentEffectinstances, 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:
phase (PipelinePhase) – Phase name.
pipeline_policy (PipelinePolicy) – Loaded pipeline policy.
- 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:
current_phase (PipelinePhase)
signal (str)
pipeline_policy (PipelinePolicy)
- Return type:
- ralph.pipeline.orchestrator.resolve_post_commit_phase(state, pipeline_policy)[source]¶
Backward-compatible wrapper for centralized post-commit routing.
- Parameters:
state (PipelineState)
pipeline_policy (PipelinePolicy)
- Return type:
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_WORKSPACEis 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:
objectOrchestrates 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:
effect (FanOutEffect)
executor (AgentExecutor)
display (ParallelDisplay)
ctx (WorkerContext | None)
- Return type:
list[Event]
- class ralph.pipeline.parallel.coordinator.WorkerContext(log=None, same_workspace=None, activity_router=None)[source]¶
Bases:
objectOptional runtime context injected into each parallel worker.
- Parameters:
log (WorkerLog | None)
same_workspace (SameWorkspaceContext | None)
activity_router (ActivityRouter | None)
- exception ralph.pipeline.parallel.coordinator.WorkerFailureError(unit_id, exit_code, error)[source]¶
Bases:
ExceptionRaised 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:
objectPaths 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:
effect (FanOutEffect)
executor (AgentExecutor)
display (ParallelDisplay)
ctx (WorkerContext | None)
activity_router (ActivityRouter | None)
- 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:
StrEnumSupported 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:
objectRuntime context for same-workspace parallel execution.
Workers run against
repo_rootdirectly. Per-worker mutable state lives underworker_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, step_ids=<factory>, phase, drain, config_path=None, cli_overrides=<factory>, worker_namespace, worker_artifact_dir, prompt_file, workspace_root)[source]¶
Bases:
BaseModelSerializable manifest describing one isolated worker invocation.
- Parameters:
unit_id (str)
description (str)
allowed_directories (list[str])
step_ids (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].
ralph.pipeline.parallel.worker_runtime¶
Early worker-runtime seam for dedicated parallel worker execution.
A manifest-launched worker deliberately does NOT enter the shared run
loop in ralph.pipeline.run_loop, so it does not inherit that
loop’s auto-integration seams either. It therefore carries its own:
run_worker_auto_integration() reproduces the run loop’s
_run_auto_integrate_recovery_preamble + _run_startup_integration
preamble before the agent is invoked, and runs again as a boundary
integration after the worker’s phase succeeds.
This matters because the manifest-worker topology is precisely the one
auto-integration exists for – several agents advancing one shared
mainline at the same time. Without these hooks such a worker neither
published its landings to its siblings nor picked theirs up, and the
coordinator-side join that would otherwise have covered it
(runner._integrate_after_fan_out, reached via FanOutEffect) is
dormant under the bundled dispatch_mode = "agent_subagents".
The seam is wrapped so it can never abort a worker: if the policy bundle, the agent registry, the pipeline dependencies or the display context are unavailable, the conflict resolvers are simply not built and the integration runs without the ability to resolve a conflict in place – it still performs the cross-agent catch-up, which is the bulk of the value, and records a conflict rather than resolving one.
- class ralph.pipeline.parallel.worker_runtime.WorkerRuntimePaths(checkpoint_path, product_criteria_path, prompt_dump_path, master_prompt_path, multimodal_sidecar_path)[source]¶
Bases:
objectWorker-local filesystem paths for prompt and checkpoint runtime state.
- Parameters:
checkpoint_path (Path)
product_criteria_path (Path)
prompt_dump_path (Path)
master_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:
- ralph.pipeline.parallel.worker_runtime.run_parallel_worker_from_manifest(*, manifest_path, display_context, pipeline_deps=None, model_identity=None, pro_hooks=None, backend=<ralph.mcp.artifacts._path_file_backend.PathFileBackend object>)[source]¶
Execute one manifest-backed worker flow without entering the shared run loop.
model_identityandpro_hooksare 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 explicitpipeline_depsbundle is supplied.- Parameters:
manifest_path (Path)
display_context (DisplayContext)
pipeline_deps (PipelineDeps | None)
model_identity (MultimodalModelIdentity | None)
pro_hooks (ProPipelineHooks | None)
backend (FileBackend)
- Return type:
int
- ralph.pipeline.parallel.worker_runtime.run_worker_auto_integration(*, config, workspace_scope, policy_bundle, registry, pipeline_deps, display_context, recover_first)[source]¶
Run one auto-integration seam on behalf of a parallel worker.
- Parameters:
config (UnifiedConfig) – The worker’s own resolved run configuration.
workspace_scope (WorkspaceScope) – The worker’s own scope. Its root is what keys the boundary refresh throttle, so several workers in one process cannot steal each other’s refresh window.
policy_bundle (PolicyBundle | None) – Resolved policy, or
Noneto skip resolution.registry (AgentRegistry | None) – Agent registry, or
Noneto skip resolution.pipeline_deps (PipelineDeps | None) – Pipeline dependencies, or
Noneto skip resolution.display_context (DisplayContext | None) – Display context, or
Noneto skip resolution.recover_first (bool) – Whether to reconcile a durable crash record left by an interrupted integration before integrating. True for the startup seam, False for the per-phase boundary.
- Returns:
The recorded
RebaseState, orNonewhen the hook decided there was nothing to do. Whenrecover_firstrecovery came back still owning the durable integration record (recovery_retained_record()) the recovery outcome is returned WITHOUT integrating: the integration writes its own durable record before mutating git, which would overwrite the unreconciled one and lose the pre-integration feature SHA the next recovery needs.- Return type:
RebaseState | None
Never raises: a failure anywhere in this seam is logged and swallowed, because an integration problem must never abort the worker whose actual job is the phase it was launched for.
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.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:
objectAssembled session, MCP server handle, and workspace scope for a parallel worker.
- Parameters:
session (AgentSession)
mcp_handle (McpServerHandle)
workspace_scope (WorkspaceScope)
- 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:
objectOptional 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
WorkerSessionConfigto propagate session contract parameters (drain, capabilities, model identity, capability profile) from the parent phase’sSessionMcpPlanso the worker exposes the same multimodal capability surface as serial execution.- Parameters:
unit (WorkUnit)
mcp_factory (McpServerFactory)
workspace_scope (WorkspaceScope)
config (WorkerSessionConfig | None)
- Return type:
ralph.pipeline.parallel.parallel_execution_mode¶
Supported parallel execution modes.
- class ralph.pipeline.parallel.parallel_execution_mode.ParallelExecutionMode(*values)[source]¶
Bases:
StrEnumSupported 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
PipelineStateand policy-declared budget counters: the first budget-tracked counter (in commit-phase BFS order) maps toactual_developer_runs; the second maps toactual_reviewer_runs.
- class ralph.pipeline.progress.AnalysisLoopCounter(completed, cap)[source]¶
Bases:
objectCanonical 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_epochto0on 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 overriderecovery_epoch(e.g.recover_missing_plan_handoffinralph/pipeline/_runner_state_helpers.pyand_advance_to_failedinralph/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 keepspipeline_policy.recovery.cycle_capscoped to the current missing-plan recovery loop rather than inflating across successful forward transitions.- Parameters:
state (PipelineState)
target_phase (PipelinePhase)
policy (PipelinePolicy | None)
- 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.consume_post_commit_phase_override(state)[source]¶
Clear a transient post-commit route after its target has been resolved.
- Parameters:
state (PipelineState)
- 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:
state (PipelineState)
run_context (RunContext)
policy (PipelinePolicy | None)
- Return type:
- 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:
iteration_field (str)
policy (PipelinePolicy)
- 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:
state (PipelineState)
policy (PipelinePolicy | None)
- 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_iterationstores 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.auto_integrate¶
Auto-integration seam: rebase a feature branch and fast-forward its target safely.
The workflow first rebases the feature branch onto its live local target. On a
conflict, it hands the stop to the conflict-resolution pipeline to resolve the
rebase in place, prove and stage the result, then run git rebase --continue.
Only when that resolution cannot land does it merge on unresolved conflict as
the endpoint fallback before fast-forwarding the target. The phase hook reuses
a clean worktree and records each attempt before the target advances.
- class ralph.pipeline.auto_integrate.IntegrationRecord(*, phase, target, pre_feature_sha, pre_target_sha, integrated_feature_sha=None, resolving_rebase=False, operation_kind='feature_integrate', owning_worktree=None)[source]¶
Bases:
BaseModelDurable phased record of an in-progress auto-integration.
Persisted to
<workspace_scope.root>/.agent/auto_integrate_in_progress.jsonviawrite_record()(atomic temp +os.replace) so a SIGKILL mid-write leaves the previous record intact and a recovery preamble on resume can decide whether to land the fast-forward (phase=’integrated’) or restore the feature branch to its pre-integration state (phase=’integrating’).- Parameters:
phase (Literal['integrating', 'integrated'])
target (str)
pre_feature_sha (str)
pre_target_sha (str | None)
integrated_feature_sha (str | None)
resolving_rebase (bool)
operation_kind (Literal['feature_integrate', 'target_reconcile'])
owning_worktree (str | None)
- phase¶
'integrating'while the rebase/merge is in flight;'integrated'once the feature branch fully contains the target and only the fast-forward remains. Restricted to those two values by theIntegrationPhaseLiteral so an on-disk record carrying a stray value is rejected byread_record()as corrupt instead of being acted on.- Type:
- target¶
The integration target branch name.
- Type:
str
- pre_feature_sha¶
The feature branch HEAD SHA captured BEFORE any rebase/merge; used to restore on a crash that interrupts the rebase.
- Type:
str
- pre_target_sha¶
The target branch SHA captured BEFORE the fast-forward attempt; the observed
<oldvalue>for the atomic compare-and-swap.- Type:
str | None
- integrated_feature_sha¶
The feature branch HEAD SHA captured AFTER the rebase/merge succeeded. Present only when phase=’integrated’.
- Type:
str | None
- resolving_rebase¶
True while a rebase-conflict resolution agent is working inside a PAUSED rebase. Recovery aborts such a rebase exactly as it aborts any other – an orphaned rebase cannot be resumed by a process that has no agent session for it – but the flag lets recovery say which of the two it found, so an operator can tell an interrupted resolution from an ordinary crashed rebase without reading the code.
- Type:
bool
- model_config = {'frozen': True}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- ralph.pipeline.auto_integrate.auto_integrate_after_commit(config, workspace_scope, state, *, conflict_resolver=None, rebase_stop_resolver=None, display=None, sleep=<built-in function sleep>, jitter=<built-in method random of Random object>)[source]¶
Integrate after a commit, recording skips and surfacing terminal-state violations.
- Parameters:
config (UnifiedConfig)
workspace_scope (WorkspaceScope)
state (RebaseState)
conflict_resolver (ConflictResolver | None)
rebase_stop_resolver (RebaseStopResolver | None)
display (ParallelDisplay | None)
sleep (Callable[[float], None])
jitter (Callable[[], float])
- Return type:
RebaseState | None
- ralph.pipeline.auto_integrate.auto_integrate_on_phase_transition(config, workspace_scope, state, *, conflict_resolver=None, rebase_stop_resolver=None, display=None, sleep=<built-in function sleep>, jitter=<built-in method random of Random object>)[source]¶
Keep a clean worktree synchronized at phase boundaries without hiding failures.
- Parameters:
config (UnifiedConfig)
workspace_scope (WorkspaceScope)
state (RebaseState)
conflict_resolver (ConflictResolver | None)
rebase_stop_resolver (RebaseStopResolver | None)
display (ParallelDisplay | None)
sleep (Callable[[float], None])
jitter (Callable[[], float])
- Return type:
RebaseState | None
- ralph.pipeline.auto_integrate.recover_incomplete_integration(workspace_scope, *, config=None)[source]¶
Recover from an interrupted integration at run-loop startup.
Behavior (AC-11):
No durable record → no-op (never disturb an operator’s own git operation).
Abort any owned engine rebase/merge in flight before doing anything else. If the abort itself FAILS, the durable record is RETAINED so the next startup can retry; the returned
RebaseStaterecords the abort failure as a skip with the original target preserved. The merge check is read viaralph.git.merge.merge_state(), so a FAILED git query counts as a possible in-flight merge (abort attempted, record retained unless a readable state later provesMERGE_HEADabsent) rather than as “nothing to abort”.If
record.phase == 'integrating': the rebase/merge never completed → restore the feature branch topre_feature_shaviareset_hard. The record is cleared ONLY afterreset_hardsucceeds AND no owned engine op remains; if reset_hard raises or any prior abort left an in-progress operation, the record is RETAINED for retry.If
record.phase == 'integrated': the rebase/merge already completed and only the fast-forward may be unfinished → safely CONTINUE the fast-forward (same worktree-aware CAS path used in the happy path).The durable record is cleared only when the recovery path confirms (1) no owned git operation remains in flight AND (2) the feature SHA was restored to
pre_feature_sha(for phase=’integrating’) OR the fast-forward reconciled cleanly (for phase=’integrated’). Any failure retains the record so the next run can retry the recovery.Every outcome that RETAINS the record sets
RebaseState.recovery_record_retained, which callers read throughrecovery_retained_record(). A caller that sees it must not start a fresh integration in the same startup: the integration writes its own durable record before mutating git and would overwrite the unreconciled one.
- Parameters:
workspace_scope (WorkspaceScope) – Scope whose root holds the durable record.
config (UnifiedConfig | None) – Run configuration, used ONLY to re-read the mainline pointer before the phase=’integrated’ ancestry verdicts. Optional, and
Nonereproduces the pre-seam behaviour exactly, so a caller that holds no config keeps working.
- Return type:
RebaseState | None
Any unexpected exception inside this function is logged and swallowed; it must never abort the run.
- ralph.pipeline.auto_integrate.recovery_retained_record(state)[source]¶
Whether
statecame back still OWNING the durable record.recover_incomplete_integration()has two shapes of outcome. Either it reconciled the interrupted operation – cleared the durableIntegrationRecordafter proving the feature branch was restored or the fast-forward landed – or it hit a transient failure and deliberately LEFT the record on disk so the next startup can retry it.In the second shape recovery is not finished, and the caller must NOT start a fresh integration in the same startup:
auto_integrate._integrate_oncewrites a newIntegrationRecord(phase='integrating', ...)before it touches git, which overwrites the only durable metadata describing the still-unreconciled operation – the pre-integration feature SHA a later recovery needs to restore, and the target the interrupted landing was aimed at.Callers read the retention fact THROUGH this predicate rather than from
last_reason: that string is operator display text, is formatted differently on every branch, and interpolates exception messages.None(no record existed, recovery was a no-op) is not retained.- Parameters:
state (RebaseState | None)
- Return type:
bool
- ralph.pipeline.auto_integrate.resolve_integration_target(config, root)[source]¶
Return the configured target only when it is a local branch.
- Parameters:
config (UnifiedConfig)
root (Path)
- Return type:
str | None
ralph.pipeline.auto_integrate_backup_refs¶
Backup-reference lifecycle for auto-integration attempts.
ralph.pipeline.auto_integrate_recovery_terminal¶
Terminal-state and backup-reference helpers for integration recovery.
- exception ralph.pipeline.auto_integrate_recovery_terminal.TerminalStateViolationError[source]¶
Bases:
RuntimeErrorRaised when an integration leaves git in a non-terminal state.
ralph.pipeline.auto_integrate_terminal¶
Verified terminal cleanup for auto-integration attempts.
- ralph.pipeline.auto_integrate_terminal.verify_and_cleanup_backup(root, backup_ref, expected_head_sha, owns_resolution)[source]¶
Verify terminal git state, then delete the no-longer-needed backup ref.
- Parameters:
root (Path)
backup_ref (str | None)
expected_head_sha (str | None)
owns_resolution (bool)
- Return type:
None
ralph.pipeline.auto_integrate_backoff¶
Bounded jittered wait between auto-integration landing retries.
Separated from ralph.pipeline.auto_integrate so that module stays
under the repo-structure _MAX_FILE_LINES cap, the same reason
ralph.pipeline.auto_integrate_recovery and
ralph.pipeline.auto_integrate_refresh live beside it.
Why a wait exists at all. The landing retry loop re-runs when the fast-forward lost a compare-and-swap, and a lost CAS means ANOTHER agent just won the race for the same mainline ref. Retrying immediately means every loser re-rebases and re-races in lockstep, spends the whole three-attempt budget in milliseconds, and starves the slowest agent off the target until its next seam – the opposite of the fleet synchronisation auto-integration exists to provide.
Both the clock and the randomness are INJECTED rather than imported
here. ralph.testing.audit_test_policy forbids time.sleep in the
deterministic suite and the per-test cap is one second, so a real
backoff could not be proven inside the gate at all; tests pass a
recording sleep and a constant jitter and assert the schedule
without ever waiting.
- ralph.pipeline.auto_integrate_backoff.RETRY_BASE_DELAY_SECONDS = 0.5¶
First backoff step. Long enough that two agents which collided are unlikely to re-collide, short enough that a seam does not visibly stall a run.
- ralph.pipeline.auto_integrate_backoff.RETRY_MAX_DELAY_SECONDS = 4.0¶
Ceiling on that backoff. The budget is three attempts inside ONE seam; the next commit or phase boundary is the real long-range retry, so waiting longer here buys nothing.
- ralph.pipeline.auto_integrate_backoff.wait_before_retry(attempt, *, sleep, jitter)[source]¶
Pause before landing attempt
attempt.- Parameters:
attempt (int) – 0-based attempt index. The caller only invokes this for
attempt >= 1, so the first attempt never waits.sleep (Callable[[float], None]) – Wait seam,
time.sleepin production.jitter (Callable[[], float]) – Uniform
[0, 1)source,random.randomin production.
- Return type:
None
The delay is exponential with FULL jitter – drawn from half to all of the exponential step – because a fixed backoff would simply re-synchronise the agents it had just separated.
Never raises. An exception out of either injected seam is swallowed, because the surrounding contract is that the integration step never raises into the run; a failed wait must cost at most a lost delay.
ralph.pipeline.auto_integrate_boundary_refresh¶
Throttle for the mainline refresh on dirty phase boundaries.
ralph.pipeline.auto_integrate.auto_integrate_on_phase_transition()
fires on eleven events per cycle, so an unconditional git fetch on
every dirty boundary is a real per-cycle cost regression. But a dirty
boundary that reports “nothing to catch up” from a pointer another agent
moved minutes ago is exactly the silent staleness that makes concurrent
multi-agent integration unreliable: the operator-visible skip is computed
from a stale ref and nobody can tell.
A monotonic-clock throttle resolves both, under two rules that exist because breaking either of them re-creates the staleness the throttle was meant to bound:
Keyed per (repository root, target branch). The window is not a process-global scalar. Several workspace scopes – linked worktrees of one fleet, or several manifest-launched parallel workers – share one process, and a scalar window let whichever of them probed first steal the whole interval from all the others.
Consumed only on a SUCCESSFUL refresh.
should_refresh()grants permission;record_outcome()arms the window, and only for an outcome thatrefresh_outcome_is_healthy()accepts. A refresh that came backREFRESH_UNREACHABLEestablished no freshness at all, so letting it burn the interval guaranteed at least one whole window of unrefreshed boundary probes after every transient blip.
The clock is injected so tests advance it directly instead of sleeping.
- ralph.pipeline.auto_integrate_boundary_refresh.BOUNDARY_REFRESH_THROTTLE = <ralph.pipeline.auto_integrate_boundary_refresh.BoundaryRefreshThrottle object>¶
Process-wide throttle shared by every phase-boundary hook, so the eleven boundary events of one cycle share a single budget PER
(root, target)pair. Tests must construct their ownBoundaryRefreshThrottlewith a fake clock rather than mutating this singleton.
- class ralph.pipeline.auto_integrate_boundary_refresh.BoundaryRefreshThrottle(*, min_interval_seconds=30.0, clock=<built-in function monotonic>, max_tracked_keys=64)[source]¶
Bases:
objectRate-limits the origin refresh performed on dirty phase boundaries.
- Parameters:
min_interval_seconds (float)
clock (Callable[[], float])
max_tracked_keys (int)
- record_forced_outcome(root, target, outcome)[source]¶
Record a divergent override, consuming it only when healthy.
- Parameters:
root (Path | str)
target (str)
outcome (str)
- Return type:
None
- record_outcome(root, target, outcome)[source]¶
Arm the next window for
(root, target)ifoutcomewas healthy.An outcome that could not vouch for the pointer (an unreachable origin, a lost race, a failed query) leaves the window unarmed, so the very next boundary probe is permitted to try again rather than inheriting a whole interval of blindness from one blip.
- Parameters:
root (Path | str) – Repository root the refresh ran in.
target (str) – Target branch the refresh tried to freshen.
outcome (str) – The
REFRESH_*outcome the refresh returned.
- Return type:
None
- should_force_refresh(root, target)[source]¶
Whether one divergent override is still available in this window.
A healthy forced refresh consumes this separate permit. Otherwise every suppressed dirty boundary that still sees local divergence would force another fetch and turn a phase burst into a storm. Unhealthy forced refreshes do not consume the permit, so they retry immediately just like ordinary unhealthy refreshes.
- Parameters:
root (Path | str)
target (str)
- Return type:
bool
- should_refresh(root, target)[source]¶
Whether a refresh of
targetunderrootis permitted now.Permission is granted when this pair has no armed window, or when
min_interval_secondshas elapsed since its last SUCCESSFUL refresh. Asking does not consume anything: the caller must report back throughrecord_outcome(), and only a healthy outcome arms the next window.- Parameters:
root (Path | str) – Repository root the refresh would run in.
target (str) – Target branch the refresh would freshen.
- Returns:
Truewhen the caller may refresh now.- Return type:
bool
- ralph.pipeline.auto_integrate_boundary_refresh.DEFAULT_MIN_REFRESH_INTERVAL_SECONDS = 30.0¶
Minimum wall time between two boundary refreshes. Short enough that a mainline pointer moved by a sibling agent is observed within one phase-boundary cycle, long enough that the eleven boundary events in a cycle collapse to at most one fetch.
ralph.pipeline.conflict_resolution¶
Out-of-graph pipeline that resolves auto-integration merge conflicts.
Auto-integration can stop on conflicts at four different seams (after a
commit, at any of eleven phase boundaries, at the fan-out join and at run
startup). None of those is a node in the main PipelinePolicy graph, so
conflict resolution cannot be a graph phase: it must be enterable from the
middle of a seam. This package is therefore a HARDCODED, out-of-graph
pipeline modelled on ralph.project_policy – a pure phase graph
(graph), a prompt renderer, its
own status-bar surface, an MCP-backed agent session and a driver that loops
within a budget and never aborts the surrounding run.
The MCP-backed session is the load-bearing part. The resolver used to call
invoke_agent directly with no RALPH_MCP_ENDPOINT, which left the
agent with no Ralph tool surface, no declare_complete contract and no
exec-policy git denial. Running the invocation through
effect_executor.execute_agent_effect (as
ralph.project_policy.cli_integration does) builds the session bridge,
so the completion contract and the git denial are both real.
- class ralph.pipeline.conflict_resolution.RebaseStop(sha, subject, conflicted_files, stop_index, stop_cap, replay_index=None, replay_total=None)[source]¶
Bases:
objectOne commit a rebase has paused on because replaying it conflicted.
Carries exactly the context a resolution session is allowed to see: which commit is being replayed and which paths conflicted, plus two INDEPENDENT counters that are easy to confuse.
stop_index/stop_capare the bounded loop’s safety counters: how many stops this loop has spent out of the fixedMAX_REBASE_CONFLICT_STOPSit is allowed, which is what terminates the loop. They say nothing about how long the rebase is.replay_index/replay_totalare the operator-facing replay position: which of the rebase’s own commits is being replayed, read from git’s rebase state by_read_replay_progress(). They are display-only, bothNonewhen that state is unreadable, and must never influence loop termination.- Parameters:
sha (str)
subject (str)
conflicted_files (tuple[str, ...])
stop_index (int)
stop_cap (int)
replay_index (int | None)
replay_total (int | None)
- ralph.pipeline.conflict_resolution.resolution_deadline(config, clock=None)[source]¶
Absolute monotonic instant the whole resolution must finish by.
Public because a REBASE resolution spans several stops and every one of them must be bounded by ONE ceiling. A per-stop deadline would multiply the resolution timeout by
MAX_REBASE_CONFLICT_STOPS, turning a 15-minute budget into a 2.5-hour one. The caller takes this once before the loop and hands the same value to every stop.- Parameters:
config (UnifiedConfig)
clock (MonotonicClock | None)
- Return type:
float
- ralph.pipeline.conflict_resolution.resolve_rebase_in_progress(root, target, resolver)[source]¶
Drive an in-progress rebase to completion through
resolver.- Parameters:
root (Path) – Repository root holding the paused rebase.
target (str) – Branch being rebased onto, passed through to the resolver as context and used as the completion check’s fallback when the replay’s own base cannot be read.
resolver (RebaseStopResolver) – Called once per stop to resolve that stop’s conflicts.
- Returns:
Trueonly when the rebase finished ANDHEADis a descendant of the commit the replay was landing on.Falsefor every other outcome, including a declining resolver, a surviving conflict marker, a refused continuation and an exhausted stop budget. Atargeta sibling moved during the resolution is NOT one of those outcomes – see_resolve_stops().- Return type:
bool
Never raises. The caller treats
Falseas “resolution declined” and falls through to the pre-existing abort-then-endpoint-merge path, so an unexpected failure here degrades to exactly the behaviour that shipped before this module existed.
- ralph.pipeline.conflict_resolution.run_conflict_resolution_pipeline(*, root, target, config, pipeline_deps, workspace_scope, policy_bundle, display, display_context, invoke=None, clock=None)[source]¶
Resolve the in-progress merge’s conflicts, or decline.
- Parameters:
root (Path) – Repository root holding the in-progress merge.
target (str) – Mainline branch being merged in.
config (UnifiedConfig) – Run configuration, supplying the wall-clock ceiling.
pipeline_deps (PipelineDeps) – Pipeline dependency bundle for the agent session.
workspace_scope (WorkspaceScope) – Workspace scope for the agent session.
policy_bundle (PolicyBundle) – Resolved policy supplying the drain’s agent chain.
display (ParallelDisplay | None) – Active display, when there is one.
display_context (DisplayContext | None) – Display context, when there is one.
invoke (ResolutionInvoker | None) – Injected round runner; defaults to a real MCP-backed session.
clock (MonotonicClock | None) – Injected monotonic clock the whole-pipeline deadline is measured against; defaults to
time.monotonic().
- Returns:
Trueonly when every previously-conflicted path is marker-free. Never raises: any failure is reported asFalseso the caller aborts the merge and records a resolution failure.- Return type:
bool
- ralph.pipeline.conflict_resolution.run_rebase_conflict_resolution_pipeline(*, root, target, stop, config, pipeline_deps, workspace_scope, policy_bundle, display, display_context, deadline=None, invoke=None, clock=None)[source]¶
Resolve ONE commit a rebase has stopped on, or decline.
Runs the same rounds, through the same MCP-backed session, under the same deterministic marker gate as
run_conflict_resolution_pipeline(). Only three things differ: the footer and prompt name the commit being replayed, the round cap isMAX_ROUNDS_PER_STOP, and the footer is NOT captured/restored here – the caller owns the footer for the whole multi-stop loop viaconflict_status_bar_session(), because capturing per stop would capture the previous stop’s own conflict bar and leave it pinned when the loop ends.- Parameters:
root (Path) – Repository root holding the paused rebase.
target (str) – Branch being rebased onto.
stop (RebaseStop) – The commit this call must resolve.
config (UnifiedConfig) – Run configuration, supplying the wall-clock ceiling.
pipeline_deps (PipelineDeps) – Pipeline dependency bundle for the agent session.
workspace_scope (WorkspaceScope) – Workspace scope for the agent session.
policy_bundle (PolicyBundle) – Resolved policy supplying the drain’s agent chain.
display (ParallelDisplay | None) – Active display, when there is one.
display_context (DisplayContext | None) – Display context, when there is one.
deadline (float | None) – Shared monotonic ceiling for the WHOLE rebase resolution, from
resolution_deadline(). Defaults to a fresh per-stop ceiling, which is correct only for a single-stop rebase.invoke (ResolutionInvoker | None) – Injected round runner; defaults to a real MCP-backed session.
clock (MonotonicClock | None) – Injected monotonic clock; defaults to
time.monotonic().
- Returns:
Trueonly when every path that conflicted on this stop is marker-free. Never raises.- Return type:
bool
ralph.pipeline.conflict_resolution.graph¶
The hardcoded conflict-resolution pipeline: a single phase with a bounded loop.
This module is the conflict-resolution pipeline’s single source of truth
for ROUTING. It is pure: no I/O, no agents, no workspace.
ralph.pipeline.conflict_resolution.driver executes what this module
decides.
The graph:
rebase_conflict_resolution
|
resolved | -> resolved (no surviving markers, invocation succeeded)
unresolved | -> rebase_conflict_resolution (round += 1, markers fed back)
exhausted| -> abandoned (budget spent; caller aborts the merge)
One rule bounds it, and it is load-bearing: the deterministic marker scan
is a hard gate. resolved requires BOTH a successful agent invocation
AND an empty surviving-marker list recomputed by Ralph over exactly the
paths that were unmerged before the round. An agent that reports success
over a file still carrying <<<<<<< has not resolved anything, and its
claim must never be able to launder a failing scan. Anything that is not
an unambiguous success routes back into another round or to
abandoned – never to resolved.
- ralph.pipeline.conflict_resolution.graph.MAX_REBASE_CONFLICT_STOPS: Final[int] = 10¶
Separate
git rebase --continuestops one integration attempt may resolve before giving up and falling back to the endpoint merge. A rebase replays N commits and can stop on a conflict at EVERY one of them, so the per-conflict round budget above cannot bound it: the two axes are independent. Ten is generous for a feature branch worth rebasing and still terminates in bounded time when a replay keeps re-conflicting; the endpoint merge remains the fallback either way.
- ralph.pipeline.conflict_resolution.graph.MAX_RESOLUTION_ROUNDS: Final[int] = 3¶
Rounds allowed within ONE integration seam. Three rounds is enough to converge a conflict or to prove it will not converge; a fourth is a display flood, not a strategy. Deliberately a module constant rather than a config key: a key would pull in the general config model, the loader allowlist and the configuration docs for marginal benefit.
- ralph.pipeline.conflict_resolution.graph.MAX_ROUNDS_PER_STOP: Final[int] = 3¶
Agent rounds allowed per rebase stop. Reuses the single-conflict budget deliberately: one rebase stop presents exactly the same problem to the agent as one merge conflict does, so a second number here would be two names for one policy.
- ralph.pipeline.conflict_resolution.graph.PHASE_RESOLUTION: Final[str] = 'rebase_conflict_resolution'¶
The resolution phase. The phase name IS the drain name declared in
ralph/policy/defaults/agents.toml, so there is no mapping table to keep in sync (the conventionralph.project_policy.pipeline_graphalso follows).
- ralph.pipeline.conflict_resolution.graph.TERMINAL_ABANDONED: Final[str] = 'abandoned'¶
the budget is spent with markers still surviving. The caller aborts the merge and records a resolution failure.
- Type:
Terminal
- ralph.pipeline.conflict_resolution.graph.TERMINAL_RESOLVED: Final[str] = 'resolved'¶
every conflicted path is marker-free and the merge may be staged and committed by Ralph.
- Type:
Terminal
- ralph.pipeline.conflict_resolution.graph.rounds_spent(round_index, cap=3)[source]¶
True when the resolution round budget is exhausted.
- Parameters:
round_index (int) – 1-based index of the round that just finished.
cap (int) – Round budget.
- Returns:
Whether the driver must stop looping and route to
TERMINAL_ABANDONED.- Return type:
bool
- ralph.pipeline.conflict_resolution.graph.route_after_round(*, invocation_succeeded, surviving_marker_paths, round_index, cap=3)[source]¶
Decide what happens after one resolution round.
Fails closed: only an invocation that succeeded AND left no surviving conflict marker reaches
TERMINAL_RESOLVED. Every other combination either buys another round or abandons.- Parameters:
invocation_succeeded (bool) – Whether the agent invocation reported success.
surviving_marker_paths (tuple[str, ...]) – Paths that STILL contain conflict markers, recomputed deterministically by Ralph after the round.
round_index (int) – 1-based index of the round that just finished.
cap (int) – Round budget.
- Returns:
TERMINAL_RESOLVED,TERMINAL_ABANDONED, orPHASE_RESOLUTIONto run another round.- Return type:
str
- ralph.pipeline.conflict_resolution.graph.route_after_stop(stops_spent, resolved, cap=10)[source]¶
Decide what happens after one
git rebase --continuestop.Mirrors
route_after_round()one level up: that function bounds the agent rounds spent on a single conflict, this one bounds the conflicted commits a single rebase may work through.- Parameters:
stops_spent (int) – Number of stops resolved so far, including this one.
resolved (bool) – Whether the rebase has finished with nothing left to replay.
cap (int) – Stop budget.
- Returns:
TERMINAL_RESOLVEDwhen the rebase completed,TERMINAL_ABANDONEDwhen the budget is spent, orPHASE_RESOLUTIONto resolve the next stop.- Return type:
str
ralph.pipeline.conflict_resolution.prompt¶
Renders the conflict-only prompt for one resolution round.
The resolution session must see the CONFLICT and nothing else. A prompt carrying the run’s source-prompt or plan payload would invite the agent to resume feature work inside an in-progress merge, which is precisely the state in which unrelated edits are most expensive to unpick. The template is therefore rendered with a closed variable set and no project payload section at all.
“Closed” means closed to the RUN’s payload, not to the session’s own
contract. The standard preamble every Ralph session gets – unattended
mode, the granted capabilities, and the brokered MCP tool surface – is
rendered here too, because a resolution session that is not told it must
edit through write_file will reach for a native tool that the MCP
broker has disabled. Only the ARTIFACT SUBMISSION block is suppressed:
this session submits nothing, it edits files and calls
declare_complete.
The rendered file lands at .agent/tmp/<phase>_prompt.md – the same
location ralph.prompts.debug_dump.prompt_dump_path() already uses
for every in-graph phase, so it is covered by the existing
runtime-artifact rules rather than introducing a new path shape.
- ralph.pipeline.conflict_resolution.prompt.PROMPT_TEMPLATE_NAME = 'conflict_resolution.jinja'¶
Packaged Jinja template holding the resolution prompt body. Kept alongside the pipeline prompt templates so it follows the same convention as every other prompt.
- ralph.pipeline.conflict_resolution.prompt.render_conflict_prompt(*, root, target, conflicted_paths, round_index, round_cap, surviving_marker_paths, replaying_commit_sha=None, replaying_commit_subject=None, stop_index=None, stop_cap=None, backend=<ralph.mcp.artifacts._path_file_backend.PathFileBackend object>)[source]¶
Render and materialize the prompt for one resolution round.
- Parameters:
root (Path) – Repository root holding the in-progress merge.
target (str) – Mainline branch being merged in.
conflicted_paths (Sequence[str]) – Paths git reported as unmerged.
round_index (int) – 1-based index of the round about to run.
round_cap (int) – Total rounds allowed.
surviving_marker_paths (Sequence[str]) – Paths that still carried conflict markers after the PREVIOUS round. Empty on round 1. This is what makes the loop converge instead of repeat.
replaying_commit_sha (str | None) – SHA of the commit the rebase stopped on. Setting it puts the prompt in REBASE mode; leaving it
Nonerenders the endpoint-merge prompt byte-identically to before.replaying_commit_subject (str | None) – Subject line of that commit.
stop_index (int | None) – 1-based index of the rebase stop being resolved.
stop_cap (int | None) – Total rebase stops allowed.
backend (FileBackend) – File backend seam, injected for tests.
- Return type:
Path | None
The rebase-mode variables add the ONE fact a merge conflict does not have – which commit is being replayed – and nothing else. The context stays the conflict alone: no source prompt, no plan, no artifact history, no analysis feedback.
- Returns:
The path the prompt was written to, or
Nonewhen it could not be written. Never raises: a prompt that cannot be materialized fails the round, it does not fail the run.- Parameters:
root (Path)
target (str)
conflicted_paths (Sequence[str])
round_index (int)
round_cap (int)
surviving_marker_paths (Sequence[str])
replaying_commit_sha (str | None)
replaying_commit_subject (str | None)
stop_index (int | None)
stop_cap (int | None)
backend (FileBackend)
- Return type:
Path | None
ralph.pipeline.conflict_resolution.status¶
Operator-facing surface for the conflict-resolution pipeline.
While the resolution pipeline runs, the persistent footer must say so: the run is stopped on a conflicted merge, an agent is editing files, and that can take minutes. Without a dedicated phase label the footer keeps showing whatever phase the run was in when the seam fired, which reads as a hang.
Every function here is defensive by contract, exactly as
ralph.project_policy.status_bar_module.push_remediation_status_bar is:
presentation must NEVER block integration. A display that raises is
logged at DEBUG and otherwise ignored.
- ralph.pipeline.conflict_resolution.status.NEUTRAL_PHASE_LABEL = 'Running'¶
Neutral footer label pushed when the resolution pipeline exits and there was no prior model to restore. Deliberately generic: this module cannot know which phase the run will resume into, and any label is better than leaving the footer claiming a resolution that has finished.
- ralph.pipeline.conflict_resolution.status.PHASE_LABEL = 'Rebase Conflict Resolution'¶
Footer label shown while the resolution pipeline owns the run.
- ralph.pipeline.conflict_resolution.status.capture_status_bar_model(display)[source]¶
Read the model currently in the footer so it can be restored.
Returns
Nonewhen the display exposes no readable status bar, in which caserestore_status_bar()is a no-op and the run loop re-pushes its own model on the next iteration.- Parameters:
display (object)
- Return type:
object | None
- ralph.pipeline.conflict_resolution.status.clear_conflict_status_bar(display, workspace_root, *, elapsed_seconds=None, run_started_monotonic=None)[source]¶
Push a neutral footer when there is no prior model to restore.
The resolution pipeline is entered from four seams, including the startup seam, where the next run-loop status-bar push can be a whole phase away. Leaving the footer on
PHASE_LABELfor that long tells the operator a resolution is still running when it has already finished – which reads exactly like the hang this phase label exists to rule out.Defensive by contract, like every function here: a display that raises is logged at DEBUG and otherwise ignored.
- Parameters:
display (object)
workspace_root (Path)
elapsed_seconds (float | None)
run_started_monotonic (float | None)
- Return type:
None
- ralph.pipeline.conflict_resolution.status.conflict_status_bar_session(display, workspace_root)[source]¶
Own the footer for a whole resolution loop: capture once, restore once.
A rebase resolution works through several stops, each of which pushes its own footer model. Capturing per stop would capture the CONFLICT bar pushed by the previous stop, so the final restore would put the resolution label back and leave it pinned after the loop ended – the display equivalent of the hang this phase label exists to rule out. Entering the context once around the entire loop captures the genuinely pre-resolution model.
Restores on exception too, so a loop that raises still hands the footer back.
- Parameters:
display (object)
workspace_root (Path)
- Return type:
Iterator[None]
- ralph.pipeline.conflict_resolution.status.emit_conflict_phase_line(display, message)[source]¶
Emit an operator-facing warn line for the resolution pipeline.
- Parameters:
display (object)
message (str)
- Return type:
None
- ralph.pipeline.conflict_resolution.status.push_conflict_status_bar(display, workspace_root, *, target, round_index, round_cap, stop_index=None, stop_cap=None, replay_index=None, replay_total=None, elapsed_seconds=None, agent_name=None, run_started_monotonic=None)[source]¶
Show the resolution phase and its round counter in the footer.
- Parameters:
display (object) – The active display, or any object; a display without an
update_status_barcallable is silently tolerated.workspace_root (Path) – Repository root shown in the footer.
target (str) – Mainline branch being merged in. Recorded on the log line so a stuck resolution is attributable.
round_index (int) – 1-based index of the round about to run.
round_cap (int) – Total rounds allowed.
stop_index (int | None) – 1-based index of the rebase stop being resolved, when the resolution is driving a rebase rather than a single endpoint merge.
Nonekeeps the footer byte-identical to the merge-mode label.stop_cap (int | None) – Total rebase stops allowed.
replay_index (int | None) – 1-based position of the commit the paused rebase is replaying, when git’s rebase state could be read.
replay_total (int | None) – Number of commits that rebase is replaying in all.
elapsed_seconds (float | None)
agent_name (str | None)
run_started_monotonic (float | None)
- Return type:
None
- ralph.pipeline.conflict_resolution.status.restore_status_bar(display, model)[source]¶
Put the pre-resolution footer model back. Never raises.
A
Nonemodel means the display exposed no readable footer to capture, so there is nothing to restore verbatim; the caller usesclear_conflict_status_bar()for that case rather than leaving the resolution label stranded.- Parameters:
display (object)
model (object | None)
- Return type:
None
ralph.pipeline.conflict_resolution.session¶
Runs one resolution round inside a REAL Ralph MCP session.
This module is the fix for the defect that made conflict resolution
structurally unable to work. The previous resolver called
ralph.agents.invoke.invoke_agent directly with no extra_env, so
RALPH_MCP_ENDPOINT was never set and no Ralph MCP session existed.
Three things follow from that, all of them fatal:
the exec-policy git denial in
ralph.mcp.tools.execnever bound, so the agent could abort or commit the merge Ralph owns;declare_completewas not in the agent’s tool surface at all, sorequires_completion_evidencecould not be demanded and a clean instant exit counted as “resolved”;nothing was recorded: output was consumed and discarded.
Routing the invocation through
ralph.pipeline.effect_executor.execute_agent_effect() – the same
seam ralph.project_policy.cli_integration uses for its
out-of-graph phases – builds the session bridge and injects
RALPH_MCP_ENDPOINT / RALPH_MCP_RUN_ID, so all three become real.
TIMEOUT SEAM. execute_agent_effect derives both watchdogs from
config.general inside _build_attempt_invoke_options; its **opts
surface carries no timeout keys. The per-attempt wall-clock share is
therefore applied by handing it a config whose
general.agent_max_session_seconds is overridden, which is the value
that function reads. That share is a slice of the integration-wide
resolution deadline, so it is normally an order of magnitude SMALLER
than the run-wide session defaults, and the
two values GeneralConfig orders against the ceiling –
agent_idle_timeout_seconds and agent_session_soft_wrapup_seconds
– are pulled down with it rather than left above it.
- ralph.pipeline.conflict_resolution.session.invoke_resolution_agent(*, agent_name, prompt_path, config, pipeline_deps, workspace_scope, policy_bundle, display, display_context, max_session_seconds)[source]¶
Run ONE resolution round through a real Ralph MCP session.
- Parameters:
agent_name (str) – Agent to invoke, from the resolution drain’s chain.
prompt_path (Path) – Rendered conflict-only prompt for this round.
config (UnifiedConfig) – Run configuration; the wall-clock share is applied to a copy of it.
pipeline_deps (PipelineDeps) – Pipeline dependency bundle.
workspace_scope (WorkspaceScope) – Workspace the merge is in progress in.
policy_bundle (PolicyBundle) – Resolved policy, supplying the drain’s capabilities.
display (ParallelDisplay | None) – Active display, when there is one.
display_context (DisplayContext | None) – Display context, when there is one.
max_session_seconds (float) – Wall-clock ceiling for THIS round.
- Returns:
a launch failure is this round’s failure, not the run’s, so the caller can fall back to the next chain candidate or the next round.
- Return type:
Whether the invocation reported success. Never raises
- ralph.pipeline.conflict_resolution.session.resolution_chain_agents(policy_bundle)[source]¶
Agents bound to the resolution drain, in fallback order.
Empty when the drain or its chain is missing, in which case the caller declines rather than guessing an agent.
- Parameters:
policy_bundle (PolicyBundle)
- Return type:
tuple[str, …]
- ralph.pipeline.conflict_resolution.session.with_session_ceiling(config, max_session_seconds)[source]¶
Return
configwith this attempt’s wall-clock ceiling applied.An earlier version DECLINED the override whenever the requested share sat below the run-wide idle watchdog or soft-wrapup threshold, on the grounds that an inconsistent ceiling is worse than the configured one. With the shipped defaults that is every share the integration ever asks for – 300 s against a 300 s idle watchdog and a 3,000 s wrapup – so the attempt silently kept the unrelated 3,300 s run-wide maximum and the integration ceiling bounded nothing at all. The dependent watchdogs are lowered alongside the ceiling instead, which preserves the ordering
GeneralConfigvalidates (soft_wrapup < max_sessionandmax_session >= idle) while making the share the value that actually cuts the session.- Parameters:
config (UnifiedConfig) – Run configuration to derive the bounded copy from.
max_session_seconds (float) – This attempt’s share of the pipeline deadline. A non-positive share cannot be expressed as a valid ceiling, so
configis returned unchanged.
- Returns:
A copy whose
generalsection is internally consistent and whose session maximum ismax_session_seconds.- Return type:
ralph.pipeline.conflict_resolution.driver¶
Drives the bounded conflict-resolution loop behind a deterministic gate.
Executes what ralph.pipeline.conflict_resolution.graph decides:
render a conflict-only prompt, run one agent round inside a real MCP
session, then RECOMPUTE the surviving conflict markers over exactly the
paths that were unmerged before the round. That recomputation is the
verdict. The agent’s own success claim is an input to it, never a
substitute for it – the same rule
ralph.project_policy.pipeline_driver applies to its analysis
agent.
The driver never aborts the surrounding run and never touches git state
itself: on exhaustion it returns False and
ralph.pipeline.auto_integrate_resolve.endpoint_merge_with_resolution()
performs the single abort-and-record it already owns. Duplicating the
abort here would double-report the same failure.
- type ralph.pipeline.conflict_resolution.driver.MonotonicClock = Callable[[], float]¶
Reads a monotonic wall clock. Injected so the whole-pipeline deadline can be proven without sleeping through it.
- type ralph.pipeline.conflict_resolution.driver.ResolutionInvoker = Callable[[str, Path, int], bool]¶
One resolution attempt:
(agent_name, prompt_path, round_index) -> ok. Injected so the default-suite tests never launch a process.
- ralph.pipeline.conflict_resolution.driver.resolution_deadline(config, clock=None)[source]¶
Absolute monotonic instant the whole resolution must finish by.
Public because a REBASE resolution spans several stops and every one of them must be bounded by ONE ceiling. A per-stop deadline would multiply the resolution timeout by
MAX_REBASE_CONFLICT_STOPS, turning a 15-minute budget into a 2.5-hour one. The caller takes this once before the loop and hands the same value to every stop.- Parameters:
config (UnifiedConfig)
clock (MonotonicClock | None)
- Return type:
float
- ralph.pipeline.conflict_resolution.driver.run_conflict_resolution_pipeline(*, root, target, config, pipeline_deps, workspace_scope, policy_bundle, display, display_context, invoke=None, clock=None)[source]¶
Resolve the in-progress merge’s conflicts, or decline.
- Parameters:
root (Path) – Repository root holding the in-progress merge.
target (str) – Mainline branch being merged in.
config (UnifiedConfig) – Run configuration, supplying the wall-clock ceiling.
pipeline_deps (PipelineDeps) – Pipeline dependency bundle for the agent session.
workspace_scope (WorkspaceScope) – Workspace scope for the agent session.
policy_bundle (PolicyBundle) – Resolved policy supplying the drain’s agent chain.
display (ParallelDisplay | None) – Active display, when there is one.
display_context (DisplayContext | None) – Display context, when there is one.
invoke (ResolutionInvoker | None) – Injected round runner; defaults to a real MCP-backed session.
clock (MonotonicClock | None) – Injected monotonic clock the whole-pipeline deadline is measured against; defaults to
time.monotonic().
- Returns:
Trueonly when every previously-conflicted path is marker-free. Never raises: any failure is reported asFalseso the caller aborts the merge and records a resolution failure.- Return type:
bool
- ralph.pipeline.conflict_resolution.driver.run_rebase_conflict_resolution_pipeline(*, root, target, stop, config, pipeline_deps, workspace_scope, policy_bundle, display, display_context, deadline=None, invoke=None, clock=None)[source]¶
Resolve ONE commit a rebase has stopped on, or decline.
Runs the same rounds, through the same MCP-backed session, under the same deterministic marker gate as
run_conflict_resolution_pipeline(). Only three things differ: the footer and prompt name the commit being replayed, the round cap isMAX_ROUNDS_PER_STOP, and the footer is NOT captured/restored here – the caller owns the footer for the whole multi-stop loop viaconflict_status_bar_session(), because capturing per stop would capture the previous stop’s own conflict bar and leave it pinned when the loop ends.- Parameters:
root (Path) – Repository root holding the paused rebase.
target (str) – Branch being rebased onto.
stop (RebaseStop) – The commit this call must resolve.
config (UnifiedConfig) – Run configuration, supplying the wall-clock ceiling.
pipeline_deps (PipelineDeps) – Pipeline dependency bundle for the agent session.
workspace_scope (WorkspaceScope) – Workspace scope for the agent session.
policy_bundle (PolicyBundle) – Resolved policy supplying the drain’s agent chain.
display (ParallelDisplay | None) – Active display, when there is one.
display_context (DisplayContext | None) – Display context, when there is one.
deadline (float | None) – Shared monotonic ceiling for the WHOLE rebase resolution, from
resolution_deadline(). Defaults to a fresh per-stop ceiling, which is correct only for a single-stop rebase.invoke (ResolutionInvoker | None) – Injected round runner; defaults to a real MCP-backed session.
clock (MonotonicClock | None) – Injected monotonic clock; defaults to
time.monotonic().
- Returns:
Trueonly when every path that conflicted on this stop is marker-free. Never raises.- Return type:
bool
ralph.pipeline.conflict_resolution.rebase_loop¶
Resolve a conflicted rebase in place, one stopped commit at a time.
A rebase is not a single conflict. It replays N commits and can stop on
a conflict at every one of them, so resolving it means a LOOP: resolve
the stop, prove the resolution, git rebase --continue, repeat. The
previous behaviour – abort on the first stop and fall back to one
endpoint three-way merge – meant the rebase-conflict resolution phase
was never reachable from a rebase at all, which is the defect this
module exists to close.
The resolver is injected as a plain callable rather than imported, for
the same reason
ralph.pipeline.auto_integrate_resolve.ConflictResolver is: it
keeps this module free of the agent session, the display and the policy
bundle, so the loop can be unit-tested with fakes and driven end to end
by a deterministic stub.
Division of labour, mirroring
ralph.pipeline.auto_integrate_resolve._stage_verify_and_commit()
exactly: the resolver only EDITS FILES. Ralph stages, Ralph re-scans for
surviving conflict markers, Ralph asks git whether any unmerged path
remains, and only Ralph continues the rebase. An agent running under
Ralph’s own MCP exec policy is denied every git invocation, so it could
not stage even if it tried.
- class ralph.pipeline.conflict_resolution.rebase_loop.RebaseStop(sha, subject, conflicted_files, stop_index, stop_cap, replay_index=None, replay_total=None)[source]¶
Bases:
objectOne commit a rebase has paused on because replaying it conflicted.
Carries exactly the context a resolution session is allowed to see: which commit is being replayed and which paths conflicted, plus two INDEPENDENT counters that are easy to confuse.
stop_index/stop_capare the bounded loop’s safety counters: how many stops this loop has spent out of the fixedMAX_REBASE_CONFLICT_STOPSit is allowed, which is what terminates the loop. They say nothing about how long the rebase is.replay_index/replay_totalare the operator-facing replay position: which of the rebase’s own commits is being replayed, read from git’s rebase state by_read_replay_progress(). They are display-only, bothNonewhen that state is unreadable, and must never influence loop termination.- Parameters:
sha (str)
subject (str)
conflicted_files (tuple[str, ...])
stop_index (int)
stop_cap (int)
replay_index (int | None)
replay_total (int | None)
- type ralph.pipeline.conflict_resolution.rebase_loop.RebaseStopResolver = Callable[[Path, str, RebaseStop], bool]¶
Resolves ONE rebase stop:
(root, target, stop) -> resolved. The implementation is expected to edit the conflicted files and nothing else; staging and continuing are Ralph’s job, never the resolver’s.Callableis imported at RUNTIME, not underTYPE_CHECKING: atypealias body is evaluated lazily, and sphinx autodoc forces that evaluation while building the API reference. The same is true ofralph.pipeline.conflict_resolution.driver.ResolutionInvoker.
- ralph.pipeline.conflict_resolution.rebase_loop.resolve_rebase_in_progress(root, target, resolver)[source]¶
Drive an in-progress rebase to completion through
resolver.- Parameters:
root (Path) – Repository root holding the paused rebase.
target (str) – Branch being rebased onto, passed through to the resolver as context and used as the completion check’s fallback when the replay’s own base cannot be read.
resolver (RebaseStopResolver) – Called once per stop to resolve that stop’s conflicts.
- Returns:
Trueonly when the rebase finished ANDHEADis a descendant of the commit the replay was landing on.Falsefor every other outcome, including a declining resolver, a surviving conflict marker, a refused continuation and an exhausted stop budget. Atargeta sibling moved during the resolution is NOT one of those outcomes – see_resolve_stops().- Return type:
bool
Never raises. The caller treats
Falseas “resolution declined” and falls through to the pre-existing abort-then-endpoint-merge path, so an unexpected failure here degrades to exactly the behaviour that shipped before this module existed.
ralph.pipeline.auto_integrate_conflict_budget¶
Bounded dev-agent budget for repeatedly unresolvable integration conflicts.
Auto-integration runs at four seams: after every commit, at every clean
phase boundary (eleven events), at the fan-out join and at run startup.
When the rebase conflicts, the endpoint merge conflicts and the
dev-agent conflict resolver cannot repair it, nothing about the
repository changes – so the very next seam repeats the identical
sequence, paying up to _MAX_RESOLVER_AGENTS full agent invocations
that share one 900-second resolution ceiling – shared per seam, but a
fresh ceiling every seam.
Nothing bounded that repetition and nothing escalated, so a single
unresolvable conflict made the run look stalled.
This module owns the bound and nothing else:
MAX_CONSECUTIVE_RESOLVER_ATTEMPTS– how many CONSECUTIVE unresolved conflicts against the same target may pay for an agent.ConflictIdentity– the observation pair that decides whether the next seam is looking at the SAME unresolved conflict.resolver_allowed()– the predicate the integration seam consults before handing the resolver down.apply_conflict_budget()– carries the counter forward onto the outcome, resets it on any successful land, and rewrites the recorded reason to name the exhausted budget and the blocked target when the agent was suppressed.
The budget only reduces how OFTEN the agent is invoked; it never widens
what the agent may do, and it never suppresses the endpoint merge. When
the budget is exhausted the seam passes conflict_resolver=None, so
the merge still runs and still aborts cleanly and the feature branch is
left bit-identical – the contract
tests/test_auto_integrate.py::test_both_conflicted_leaves_feature_bit_identical
pins for the no-resolver case.
The counter is CONSECUTIVE by design: any successful land resets it to
zero, so a transient conflict can never permanently consume the budget.
Run startup passes a fresh RebaseState, which deliberately
grants one clean attempt per run.
The counter is also scoped to ONE conflict, not merely to one target
branch NAME. The mainline this run integrates onto is shared: other
agents move its tip continuously, and the developer keeps adding
feature commits between seams. A budget keyed only on last_target
would therefore keep suppressing the resolver for a conflict that no
longer exists – new feature work against main would inherit the
exhausted budget of an older, unrelated conflict and never be offered
to the agent at all. ConflictIdentity closes that: the count
is carried forward only while the feature tip AND the target tip are
both unchanged, which is exactly the situation in which re-invoking
the agent would replay work it has already failed.
- class ralph.pipeline.auto_integrate_conflict_budget.ConflictIdentity(feature_sha=None, target_sha=None)[source]¶
Bases:
objectThe (feature tip, target tip) pair that identifies one conflict.
Observed by the integration seam immediately before it attempts the rebase, and recorded on
RebaseStatewhenever the attempt ends in an unresolved conflict. The next seam compares its own observation against the recorded one: equal means “the same conflict the agent already failed on”, so the carried count still applies; different means new work on either side, so the budget starts over.An unobservable side (
None–git rev-parsefailed, or a legacy checkpoint written before these fields existed) is treated as MATCHING. That direction is deliberate: it preserves the bound rather than handing an unbounded supply of 900 s agent invocations to a repository we cannot even read a SHA from.- Parameters:
feature_sha (str | None)
target_sha (str | None)
- matches(state)[source]¶
Whether
staterecorded a conflict with this same identity.- Parameters:
state (RebaseState)
- Return type:
bool
- ralph.pipeline.auto_integrate_conflict_budget.MAX_CONSECUTIVE_RESOLVER_ATTEMPTS = 2¶
Consecutive unresolved conflicts against the same target that may each pay for a dev-agent conflict resolution. The third and later attempts record an escalation instead of invoking the agent.
- ralph.pipeline.auto_integrate_conflict_budget.apply_conflict_budget(record, *, prior, target, resolver_suppressed, identity=ConflictIdentity(feature_sha=None, target_sha=None))[source]¶
Carry the conflict counter onto
recordand escalate when exhausted.A successful land resets the counter to zero and clears the recorded conflict identity.
A recorded conflict increments the count (from zero when
identityshows this is a different conflict) and stampsidentityonto the outcome so the next seam can tell whether it is looking at the same conflict.Any other outcome (a skip, for instance) carries the prior count AND the prior identity forward unchanged, so an unrelated skip neither consumes nor forgives the budget.
When
resolver_suppressedis True and the outcome is still a conflict, the recorded reason is rewritten to name the exhausted budget and the blocked target, which is what turns an apparent stall into an actionable operator message.- Parameters:
record (RebaseState)
prior (RebaseState)
target (str)
resolver_suppressed (bool)
identity (ConflictIdentity)
- Return type:
- ralph.pipeline.auto_integrate_conflict_budget.prior_conflict_count(state, target, identity=ConflictIdentity(feature_sha=None, target_sha=None))[source]¶
Consecutive unresolved conflicts carried in from the previous seam.
The count only applies to the SAME conflict, which takes two checks:
the SAME target – an integration that switches mainline (a reconfigured
auto_integrate_target, or a detected default that changed) is a different conflict; andthe SAME endpoints – a new feature commit, or a mainline tip another agent has since moved, is also a different conflict even though the branch name did not change.
Either difference starts with a full budget.
- Parameters:
state (RebaseState)
target (str)
identity (ConflictIdentity)
- Return type:
int
- ralph.pipeline.auto_integrate_conflict_budget.resolver_allowed(state, target, identity=ConflictIdentity(feature_sha=None, target_sha=None))[source]¶
Whether this attempt may invoke the dev-agent conflict resolver.
True for the first
MAX_CONSECUTIVE_RESOLVER_ATTEMPTSconsecutive unresolved conflicts against the sametargetAND the sameidentity. A freshRebaseStatealways yields True, so the first attempt of a run is never suppressed.- Parameters:
state (RebaseState)
target (str)
identity (ConflictIdentity)
- Return type:
bool
ralph.pipeline.auto_integrate_budget_seam¶
Git-observing seam between auto-integration and its conflict budget.
Separated from ralph.pipeline.auto_integrate so that module
stays under the repo-structure _MAX_FILE_LINES cap, and kept out of
ralph.pipeline.auto_integrate_conflict_budget so that module
stays a pure, trivially unit-testable policy: it decides the bound, it
never touches a repository. This module is the other half – the two
places where the bound needs to look at (or reason about) real git
state.
observe_conflict_identity()reads the endpoint pair that identifies the conflict an attempt is about to try to reconcile.carry_budget_through_skip()keeps that bound intact across an early skip, which short-circuits before the budget is consulted.charge_failed_attempt()keeps it intact across an unexpected failure, which short-circuits before the budget is applied.
- ralph.pipeline.auto_integrate_budget_seam.carry_budget_through_skip(skip, *, prior)[source]¶
Preserve the conflict budget across an early skip.
An early skip (dirty worktree, failed preconditions, …) short-circuits before the budget is consulted, so without this the freshly built skip record would carry the model defaults –
consecutive_conflicts=0and no identity – and REFUND the budget. A dirty worktree between two seams would then buy the resolver another 900 s attempt at a conflict it has already failed, which is the unbounded behaviour the budget exists to stop. A skip changes nothing about the repository, so it must neither consume nor forgive the budget.Skips with no resolved target (detached HEAD, AC-13) are returned untouched: there is no target to scope a count to.
- Parameters:
skip (RebaseState)
prior (RebaseState)
- Return type:
- ralph.pipeline.auto_integrate_budget_seam.charge_failed_attempt(skip, *, prior, target, identity, resolver_offered)[source]¶
Account for an integration attempt that died with an exception.
An unexpected failure is turned into a skip record built from the model defaults –
consecutive_conflicts=0and no identity – which REFUNDS the budget. The dev-agent conflict resolver runs inside the guarded region, so a resolver that raises would otherwise zero the count at every seam and the bound would never engage: exactly the unbounded 900 s-per-seam behaviour the budget exists to stop.When a resolver was actually handed down, the failure is therefore CHARGED as a consumed attempt – the seam paid for the agent and did not land, which is what the budget counts. With no resolver in play nothing was paid for, so the prior count is merely carried. Either way this is a bound on agent invocations only; the rebase and the endpoint merge still run at the next seam, and any successful land still resets the count to zero.
- Parameters:
skip (RebaseState)
prior (RebaseState)
target (str)
identity (ConflictIdentity)
resolver_offered (bool)
- Return type:
- ralph.pipeline.auto_integrate_budget_seam.observe_conflict_identity(root, target)[source]¶
Observe the endpoint pair that identifies one integration attempt.
Read once per seam, before any git mutation, and threaded through the conflict budget so a conflict is only ever counted against the exact (feature tip, target tip) pair that produced it. See
ConflictIdentityfor why an unreadable side is recorded asNonerather than treated as a new conflict.- Parameters:
root (Path)
target (str)
- Return type:
ralph.pipeline.auto_integrate_context¶
Pre-flight context helpers for auto-integration’s early skip table.
Separated from ralph.pipeline.auto_integrate so that module
stays under the repo-structure _MAX_FILE_LINES cap. Everything
here belongs to the phase BEFORE any git mutation: reading the branch
the integration would run on, and deciding what the origin refresh
performed in that same phase means for the record the seam returns.
The refresh helpers exist because the early skip paths (“nothing to integrate”, “already at the target”) are decided FROM the target pointer the refresh was supposed to freshen. A refresh that could not reach origin therefore makes those verdicts rest on a pointer of unknown age, and the fail-open contract means nothing else fails. The outcome has to travel with the record instead of being discarded, or an unreachable mainline is completely silent on exactly the paths that run most often.
- ralph.pipeline.auto_integrate_context.current_branch_or_detached_marker(root)[source]¶
Return the current branch name or
Noneif HEAD is detached.Detaches the typed-exception guard from
get_current_branch()’s broad fallback so the auto-integrate skip table can record a DETACHED-HEAD outcome (AC-02/AC-13 skip condition: “no branch to integrate”). GitPython raisesTypeErrorfromrepo.active_branch.namewhen HEAD points at a detached SHA. Any other exception – not a git repo, transport error, etc. – propagates so the caller can surface the actual failure.- Returns:
The current branch name, or
Nonewhen HEAD is detached.- Parameters:
root (Path)
- Return type:
str | None
- ralph.pipeline.auto_integrate_context.record_refresh(record, refresh)[source]¶
Stamp the refresh outcome onto a record built before it was known.
A no-op when
refreshisNone(no refresh ran) so the disabled-feature and no-target paths stay byte-identical to what they recorded before the refresh became observable.- Parameters:
record (RebaseState)
refresh (str | None)
- Return type:
- ralph.pipeline.auto_integrate_context.record_when_stale(record, refresh)[source]¶
Return
recordstamped withrefresh, orNoneif healthy.The phase-boundary hook is deliberately quiet: it records nothing when there is nothing to integrate. That silence is only defensible while the target pointer the verdict was read through is trustworthy, so this is the one place that decides between staying quiet and speaking up. The caller builds
recordeagerly because constructing one frozen model is cheaper than threading a factory.- Parameters:
record (RebaseState)
refresh (str | None)
- Return type:
RebaseState | None
- ralph.pipeline.auto_integrate_context.refresh_outcome_is_healthy(refresh)[source]¶
Whether
refreshleaves the target pointer trustworthy.Noneis NOT healthy. It means no refresh happened on this path, so nothing vouched for the pointer the decision was read through – the same epistemic position as a refresh that failed, and the opposite ofREFRESH_DISABLED, where the local ref IS still re-observed and only the network fetch is skipped. TreatingNoneas healthy is what made a throttle-suppressed cross-agent catch-up indistinguishable from a verified one.- Parameters:
refresh (str | None)
- Return type:
bool
ralph.pipeline.auto_integrate_catchup¶
Background catch-up fast-forward worker for auto-integration.
Between integration seams the shared target branch keeps moving (other
fleet agents land on it), while this agent’s checkout only catches up
at the next clean seam. When the checkout has NO commits of its own the
catch-up is a pure git merge --ff-only – no rebase, no conflict
resolution, no agent tokens. Waiting for the next seam to perform that
free move lets divergence accumulate, and divergence is exactly what
later costs a conflict-resolution agent invocation.
AutoIntegrateCatchupWorker closes that gap: a bounded daemon
thread wakes every DEFAULT_CATCHUP_INTERVAL_SECONDS seconds and
attempts one attempt_catchup_fast_forward() tick. A tick mutates
the repository ONLY when every one of these holds:
auto-integration is enabled and a target branch resolves (same refs-only precedence as
resolve_integration_target());HEAD is on a branch (a detached HEAD means a rebase or similar is in flight – never interfere) and that branch is NOT the target;
the current branch tip is a strict ancestor of the target tip, i.e. the move is a genuine fast-forward;
the worktree has no uncommitted tracked changes (same tracked-only definition of clean as the boundary seams).
QUIET-PROBE CONTRACT. The tick fires every 30 seconds for the whole
run, so its skip paths must be invisible: no display lines, no log
lines at ANY level, and no run_git()
subprocesses – every ProcessManager spawn writes RUNNING/EXITED
lifecycle lines to the log, which turned the early run_git-based probes
into a 30-second drumbeat of log noise. All read-only observation
therefore happens IN-PROCESS via GitPython (the
ralph.git.operations precedent): branch name, target resolution,
and both tip SHAs are plain ref-file reads that spawn nothing. The
ancestry and cleanliness checks go through GitPython’s own git wrapper
(not ProcessManager), and only run once the SHAs prove a fast-forward
is even possible. The single ProcessManager subprocess left is the
git merge --ff-only that actually lands – an event worth its log
line. merge --ff-only refuses every non-fast-forward and every
file overwrite non-destructively, and _still_safe_to_merge()
re-verifies branch identity and no-rebase-in-flight immediately before
the spawn – see that function’s docstring for the bounded residual
window this design accepts.
Threading contract (mirrors ralph.pro_support.heartbeat.ProHeartbeatClient):
the worker runs in a daemon thread so the process can always exit;
start() is idempotent; stop() only sets a threading.Event
and never joins, so shutdown can never block on a slow git subprocess.
The thread parks in Event.wait between ticks and every git touch is
ref-file I/O or a subprocess wait, all of which release the GIL – the
worker cannot stall the pipeline thread. The run loop starts the worker
at pipeline start (start_catchup_worker_if_enabled()) and stops
it in the run-loop cleanup path alongside the other background
collaborators.
- class ralph.pipeline.auto_integrate_catchup.AutoIntegrateCatchupWorker(config, root, *, interval_seconds=30.0, tick=None, wait=None)[source]¶
Bases:
objectBounded daemon-threaded catch-up loop over
attempt_catchup_fast_forward().Constructor parameters are explicit so tests can inject a recording
tickcallable and deterministicwaitfunction instead of waiting on a real clock. No I/O happens at construction time;start()launches the daemon thread andstop()is an idempotent event-set that never joins (the thread is daemonic, and joining could block shutdown on a slow git subprocess).- Parameters:
config (UnifiedConfig)
root (Path)
interval_seconds (float)
tick (Callable[[], str] | None)
wait (Callable[[float], bool] | None)
- ralph.pipeline.auto_integrate_catchup.CATCHUP_DISABLED = 'disabled'¶
Tick outcome tags. Exactly one is returned per
attempt_catchup_fast_forward()call; onlyCATCHUP_FAST_FORWARDEDmeans the repository changed.
- ralph.pipeline.auto_integrate_catchup.DEFAULT_CATCHUP_INTERVAL_SECONDS: float = 30.0¶
Cadence of the background catch-up probe. Each tick costs a handful of in-process ref reads (no subprocess, no log lines), so a 30-second cadence is negligible next to the conflict-resolution tokens an accumulated divergence costs later.
- ralph.pipeline.auto_integrate_catchup.attempt_catchup_fast_forward(config, root)[source]¶
One catch-up probe: fast-forward the checkout onto the target if free.
Stateless – every gate is re-derived from config and live git state, matching the seam contract that integration decisions are never cached across observations. Returns one of the
CATCHUP_*outcome tags; the repository is mutated only onCATCHUP_FAST_FORWARDED.Gate order is cheapest-first under the quiet-probe contract: the config read and every ref observation are in-process and free, so the two checks that shell out through GitPython (ancestry, then cleanliness) run only once the SHAs prove the checkout is strictly behind the target – the steady states (up to date, diverged-with- own-commits, on the target) never spawn anything at all.
The final
merge --ff-onlyis handed the observed target SHA, not the ref name, so a target that advances between the ancestry check and the merge can at worst land the slightly older (still-ancestor) tip – never a non-fast-forward. If the CHECKOUT moved in between (an agent committed),merge --ff-onlyagainst a now-non-descendant SHA refuses without mutating anything and the tick reportsCATCHUP_REFUSED.- Parameters:
config (UnifiedConfig)
root (Path)
- Return type:
str
- ralph.pipeline.auto_integrate_catchup.resolve_integration_target(config, root)[source]¶
Return the configured target only when it is a local branch.
- Parameters:
config (UnifiedConfig)
root (Path)
- Return type:
str | None
- ralph.pipeline.auto_integrate_catchup.start_catchup_worker_if_enabled(config, root, *, interval_seconds=30.0)[source]¶
Construct and start the catch-up worker when auto-integration is on.
Returns
None(and spawns nothing) whenconfig.general.auto_integrate_enabledis not a true boolean – the same defensive read every seam uses – so a disabled run never pays the background git probes at all. The caller owns the returned worker and must callstop()on every exit path.- Parameters:
config (UnifiedConfig)
root (Path)
interval_seconds (float)
- Return type:
AutoIntegrateCatchupWorker | None
ralph.pipeline.auto_integrate_ff¶
Fast-forward helpers for ralph.pipeline.auto_integrate.
Houses the worktree-aware / atomic-CAS fast-forward path so the
main ralph.pipeline.auto_integrate module stays under the
repo-structure _MAX_FILE_LINES cap. The helpers here are
a coherent unit (the AC-08 / AC-09 / AC-10 fast-forward branch table)
and have no callers outside ralph.pipeline.auto_integrate.
When the target branch is checked out somewhere, the ONLY accepted
landing path is git merge --ff-only in that worktree: merge
--ff-only advances the ref, the index and the working tree together
and exits non-zero WITHOUT mutating anything when local changes would
be overwritten. There is no CAS fallback when the target has a live
checkout – AC-10/E2 forbids bare update-ref on a checked-out
target because it silently desyncs that checkout’s index and working
tree (its git status would describe the freshly landed work as a
local reverse diff, and a reset --hard there would destroy work).
When merge --ff-only refuses (the worktree is dirty, the requested
SHA is not a fast-forward, or git refuses for any other reason), the
shared target ref is LEFT UNTOUCHED and the attempt returns a loud,
retryable diagnostic: the next clean seam on that worktree will
re-attempt the same merge --ff-only and land once the operator’s
uncommitted work is committed or stashed. A dirty sibling worktree
must NOT desynchronise the fleet – it must self-resume on the next
seam. The previous behaviour (CAS-advance the shared ref even when
the target had a live dirty checkout) violated AC-10/E2: it advanced
the shared ref while leaving that checkout’s index and working tree
behind, and the test
test_dirty_target_worktree_advances_ref_without_touching_files
that blessed it is the exact shape AC-10 forbids.
When the target is NOT checked out anywhere, _fast_forward_via_cas
is the only path: the CAS is conditioned on the exact SHA the
function observed, so a sibling that landed in between wins and the
loser never force-writes (AC-08). merge --ff-only cannot be used
when no worktree holds the target because it would have to operate in
the integration worktree, which sits on a different branch.
- ralph.pipeline.auto_integrate_ff.fast_forward_target(repo_root, target, feature_sha)[source]¶
Move the local mainline ref to
feature_shavia CAS or worktree ff.Returns
(fast_forwarded, skip_reason).skip_reasonis empty when the fast-forward succeeded.AC-08 atomicity contract: the ancestry decision is BOUND to the same observed target SHA the CAS uses, and that pair is observed ONCE, as late as possible – inside
_fast_forward_via_cas(), immediately before the ref move. Earlier implementations checkedis_ancestor(target, feature_sha)before reading the SHA, leaving a TOCTOU window where a concurrent landing between the ancestor check and the SHA read could satisfy the CAS with a SHA that was NOT an ancestor offeature_sha– the bug class closed by this design. If the target moves between that observation and the CAS, the CAS fails closed (the ref no longer equals the observed SHA) and a concurrency skip is recorded.A cheap pre-flight GATE runs before any of that – see
_landing_gate_reason(). It is a gate and nothing more: the SHA it reads is deliberately not carried forward, because the worktree query and themerge --ff-onlyattempt that may follow it are themselves git subprocesses, and a fleet sibling can move the ref across them. Earlier revisions of this function called the CAS’s own observer here and dropped its SHA on the floor with a_, which read as an observation that bound something when it bound nothing.The worktree path always tries
git merge --ff-onlyfirst so the checkout’s ref, index and working tree advance together, and falls back to that single-observation CAS only when git refuses. Neither path can lose a concurrent landing or overwrite the worktree’s uncommitted FILE content; the CAS fallback does advance the shared ref under a dirty target checkout, which is intentional and announced – see this module’s docstring.A FAILED worktree query is not “nobody holds the target”. In this repository’s own linked-worktree topology the mainline genuinely is checked out in a sibling worktree, and CAS-ing the shared ref there advances
refs/heads/<target>while leaving that checkout’s index and working tree behind. So a failed query fails closed with a RETRYABLE reason and the bounded retry loop inralph.pipeline.auto_integratere-attempts, rather than desynchronising a live checkout it could not see.Never force-moves the target. The skip reasons are recorded in the final
RebaseState.last_reason.- Parameters:
repo_root (Path)
target (str)
feature_sha (str)
- Return type:
tuple[bool, str]
- ralph.pipeline.auto_integrate_ff.is_retryable_fast_forward_failure(reason)[source]¶
Whether a failed fast-forward reflects a transient target move.
- Parameters:
reason (str)
- Return type:
bool
- ralph.pipeline.auto_integrate_ff.maybe_push_target(config, repo_root, target, record)[source]¶
Publish a successful local landing only when remote sync is enabled.
- Parameters:
config (UnifiedConfig | None)
repo_root (Path)
target (str)
record (RebaseState)
- Return type:
ralph.pipeline.auto_integrate_outcome¶
Outcome classification for ralph.pipeline.auto_integrate.
Owns the single branch table that maps a rebase result plus an optional
endpoint-merge result onto the (action, reason) pair the runner
renders in its auto-integrate: line, and the thin
RebaseState constructors built on
top of it.
Extracted from ralph.pipeline.auto_integrate to keep that module
under the repo-structure _MAX_FILE_LINES cap, the same way the
record helpers were extracted to
ralph.pipeline.auto_integrate_record. The classifiers are a
coherent unit – one decision table, no git I/O, no callers outside the
auto-integration pipeline – so the split follows a real seam rather
than a line count.
Every function here is pure: given the engine results it returns a value and touches neither the filesystem nor a subprocess.
- ralph.pipeline.auto_integrate_outcome.ACTION_SKIPPED = 'skipped'¶
Outcome verbs recorded on
RebaseState.last_actionso the runner can format the user-facing log line (rebased / merged / skipped / conflict / recovered). The landing result is recorded on the separatefast_forwardedboolean, not as an action verb.
- ralph.pipeline.auto_integrate_outcome.classify_merge_only_outcome(merge_outcome)[source]¶
Classify an attempt that went straight to the endpoint merge.
Used by the bounded retry after a merge-producing attempt, where
rebase_ontois deliberately skipped so the previous attempt’s merge commit survives. No rebase ran, so none of the rebase-flavoured headlines apply.- Parameters:
merge_outcome (MergeResult | None)
- Return type:
tuple[str, str | None]
- ralph.pipeline.auto_integrate_outcome.classify_rebase_outcome(*, rebase_outcome, merge_attempted, merge_outcome)[source]¶
Map a rebase + optional merge result to a
(action, reason)pair.Split out from
record_rebase_outcome()to keep the headline builder within the ruff PLR0911 return-statement cap (the builder is a single-return constructor; this classifier owns the branch table).- Parameters:
rebase_outcome (RebaseSuccess | RebaseConflicts | RebaseNoOp | RebaseFailed)
merge_attempted (bool)
merge_outcome (MergeResult | None)
- Return type:
tuple[str, str | None]
- ralph.pipeline.auto_integrate_outcome.record_conflict(*, reason, target)[source]¶
Build a
RebaseStaterecording a conflict outcome (AC-07).- Parameters:
reason (str)
target (str | None)
- Return type:
- ralph.pipeline.auto_integrate_outcome.record_rebase_outcome(*, rebase_outcome, merge_attempted, merge_outcome, target)[source]¶
Build a
RebaseStatefrom the rebase + optional merge result.Success path ->
last_action='rebased'or'merged'andfast_forwardedis set by the caller after the fast-forward phase; the function leavesfast_forwarded=Falsehere so the caller can update it once the CAS / worktree-ff step completes.The mapping is centralized in
classify_rebase_outcome()so this function is a thin constructor over the (action, reason) pair it returns.- Parameters:
rebase_outcome (RebaseSuccess | RebaseConflicts | RebaseNoOp | RebaseFailed)
merge_attempted (bool)
merge_outcome (MergeResult | None)
target (str)
- Return type:
ralph.pipeline.auto_integrate_rebase_merge¶
Rebase-then-endpoint-merge engine driver for auto-integration.
Extracted from ralph.pipeline.auto_integrate so that module stays
under the repo-structure _MAX_FILE_LINES cap, the same reason the
durable record helpers moved to
ralph.pipeline.auto_integrate_record and the outcome branch table
to ralph.pipeline.auto_integrate_outcome.
Everything here belongs to one phase of an integration attempt: run the
rebase engine; when it conflicts, resolve it IN PLACE stop by stop –
Ralph stages each proved-resolved stop and runs git rebase
--continue, leaving linear history and no merge commit – and only
when that resolution declines, exhausts a budget, errors or is
unavailable abort the rebase cleanly and fall back to a single endpoint
three-way merge (itself optionally agent-resolved). The caller owns the
phases either side of it – the durable crash record, the fast-forward,
and the retry loop.
- ralph.pipeline.auto_integrate_rebase_merge.REBASE_SKIPPED_FOR_MERGE = 'rebase skipped on retry to preserve the integration merge commit'¶
RebaseNoOpreason recorded when the bounded retry deliberately skipsrebase_ontoto preserve the previous attempt’s merge commit.
- class ralph.pipeline.auto_integrate_rebase_merge.RebaseRunResult(rebase_outcome, merge_attempted, merge_outcome, short_circuit)[source]¶
Bases:
objectOutcome of
run_rebase_or_merge().On the happy path
short_circuitisNoneandrebase_outcomecarries the engine result so the caller can build the finalRebaseState. On a conflict / failure that has already been cleared from the durable record,short_circuitcarries theRebaseStatethe caller should return directly.- Parameters:
rebase_outcome (RebaseSuccess | RebaseConflicts | RebaseNoOp | RebaseFailed)
merge_attempted (bool)
merge_outcome (MergeResult | None)
short_circuit (RebaseState | None)
- ralph.pipeline.auto_integrate_rebase_merge.run_rebase_or_merge(root, target, conflict_resolver, *, prefer_merge=False, rebase_stop_resolver=None, display=None)[source]¶
Drive rebase_onto, resolving conflicts in place before any fallback.
On success returns a
RebaseRunResultwithshort_circuitNoneand therebase_outcome/merge_outcomethe caller uses to build the finalRebaseState. A rebase that FAILS, and a conflicted rebase whose in-place resolution does not land, both fall back to the endpoint merge — a rebase that fails for any reason must never end the integration attempt while a single three-way merge could still land it. When aborting a conflicted rebase leaves it in progress, the durable crash record is retained so startup recovery can restore the repository.With
prefer_mergethe rebase is skipped entirely and the endpoint merge runs directly. That flag is set ONLY by the retry loop, and only after an attempt that produced a merge commit: a plaingit rebase(no--rebase-merges) would drop that merge and replay the raw feature commits back into the conflict the resolver just settled.Before
rebase_ontois called the function also detects ranges a plain rebase would mishandle (B3 merge commits, B7 root commits, B4/B6 all-empty replays) and routes them straight to the endpoint merge so the rebase engine never silently flattens a merge resolution, replays an already-upstream patch into a conflict, or drops a root commit. The rebase--empty=dropflag from step 2 still lands the all-empty case on modern git; this routing is the backstop for older git that ignores the flag.With a
rebase_stop_resolvera CONFLICTED rebase is resolved in place – commit by commit, throughgit rebase --continue– before the abort-and-endpoint-merge fallback is considered at all.displayis used only to own the resolution footer for the whole loop; it is optional and never required for correctness.- Parameters:
root (Path)
target (str)
conflict_resolver (ConflictResolver | None)
prefer_merge (bool)
rebase_stop_resolver (RebaseStopResolver | None)
display (ParallelDisplay | None)
- Return type:
ralph.pipeline.auto_integrate_record¶
Durable crash-record model + I/O for ralph.pipeline.auto_integrate.
Houses the IntegrationRecord pydantic model and the
atomically-written record I/O helpers so the main
ralph.pipeline.auto_integrate module stays under the
repo-structure _MAX_FILE_LINES cap. The four I/O helpers
(record_path, write_record, read_record,
clear_record) and the record model form a coherent unit –
the phased crash-record file lifecycle – and have no callers
outside ralph.pipeline.auto_integrate.
- ralph.pipeline.auto_integrate_record.IntegrationPhase¶
Phases the auto-integrate recovery preamble understands.
integratingwhile the rebase/merge is in flight;integratedonce the feature branch fully contains the target and only the fast-forward remains. Any other on-disk value is treated as corrupt byread_record()– a malformed record must never be acted on as if it were a known phase.alias of
Literal[‘integrating’, ‘integrated’]
- class ralph.pipeline.auto_integrate_record.IntegrationRecord(*, phase, target, pre_feature_sha, pre_target_sha, integrated_feature_sha=None, resolving_rebase=False, operation_kind='feature_integrate', owning_worktree=None)[source]¶
Bases:
BaseModelDurable phased record of an in-progress auto-integration.
Persisted to
<workspace_scope.root>/.agent/auto_integrate_in_progress.jsonviawrite_record()(atomic temp +os.replace) so a SIGKILL mid-write leaves the previous record intact and a recovery preamble on resume can decide whether to land the fast-forward (phase=’integrated’) or restore the feature branch to its pre-integration state (phase=’integrating’).- Parameters:
phase (Literal['integrating', 'integrated'])
target (str)
pre_feature_sha (str)
pre_target_sha (str | None)
integrated_feature_sha (str | None)
resolving_rebase (bool)
operation_kind (Literal['feature_integrate', 'target_reconcile'])
owning_worktree (str | None)
- phase¶
'integrating'while the rebase/merge is in flight;'integrated'once the feature branch fully contains the target and only the fast-forward remains. Restricted to those two values by theIntegrationPhaseLiteral so an on-disk record carrying a stray value is rejected byread_record()as corrupt instead of being acted on.- Type:
- target¶
The integration target branch name.
- Type:
str
- pre_feature_sha¶
The feature branch HEAD SHA captured BEFORE any rebase/merge; used to restore on a crash that interrupts the rebase.
- Type:
str
- pre_target_sha¶
The target branch SHA captured BEFORE the fast-forward attempt; the observed
<oldvalue>for the atomic compare-and-swap.- Type:
str | None
- integrated_feature_sha¶
The feature branch HEAD SHA captured AFTER the rebase/merge succeeded. Present only when phase=’integrated’.
- Type:
str | None
- resolving_rebase¶
True while a rebase-conflict resolution agent is working inside a PAUSED rebase. Recovery aborts such a rebase exactly as it aborts any other – an orphaned rebase cannot be resumed by a process that has no agent session for it – but the flag lets recovery say which of the two it found, so an operator can tell an interrupted resolution from an ordinary crashed rebase without reading the code.
- Type:
bool
- model_config = {'frozen': True}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- ralph.pipeline.auto_integrate_record.clear_record(workspace_root)[source]¶
Unlink the durable record; missing-ok.
- Parameters:
workspace_root (Path)
- Return type:
None
- ralph.pipeline.auto_integrate_record.read_record(workspace_root)[source]¶
Return the durable record or
Nonewhen absent / corrupt.A corrupt record is treated as absent so a partial write from a crashed prior run never wedges the recovery preamble. Corrupt here means: missing file, unreadable file, invalid JSON, non-object payload, schema mismatch, OR an on-disk
phaseoutside theIntegrationPhaseLiteral (e.g. a stray value left behind by an older partially-applied write). A record with a stray phase must never be acted on as if it were a known phase – the recovery path would otherwise run theintegratedfast-forward continuation on a record that is neither integrating nor integrated.- Parameters:
workspace_root (Path)
- Return type:
IntegrationRecord | None
- ralph.pipeline.auto_integrate_record.record_path(workspace_root)[source]¶
Return the durable crash-record path anchored to
workspace_root.- Parameters:
workspace_root (Path)
- Return type:
Path
- ralph.pipeline.auto_integrate_record.set_resolving_rebase(workspace_root, resolving)[source]¶
Flag (or unflag) the durable record as an in-flight rebase resolution.
Called around the resolve-and-continue loop so a run killed while an agent was editing a paused rebase leaves evidence of WHY the rebase was paused.
- Returns:
Whether the durable state on disk now says
resolving. That includes the two no-op cases: a record already carrying the wanted value, and NO RECORD FILE at all – with nothing on disk, recovery has nothing to act on either, so no interrupted- resolution warning is lost by proceeding.Falseis returned whenever the durable state was left disagreeing with the caller: a failed write, and also a record file that EXISTS but could not be read back.read_record()deliberately collapses absent, corrupt and transiently-unreadable into a singleNone, so the file’s existence is what separates “nothing to record” from “something is there and we could not see it”; the latter must not be reported as a persisted flag.- Parameters:
workspace_root (Path)
resolving (bool)
- Return type:
bool
Never raises. Deciding what an unpersistable flag means belongs to the caller:
ralph.pipeline.auto_integrate_rebase_mergerefuses to start a resolution it could not record and takes the safe abort-then-endpoint-merge path instead.
- ralph.pipeline.auto_integrate_record.write_record(workspace_root, record)[source]¶
Atomically write
recordto the durable record path.The atomic-write pattern (temp file in the same directory +
os.replace) mirrors the existingralph.git.operations._atomic_append_textcontract: a crash mid-write never leaves a half-written record on disk. Ifos.replacefails, the staging file is removed before re-raising.- Parameters:
workspace_root (Path)
record (IntegrationRecord)
- Return type:
None
ralph.pipeline.auto_integrate_resolve¶
Agent-assisted conflict resolution for the auto-integrate endpoint merge.
When the endpoint merge of the mainline into the feature branch
conflicts, the pipeline can hand the conflicted working tree to a
conflict resolver — in production a focused dev-agent invocation whose
ONLY job is to rewrite the conflicted files in place. The resolver
never runs a git command: Ralph stages the previously-conflicted paths
itself and verifies the result, because an agent running under Ralph’s
own MCP exec policy is denied every git invocation. The merge commit
itself is always created deterministically by
ralph.git.merge.commit_merge_in_progress(), never by the agent,
so the integration flow (fast-forward of the mainline, crash records,
state receipts) stays byte-deterministic around the agent call.
Fault-tolerance contract:
The resolver callable may fail or raise; both are contained here and reported as
MergeResult(outcome='resolution_failed')after the in-progress merge is aborted, leaving the feature branch bit-identical to its pre-merge state.A merge that conflicts without leaving
MERGE_HEAD(refused pre-start) is returned as a plain conflict — there is nothing for a resolver to repair. That verdict is read throughralph.git.merge.merge_state(), never its boolean projection: when the git query itself fails the merge state is UNKNOWN, which is not evidence of a clean tree, so the abort is attempted rather than assumed unnecessary.A resolution that leaves a conflict marker in any previously conflicted file is REFUSED.
git addon a marker-bearing file silently clears its unmerged state, so the git-authoritativeunmerged_pathscheck alone cannot prove a real resolution.This module never raises.
- ralph.pipeline.auto_integrate_resolve.ConflictResolver¶
(repo_root, target_branch) -> resolved. Returning True means every conflict marker was rewritten on disk; Ralph then stages, verifies and commits the merge.- Type:
Signature of a conflict resolver
alias of
Callable[[Path,str],bool]
- ralph.pipeline.auto_integrate_resolve.RESOLUTION_FAILED = 'resolution_failed'¶
MergeResult outcome recorded when a resolver was given the conflict and could not (or did not) fully resolve it. Distinct from plain
'conflict'so the operator-facing reason names the failed resolution attempt.
- ralph.pipeline.auto_integrate_resolve.endpoint_merge_with_resolution(root, target, resolver)[source]¶
Attempt the endpoint merge; on conflict, optionally resolve it.
Returns the final
MergeResultorNonewhen the merge attempt itself raised (the caller records the exception headline). With a resolver, a conflicted merge is left in progress, the resolver runs, and on full resolution (no unmerged paths) the merge is committed deterministically; any resolution failure aborts the merge and reportsRESOLUTION_FAILED.- Parameters:
root (Path)
target (str)
resolver (Callable[[Path, str], bool] | None)
- Return type:
MergeResult | None
ralph.pipeline.auto_integrate_agent¶
Builds the conflict resolver handed to the auto-integrate step.
This module is now a thin adapter. It builds the production
ConflictResolver – the
(repo_root, target) -> bool callable
endpoint_merge_with_resolution()
consumes – and delegates the actual work to
ralph.pipeline.conflict_resolution.run_conflict_resolution_pipeline(),
the out-of-graph pipeline that owns prompting, status, session and the
bounded retry loop.
The delegation exists because the previous implementation invoked the
agent OUTSIDE Ralph’s MCP session: it called invoke_agent directly
with no RALPH_MCP_ENDPOINT, so there was no Ralph tool surface, no
declare_complete contract and – critically – no exec policy. The
claim that “an agent running under Ralph’s own MCP exec policy is denied
every git invocation” was therefore aspirational rather than true. It is
true now: the pipeline runs every round through
effect_executor.execute_agent_effect, which builds the session
bridge, so ralph.mcp.tools.exec.apply_exec_policy actually binds and
the agent cannot commit, abort or move a ref. Ralph alone stages the
previously-conflicted paths and then advances the integration itself:
for a REBASE stop it runs git rebase --continue, replaying the
commit and leaving linear history; only the endpoint-merge mode
finishes by creating a merge commit.
Fault-tolerance contract, unchanged: every failure mode returns False
so the integration step aborts the operation in progress – the rebase
for a declined stop, the merge for a declined endpoint merge – and
records a conflict instead of crashing the run. A MISSING dependency
(no pipeline_deps, no
workspace_scope) is one of those failures – it declines rather than
falling back to an MCP-less invocation, because that fallback is the
defect this module exists to remove.
- ralph.pipeline.auto_integrate_agent.build_agent_conflict_resolver(*, policy_bundle, registry, display, config, pipeline_deps=None, workspace_scope=None, display_context=None)[source]¶
Build the pipeline-backed conflict resolver for the integration step.
The returned callable matches
ConflictResolver: it is invoked with(repo_root, target_branch)while the conflicted merge is in progress and returnsTrueonly when Ralph’s own deterministic marker scan found the conflicts resolved.- Parameters:
policy_bundle (PolicyBundle) – Resolved policy supplying the resolution drain.
registry (_SupportsAgentLookup) – Agent registry. Used to decline BEFORE a session is started when no agent of the resolution chain is actually installed in this workspace.
display (ParallelDisplay) – Active display; the pipeline pushes its own status bar through it and restores the previous one on exit.
config (UnifiedConfig) – Run configuration, supplying the wall-clock ceiling.
pipeline_deps (PipelineDeps | None) – Pipeline dependencies. Required: without them no MCP session can be built.
workspace_scope (WorkspaceScope | None) – Workspace scope. Required, for the same reason.
display_context (DisplayContext | None) – Display context, when there is one.
- Returns:
The resolver callable. It never raises.
- Return type:
- ralph.pipeline.auto_integrate_agent.build_agent_rebase_stop_resolver(*, policy_bundle, registry, display, config, pipeline_deps=None, workspace_scope=None, display_context=None)[source]¶
Build the resolver that resolves ONE stop of a conflicted rebase.
The rebase counterpart of
build_agent_conflict_resolver(), and deliberately a SEPARATE builder rather than a widening ofConflictResolver: a rebase stop carries context a merge conflict does not have (which commit is being replayed, how far through the replay the loop is), and every existing caller of the merge resolver keeps its two-argument signature untouched.- Parameters:
policy_bundle (PolicyBundle) – Resolved policy supplying the resolution drain.
registry (_SupportsAgentLookup) – Agent registry, used to decline before a session starts when no agent of the resolution chain is installed.
display (ParallelDisplay) – Active display; the caller owns the footer across the whole loop, this resolver only pushes per-stop updates.
config (UnifiedConfig) – Run configuration, supplying the wall-clock ceiling.
pipeline_deps (PipelineDeps | None) – Pipeline dependencies. Required for an MCP session.
workspace_scope (WorkspaceScope | None) – Workspace scope. Required, for the same reason.
display_context (DisplayContext | None) – Display context, when there is one.
- Returns:
The resolver callable. It never raises.
- Return type:
- ralph.pipeline.auto_integrate_agent.emit_integration_warn_line(display, message)[source]¶
Put a decline reason where the OPERATOR can see it, not just the log.
Every branch below already logs, and the log file is exactly where the reason used to die: a run whose auto-integration silently stopped resolving conflicts looks, from the transcript, like a feature that does not work. One line on the transcript is the difference between “auto rebase is broken” and “no resolution agent is installed”.
Duck-typed and defensive, exactly as
ralph.pipeline.conflict_resolution.status.emit_conflict_phase_line()is: a display exposing noemit_warn_lineis a silent no-op, and a display that raises is swallowed. Presentation must never be able to raise into the integration step.- Parameters:
display (object)
message (str)
- Return type:
None
ralph.pipeline.auto_integrate_recovery¶
Startup crash recovery for interrupted auto-integrations (AC-11).
Extracted from ralph.pipeline.auto_integrate to keep that module
under the repo-structure _MAX_FILE_LINES cap. The public entry
point recover_incomplete_integration() is re-exported by
ralph.pipeline.auto_integrate so existing imports and monkeypatch
targets keep working unchanged.
- exception ralph.pipeline.auto_integrate_recovery.TerminalStateViolationError[source]¶
Bases:
RuntimeErrorRaised when an integration leaves git in a non-terminal state.
- ralph.pipeline.auto_integrate_recovery.post_attempt_verify(root, *, expected_head_sha, owns_resolution)[source]¶
Raise when rebase markers remain or HEAD differs from the expected SHA.
- Parameters:
root (Path)
expected_head_sha (str | None)
owns_resolution (bool)
- Return type:
None
- ralph.pipeline.auto_integrate_recovery.recover_incomplete_integration(workspace_scope, *, config=None)[source]¶
Recover from an interrupted integration at run-loop startup.
Behavior (AC-11):
No durable record → no-op (never disturb an operator’s own git operation).
Abort any owned engine rebase/merge in flight before doing anything else. If the abort itself FAILS, the durable record is RETAINED so the next startup can retry; the returned
RebaseStaterecords the abort failure as a skip with the original target preserved. The merge check is read viaralph.git.merge.merge_state(), so a FAILED git query counts as a possible in-flight merge (abort attempted, record retained unless a readable state later provesMERGE_HEADabsent) rather than as “nothing to abort”.If
record.phase == 'integrating': the rebase/merge never completed → restore the feature branch topre_feature_shaviareset_hard. The record is cleared ONLY afterreset_hardsucceeds AND no owned engine op remains; if reset_hard raises or any prior abort left an in-progress operation, the record is RETAINED for retry.If
record.phase == 'integrated': the rebase/merge already completed and only the fast-forward may be unfinished → safely CONTINUE the fast-forward (same worktree-aware CAS path used in the happy path).The durable record is cleared only when the recovery path confirms (1) no owned git operation remains in flight AND (2) the feature SHA was restored to
pre_feature_sha(for phase=’integrating’) OR the fast-forward reconciled cleanly (for phase=’integrated’). Any failure retains the record so the next run can retry the recovery.Every outcome that RETAINS the record sets
RebaseState.recovery_record_retained, which callers read throughrecovery_retained_record(). A caller that sees it must not start a fresh integration in the same startup: the integration writes its own durable record before mutating git and would overwrite the unreconciled one.
- Parameters:
workspace_scope (WorkspaceScope) – Scope whose root holds the durable record.
config (UnifiedConfig | None) – Run configuration, used ONLY to re-read the mainline pointer before the phase=’integrated’ ancestry verdicts. Optional, and
Nonereproduces the pre-seam behaviour exactly, so a caller that holds no config keeps working.
- Return type:
RebaseState | None
Any unexpected exception inside this function is logged and swallowed; it must never abort the run.
- ralph.pipeline.auto_integrate_recovery.recovery_retained_record(state)[source]¶
Whether
statecame back still OWNING the durable record.recover_incomplete_integration()has two shapes of outcome. Either it reconciled the interrupted operation – cleared the durableIntegrationRecordafter proving the feature branch was restored or the fast-forward landed – or it hit a transient failure and deliberately LEFT the record on disk so the next startup can retry it.In the second shape recovery is not finished, and the caller must NOT start a fresh integration in the same startup:
auto_integrate._integrate_oncewrites a newIntegrationRecord(phase='integrating', ...)before it touches git, which overwrites the only durable metadata describing the still-unreconciled operation – the pre-integration feature SHA a later recovery needs to restore, and the target the interrupted landing was aimed at.Callers read the retention fact THROUGH this predicate rather than from
last_reason: that string is operator display text, is formatted differently on every branch, and interpolates exception messages.None(no record existed, recovery was a no-op) is not retained.- Parameters:
state (RebaseState | None)
- Return type:
bool
ralph.pipeline.auto_integrate_recovery_terminal¶
Terminal-state and backup-reference helpers for integration recovery.
- exception ralph.pipeline.auto_integrate_recovery_terminal.TerminalStateViolationError[source]
Bases:
RuntimeErrorRaised when an integration leaves git in a non-terminal state.
- ralph.pipeline.auto_integrate_recovery_terminal.delete_rebase_backup_refs(root)[source]
Best-effort delete of recovery backup refs after a verified outcome.
- Parameters:
root (Path)
- Return type:
None
- ralph.pipeline.auto_integrate_recovery_terminal.post_attempt_verify(root, *, expected_head_sha, owns_resolution)[source]
Raise when rebase markers remain or HEAD differs from the expected SHA.
- Parameters:
root (Path)
expected_head_sha (str | None)
owns_resolution (bool)
- Return type:
None
ralph.pipeline.auto_integrate_refresh¶
Local freshness observation for auto-integration.
Remote fetches occur only through the opt-in remote-sync path.
- ralph.pipeline.auto_integrate_refresh.refresh_target(config, root, target)[source]¶
Re-observe the local target without contacting a remote.
Remote fetching belongs exclusively to the opt-in remote-sync path. Local integration still re-reads the shared branch ref so sibling worktrees remain visible when remote sync is disabled.
- Parameters:
config (UnifiedConfig)
root (Path)
target (str)
- Return type:
str
ralph.pipeline.auto_integrate_remote_sync¶
Fail-open, opt-in pull, reconcile, push, and retry helpers.
Remote work is disabled by default. When enabled, every operation records a bounded outcome and leaves local integration intact on failure.
- ralph.pipeline.auto_integrate_remote_sync.REMOTE_CREATED = 'created'¶
The pull fetched,
<remote>/<target>did not exist, the push created it. Distinct outcome so the operator sees a remote branch appear rather than discovering it later.
- ralph.pipeline.auto_integrate_remote_sync.REMOTE_LOCAL_AHEAD = 'local ahead'¶
The local target is strictly ahead of the remote. No remote-side motion needed; the next landing pushes the local state to the remote.
- ralph.pipeline.auto_integrate_remote_sync.REMOTE_NO_REMOTE = 'no remote'¶
The remote named in config is not configured in this repository; the step degrades to local-only with a recorded skip.
- ralph.pipeline.auto_integrate_remote_sync.REMOTE_NO_REMOTE_BRANCH = 'no remote branch'¶
<remote>/<target>does not exist; nothing to pull. The local integration proceeds untouched.
- ralph.pipeline.auto_integrate_remote_sync.REMOTE_PULLED = 'pulled'¶
The pull fetched and the local target was fast-forwarded to the remote tip. Recorded so the operator line can name what happened.
- ralph.pipeline.auto_integrate_remote_sync.REMOTE_PULL_FAILED = 'pull failed'¶
The pull failed for a transient reason (unreachable, timeout, auth). Local integration proceeds untouched. The reason is recorded.
- ralph.pipeline.auto_integrate_remote_sync.REMOTE_PUSHED = 'pushed'¶
The push succeeded after a normal landing (or after one or more reconcile cycles on a non-fast-forward rejection).
- ralph.pipeline.auto_integrate_remote_sync.REMOTE_PUSH_RECONCILED = 'push reconciled'¶
The push was rejected non-fast-forward and a reconcile cycle succeeded; next attempt will retry the push with the reconciled target.
- ralph.pipeline.auto_integrate_remote_sync.REMOTE_PUSH_REJECTED = 'push rejected'¶
The push was rejected and the bounded reconcile loop exhausted without landing. Recorded, not terminal – the next seam retries.
- ralph.pipeline.auto_integrate_remote_sync.REMOTE_RECONCILED = 'reconciled'¶
The pull fetched and the local target was rebased onto the remote target. The remote’s existing commits were NEVER rewritten; only the unpublished local commits on top of
refs/heads/<target>were replayed on top of<remote>/<target>.
- ralph.pipeline.auto_integrate_remote_sync.REMOTE_REMOTE_UNREACHABLE = 'origin unreachable'¶
Failure classification – the recorded reason the run did not publish, used both for the operator line and the waiting-state end-of-run summary.
- ralph.pipeline.auto_integrate_remote_sync.pull_and_reconcile_target(config, repo_root, target, *, remote=None, clock=<built-in function monotonic>)[source]¶
Run the throttled pull side; reconcile the local target from the remote.
Uses the configured remote when remote integration is enabled; otherwise it performs no remote operation.
The throttle is keyed
(root, remote, target)and armed only by a HEALTHY fetch: a transient blip cannot buy a whole window of unrefreshed probes. Interval zero = every seam (AC-19).Reconcile outcomes:
Local ahead – no local ref moves on the pull side; the next landing pushes the local state.
Remote strictly ahead –
refs/heads/<target>is advanced to<remote>/<target>via the same landing machinery as Part A (owning worktree’smerge --ff-onlyfirst, atomic CAS fallback, never a force move). Recorded asREMOTE_PULLED.Diverged – the local target is rebased onto the remote target through the existing rebase engine; the operator-visible outcome is
REMOTE_RECONCILED. Remote’s commits are never rewritten (same SHA before and after).Pull failure – logged and recorded as
REMOTE_PULL_FAILEDwith the recorded reason so the run can render it on the operator line.
- Parameters:
config (UnifiedConfig | None) – Unified run configuration;
Noneskips every remote call. The opt-in flag and the configured remote are read here so the helper is the single source of truth for the gated-on check.repo_root (Path) – Repository root in which to run the probe.
target (str) – Branch whose freshness to observe and reconcile.
remote (str | None) – Override the configured remote (test-only seam).
clock (Callable[[], float]) – Monotonic time source for the throttle, injected so the test suite advances time without sleeping.
- Returns:
RebaseStateannotated withlast_remote_syncandlast_refresh; orNonewhen the call is a no-op (disabled, throttled, or no remote configured).- Return type:
RebaseState | None
- ralph.pipeline.auto_integrate_remote_sync.push_target_after_landing(config, repo_root, target, record, *, remote=None)[source]¶
Push the local target to the configured remote after a successful land.
Returns the
RebaseStateannotated withlast_remote_syncandlast_push. Never raises; never reverts a local landing. The- Parameters:
config (UnifiedConfig | None)
repo_root (Path)
target (str)
record (RebaseState)
remote (str | None)
- Return type:
- ralph.pipeline.auto_integrate_remote_sync.reconcile_after_rejected_push(config, repo_root, target, record, *, remote=None, max_attempts=3, reintegrate=None)[source]¶
Try a bounded reconcile-then-push cycle after a non-fast-forward.
Per-seam cap is
_MAX_REMOTE_SYNC_ATTEMPTS(3), matching the Part-A landing loop budget. Exhausting the cap is NOT a give-up: the pending push is carried forward onRebaseState.last_remote_syncasREMOTE_PUSH_REJECTEDand retried on the next seam, so the fail-open contract holds for the whole run, not just one seam.- Parameters:
config (UnifiedConfig | None)
repo_root (Path)
target (str)
record (RebaseState)
remote (str | None)
max_attempts (int)
reintegrate (Callable[[], bool] | None)
- Return type:
ralph.pipeline.auto_integrate_terminal¶
Verified terminal cleanup for auto-integration attempts.
- ralph.pipeline.auto_integrate_terminal.verify_and_cleanup_backup(root, backup_ref, expected_head_sha, owns_resolution)[source]
Verify terminal git state, then delete the no-longer-needed backup ref.
- Parameters:
root (Path)
backup_ref (str | None)
expected_head_sha (str | None)
owns_resolution (bool)
- Return type:
None
ralph.pipeline.auto_integrate_sync¶
Bounded, OBSERVE-ONLY freshness probe for the auto-integrate target.
This is the ONLY code path in ralph/ that may contact a remote, and
it contacts it strictly read-only: git fetch updates
refs/remotes/<remote>/<target> and NOTHING else. Remote state must
never affect a local rebase, merge or landing: the module never moves
refs/heads/<target>, never touches a worktree, never pushes. The
authoritative mainline pointer is always the LOCAL ref – in the
linked-worktree fleet this feature exists for, every agent shares one
git common directory and sibling agents advance refs/heads/<target>
directly, so re-reading that ref IS the freshness primitive.
The refresh used to fast-forward the local target ref from a strictly-
ahead origin/<target> (a clone-topology convenience). That let a
remote nobody asked about rewrite the base of every local rebase, so
the advance was removed: an origin observed ahead is now REPORTED
(REFRESH_ORIGIN_AHEAD) and the local ref is left alone.
Every failure is fail-open: an absent remote, an unreachable host, a timeout or a diverged history all leave the repository untouched, so integration proceeds against the local ref exactly as it would have without the probe.
The remote name is parameterized (origin by default for
backwards compatibility, any configured remote name when remote sync is
opted into) so the same probe services both the legacy observe-only
the opt-in configured-remote sync.
- ralph.pipeline.auto_integrate_sync.DEFAULT_REFRESH_REMOTE = 'origin'¶
Default configured remote name.
- ralph.pipeline.auto_integrate_sync.REFRESH_DISABLED = 'fetch disabled'¶
Typed outcomes of
refresh_target_from_remote(). The refresh is fail-open by design – an unreachable remote degrades to local-only integration rather than failing the run – so the outcome is the ONLY signal that tells an operator how the mainline pointer the integration used was observed. It is recorded onRebaseState.last_refreshand rendered in the auto-integrate line.
- ralph.pipeline.auto_integrate_sync.REFRESH_LOCAL_AHEAD = 'local ahead of remote'¶
Local target contains the fetched remote target. This matters only to the opt-in sync path: it must publish later, not rebase local history.
- ralph.pipeline.auto_integrate_sync.REFRESH_LOCAL_FLEET = 'local fleet'¶
The target pointer was re-observed from the SHARED ref store rather than from a remote. This is the normal outcome for Ralph’s own linked-worktree fleet, where every agent’s
wt-0NN-*worktree shares one git common directory and sibling agents advance the localrefs/heads/<target>directly, with noorigininvolved. Distinct fromREFRESH_NO_ORIGIN, which means the target could not be observed AT ALL – neither remotely nor locally.
- ralph.pipeline.auto_integrate_sync.REFRESH_ORIGIN_AHEAD = 'origin ahead (local ref kept)'¶
the local ref is authoritative for every local rebase and landing decision, so nothing is applied and nothing local moves.
- Type:
Origin holds commits the local ref lacks. Observation ONLY
- ralph.pipeline.auto_integrate_sync.REFRESH_REFRESHED = 'refreshed from origin'¶
Historical outcome retained for records persisted by earlier versions, which fast-forwarded the local ref from origin. The observe-only refresh never produces it: remote state no longer moves any local ref.
- ralph.pipeline.auto_integrate_sync.REFRESH_SUPPRESSED = 'refresh suppressed by throttle'¶
The boundary refresh throttle declined this probe, so NO refresh was taken. Recorded rather than left as
Nonebecause a boundary decided from a pointer nobody re-read this round is exactly as unverifiable as one whose refresh failed – the operator has to be able to tell that case from a genuinely fresh one.
- ralph.pipeline.auto_integrate_sync.observe_target_sha(repo_root, target)[source]¶
Re-read
refs/heads/<target>from the shared ref store.Returns the SHA, or
Nonewhen the branch does not exist.Branch refs live in the git COMMON directory, not in the per-worktree git dir, so this read observes updates made by any sibling worktree in the fleet – including ones that landed microseconds ago. That is what makes it the correct freshness primitive for the local fleet: the pointer other agents advance is already the one this call reads.
Never raises; an unusable repository reports
Noneso the fail-open refresh contract is preserved.- Parameters:
repo_root (Path)
target (str)
- Return type:
str | None
- ralph.pipeline.auto_integrate_sync.refresh_target_from_remote(repo_root, target, *, timeout_seconds, remote='origin')[source]¶
Observe the freshness of
refs/heads/<target>, fetchingremoteif any.Returns one of the
REFRESH_*outcomes. Never raises, never pushes, and NEVER moves a local ref: the fetch updates only the remote-tracking ref, and the comparison below is pure reporting. Remote state must not affect local rebase operations, so an origin observed strictly ahead is recorded asREFRESH_ORIGIN_AHEADand the local ref – the authoritative pointer every local decision uses – is left exactly where the local fleet put it.- Parameters:
repo_root (Path) – Repository root in which to run the probe.
target (str) – Branch whose freshness to observe.
timeout_seconds (float) – Per-attempt wall-clock fetch budget.
remote (str) – The remote to fetch from. Defaults to
originfor the configured remote-sync tier passes its configured remote here.
- Return type:
str
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:
objectRegistry 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
SubagentPidRegistryis created per invocation and threaded into both the execution strategy (viasubagent_pid_source=) and the parser (viasubagent_pid_registry=) so any PID registered by either layer becomes visible toProcessMonitor.spawned_subagent_count().The per-transport factory helpers in
ralph.process.monitor._subagent_pid_source_providerswrap the shared registry to expose aSubagentPidSourcethat filters by transport source label. OpenCode’sChildLivenessSubagentPidSourcecontinues to use its ownChildLivenessRegistry(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
AgentCatalogbound to this registry.When no catalog is injected at construction time, the registry falls back to
ralph.agents.catalog.default_catalog().register_agent_supportuses this property to write into the caller-owned catalog only, so a freshAgentRegistry(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:
- 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
- class ralph.pipeline.runner.DynamicBindingMcpServerFactory(workspace, *, reserve_port=None, start_server=<function start_mcp_server>, lifecycle_deps=None)[source]¶
Bases:
McpServerFactoryBuild MCP server handles with dynamically allocated localhost endpoints.
- Parameters:
workspace (WorkspaceLike)
reserve_port (Callable[[], int] | None)
start_server (StartServer)
lifecycle_deps (lifecycle.LifecycleDeps | None)
- class ralph.pipeline.runner.McpSupervisor(bridge, *, check_interval=datetime.timedelta(seconds=2), on_restart=None, on_error=None, terminate_agents=<function _terminate_agent_processes>)[source]¶
Bases:
objectBackground-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)everycheck_intervalseconds. Restarts are recorded via the optionalon_restartcallback. If the restart budget is exhausted, the storedMcpServerErroris re-raised when the context manager exits — taking priority over any agent-level error.- Parameters:
bridge (RestartAwareMcpBridge)
check_interval (timedelta)
on_restart (Callable[[int], None] | None)
on_error (Callable[[McpServerError], None] | None)
terminate_agents (Callable[[str], None])
- 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:
objectAgentExecutor 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_logsso the memory the log holds (up toDEFAULT_MAX_OVERFLOW_FILE_BYTESper unit) is released when the unit is no longer needed. Callsclose()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_flagresolved viaresolve_model_identity(transport, model_flag)3.UNKNOWN_IDENTITYfallback- Parameters:
transport (AgentTransport | None)
drain (str)
workspace_path (Path | None)
agents_policy (AgentsPolicy | None)
model_opts (SessionModelOpts | None)
model_flag (str | None)
- Return type:
- 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
bridgeis aRestartAwareMcpBridge. RaisesMcpServerErrorwhen 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:
- ralph.pipeline.runner.create_initial_state(config, *, agents_policy=None, pipeline_policy, counter_overrides=None)[source]¶
Create initial pipeline state from configuration.
- Parameters:
config (UnifiedConfig)
agents_policy (AgentsPolicy | None)
pipeline_policy (PipelinePolicy)
counter_overrides (dict[str, int] | None)
- 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:
phase (str)
agents_policy (AgentsPolicy | None)
- 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:
state (PipelineState)
workspace_root (Path)
subscriber (PipelineSubscriber | None)
display (ParallelDisplay | None)
display_context (DisplayContext)
- 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:
display (ParallelDisplay)
previous_phase (str)
state (PipelineState)
verbosity (Verbosity)
pipeline_policy (PipelinePolicy)
- Return type:
str
- ralph.pipeline.runner.execute_agent_effect(effect, config, pipeline_deps, workspace_scope, *, display_context, display=None, verbosity=Verbosity.VERBOSE, state=None, policy_bundle=None, 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.
display_contextis a required keyword so a call site cannot silently omit the display dependency: at least one ofdisplay_context/displaymust be non-None orget_display_context()raises.- Parameters:
effect (InvokeAgentEffect)
config (UnifiedConfig)
pipeline_deps (PipelineDeps)
workspace_scope (WorkspaceScope)
display_context (DisplayContext | None)
display (ParallelDisplay | None)
verbosity (Verbosity)
state (PipelineState | None)
policy_bundle (PolicyBundle | None)
bridge (RestartAwareMcpBridge | None)
raw_output_sink (deque[str] | None)
rendered_output_sink (deque[str] | None)
run_id (str | None)
required_artifact (RequiredArtifact | None)
session_id (str | None)
extra_env (dict[str, str] | None)
raise_resumable_exit (bool)
agent_invocation_error_sink (Callable[[Exception], object] | None)
opts (object)
- Return type:
- 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:
- ralph.pipeline.runner.handle_phase(effect, ctx)[source]¶
Dispatch to the appropriate phase handler.
- Parameters:
effect (Effect) – The effect that triggered this phase.
ctx (PhaseContext) – Phase context with workspace and policy.
- Returns:
List of events to emit to the reducer.
- Raises:
PhaseHandlerNotFoundError – If no handler is registered for the phase.
- Return type:
list[Event]
- 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:
- ralph.pipeline.runner.install_signal_handlers(loop, root_task, bridge, controller=None)[source]¶
Register SIGINT handlers that cancel
root_taskand forward to child PIDs.The fourth argument is type-broadened to accept an
InterruptController(legacy) OR anInterruptDispatcher(new). Discrimination is byisinstanceinside the body. When a controller is passed, the implementation synthesizes a dispatcher that forwards the controller’skill_process_groupandhard_exitso 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:
loop (asyncio.AbstractEventLoop)
root_task (asyncio.Task[object])
bridge (SignalBridge)
controller (InterruptController | InterruptDispatcher | None)
- 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, force_height=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).
force_height (int | None) – Override terminal height detection.
Nonefalls back toconsole.size.height(the canonical Rich Console contract);Nonedisables height-aware rendering for callers that don’t consume the height field.
- Returns:
Fully initialised DisplayContext.
- Return type:
- ralph.pipeline.runner.materialize_master_prompt(*, workspace_root, name, default_product_criteria=None, worker_namespace=None, planning_style=False, backend=<ralph.mcp.artifacts._path_file_backend.PathFileBackend object>)[source]¶
Write a master prompt file for the named agent and return its path.
The injected
backendcontrols bothmkdirand the physical text writes so a byte-identical re-emit of the current or system prompt does not advance the file’s mtime or generate an additional fseventsd notification. The default backend is the real-Path backend; tests inject an in-memory counting backend to verify the idempotent skip. The post-condition “file contains the rendered prompt” always holds: any read uncertainty or content mismatch falls through to a real write.- Parameters:
workspace_root (Path)
name (str)
default_product_criteria (str | None)
worker_namespace (Path | None)
planning_style (bool)
backend (FileBackend)
- Return type:
str
- 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:
context (PromptPhaseContext | None)
options (PromptPhaseOptions | None)
kwargs (object)
- Return type:
str
- ralph.pipeline.runner.metadata_summary(metadata)[source]¶
Backwards-compatible metadata summary shim.
Returns the registry’s stable
key=value, ...summary for the preferred metadata keys (status / summary / phase / decision / message / event / tool / path / workdir / command). Kept as a free function so the existing pipeline-runner test that assertsrunner_module.metadata_summarystill works.- Parameters:
metadata (dict[str, object])
- 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:
drain (str | None)
phase (str | None)
pipeline_policy (PipelinePolicy | None)
agents_policy (AgentsPolicy | None)
- Return type:
- 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_handlerare not overwritten.The phase module imports are deferred to call time:
ralph.policy.loaderimports this function during policy loading, and importing the phase modules at module load time would re-enter that same loading chain. By the time policy loading finishes and this function is actually called, all phase modules are fully available as a side effect of the load.- Parameters:
policy (PipelinePolicy) – Loaded pipeline policy.
- Return type:
None
- ralph.pipeline.runner.render_agent_activity_line(output, agent_name)¶
Render an agent event through the single registry.
After the wt-028-display consolidation, this function constructs a canonical
AgentActivityEventfrom the parser-shapedAgentOutputLinevia the shared normalizer (ralph.display.agent_event_renderer.normalize_event_from_agent_output_line()) so agent-specific quirks (claude / codex / opencode / …) are removed BEFORE rendering. It then delegates every presentation decision to the single registry viaralph.display.agent_event_renderer.render_event()and extracts the plain text from the returnedrich.text.Textso downstream callers that expect a styled Text keep working unchanged.The previous per-type helpers (
_render_text_line/_render_tool_use_line/_render_tool_result_line/_render_error_line/_render_metadata_event_line/_tool_input_summary/_metadata_summary/_kv_summary/_styled_prefix) were the pipeline runner’s competing formatter; they have been deleted and this function is the single rendering seam on the pipeline-runnner side.Production call sites construct typed events at ingestion and call
render_event()directly. The plain-text adapterrender_event_kind_textis kept only at the final presentation boundary (for paths that don’t carry a Console / unit_id and need a plain string back), not in the rendering seam.- Parameters:
output (AgentOutputLine)
agent_name (str)
- Return type:
Text | 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_displayhelper fromralph.pipeline.legacy_console_display. Pass-through for non-None inputs; constructs aParallelDisplayfrom the supplied context whendisplayisNone. Whenis_quiet=True, the constructed display short-circuits all banner and log-line emissions (see ParallelDisplay quiet-mode contract).- Parameters:
display (ParallelDisplay | None)
display_context (DisplayContext | None)
is_quiet (bool)
- Return type:
- 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:
- 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:
- 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
RestartAwareMcpBridgethat can auto-restart the server on crash up to theextras.restart_policybudget (default: 20 restarts, defined byMcpRestartPolicy).- Parameters:
session (SessionLike)
workspace (WorkspaceLike)
upstream_registry (UpstreamRegistry | None)
deps (LifecycleDeps | None)
extras (McpServerExtras | None)
- Return type:
ralph.pipeline.activity_stream¶
Activity stream rendering and artifact handoff for the pipeline runner.
- ralph.pipeline.activity_stream.metadata_summary(metadata)[source]¶
Backwards-compatible metadata summary shim.
Returns the registry’s stable
key=value, ...summary for the preferred metadata keys (status / summary / phase / decision / message / event / tool / path / workdir / command). Kept as a free function so the existing pipeline-runner test that assertsrunner_module.metadata_summarystill works.- Parameters:
metadata (dict[str, object])
- Return type:
str
- ralph.pipeline.activity_stream.render_agent_activity_line(output, agent_name)¶
Render an agent event through the single registry.
After the wt-028-display consolidation, this function constructs a canonical
AgentActivityEventfrom the parser-shapedAgentOutputLinevia the shared normalizer (ralph.display.agent_event_renderer.normalize_event_from_agent_output_line()) so agent-specific quirks (claude / codex / opencode / …) are removed BEFORE rendering. It then delegates every presentation decision to the single registry viaralph.display.agent_event_renderer.render_event()and extracts the plain text from the returnedrich.text.Textso downstream callers that expect a styled Text keep working unchanged.The previous per-type helpers (
_render_text_line/_render_tool_use_line/_render_tool_result_line/_render_error_line/_render_metadata_event_line/_tool_input_summary/_metadata_summary/_kv_summary/_styled_prefix) were the pipeline runner’s competing formatter; they have been deleted and this function is the single rendering seam on the pipeline-runnner side.Production call sites construct typed events at ingestion and call
render_event()directly. The plain-text adapterrender_event_kind_textis kept only at the final presentation boundary (for paths that don’t carry a Console / unit_id and need a plain string back), not in the rendering seam.- Parameters:
output (AgentOutputLine)
agent_name (str)
- Return type:
Text | 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=andsubagent_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:
- 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, has_uncommitted_changes_fn=<function has_uncommitted_changes>, agy_agents_probe=None)[source]¶
Select the next pipeline effect based on current state and policy.
- Parameters:
state (PipelineState)
policy_bundle (PolicyBundle)
workspace_scope (WorkspaceScope | None)
config (UnifiedConfig | None)
has_uncommitted_changes_fn (Callable[[Path], bool])
agy_agents_probe (Callable[[], str] | None)
- 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:
effect (FanOutEffect)
state (PipelineState)
display (ParallelDisplay)
pipeline_deps (PipelineDeps | None)
opts (object)
- Return type:
PipelineState
- ralph.pipeline.fan_out.write_parallel_development_summary(workspace_scope, effect, state, verification=None)[source]¶
Write the Markdown parallel-development summary after fan-out completes.
- Parameters:
workspace_scope (WorkspaceScope)
effect (FanOutEffect)
state (PipelineState)
verification (VerificationResult | None)
- Return type:
None
- ralph.pipeline.fan_out.write_parallel_summary_handoff(workspace_root, summary, *, backend=<ralph.mcp.artifacts._path_file_backend.PathFileBackend object>)[source]¶
Write the markdown handoff for the internally generated parallel summary.
The summary is engine-generated (never an agent-submitted markdown artifact), so fan-out renders the handoff itself. It reuses the development-result handoff path so the analysis phase picks it up through the same fallback path without code changes.
- Parameters:
workspace_root (Path)
summary (Mapping[str, object])
backend (FileBackend)
- Return type:
str | 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.verbosityfield is an integer (0-4); the new CLI surface is theVerbosityStrEnum. This helper accepts either and falls back toVerbosity.VERBOSEfor unknown / unset inputs.
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:
- 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:
state (PipelineState)
workspace_root (Path)
subscriber (PipelineSubscriber | None)
display (ParallelDisplay | None)
display_context (DisplayContext)
- 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:
display (ParallelDisplay)
previous_phase (str)
state (PipelineState)
verbosity (Verbosity)
pipeline_policy (PipelinePolicy)
- 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.
When a commit-role phase declares
commit_policywith noincrements_counter, the trace CONTINUES alongon_successrather than returningNone– the counter belongs to a later lifecycle phase (e.g.development_final_commit), and earlier phases (planning,development,development_commit) must still resolve the counter so the Status Bar shows an outer cycle while in them. Terminal phases (complete,failed_terminal) self-loop and the trace exhausts naturally, so the function still returnsNonefor genuine terminals.- Parameters:
phase_name (str)
policy (PipelinePolicy)
- 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:
phase (str)
agent_name (str)
display_context (DisplayContext)
state (PipelineState)
pipeline_policy (PipelinePolicy)
- 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:
drain (str | None)
phase (str | None)
pipeline_policy (PipelinePolicy | None)
agents_policy (AgentsPolicy | None)
- Return type:
- 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:
drain (str | None)
phase (str | None)
pipeline_policy (PipelinePolicy | None)
agent (AgentConfig | None)
agents_policy (AgentsPolicy | None)
- Return type:
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:
ProtocolResolve the required artifact contract for a phase/drain.
- class ralph.pipeline.factory.CheckMcpBridgeHealthFn(*args, **kwargs)[source]¶
Bases:
ProtocolCheck that an MCP bridge is healthy, raising on failure.
- class ralph.pipeline.factory.DefaultPipelineFactory[source]¶
Bases:
objectDefault composition root for the main pipeline and plumbing commands.
This thin, stateless factory implements
PipelineFactoryby delegating tobuild_default_pipeline_deps(). Because the extended call sites (main pipeline, parallel worker runtime) need to injectmodel_identityandpolicy_bundle, thebuild()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
PipelineDepswired to production defaults.- Parameters:
config (UnifiedConfig)
display_context (DisplayContext)
model_identity (MultimodalModelIdentity | None)
policy_bundle (PolicyBundle | None)
recovery_sleep (Callable[[float], None] | None)
pro_hooks (ProPipelineHooks | None)
- Return type:
- class ralph.pipeline.factory.HeartbeatPolicyFromEnvFn(*args, **kwargs)[source]¶
Bases:
ProtocolReturn the heartbeat policy read from the environment.
- class ralph.pipeline.factory.MaterializeMasterPromptFn(*args, **kwargs)[source]¶
Bases:
ProtocolMaterialize a master prompt file and return its path.
Matches the wrapper in
ralph._session_runtime_depsand the publicralph.prompts.master_prompt.materialize_master_promptsurface.
- class ralph.pipeline.factory.McpSupervisorFactoryFn(*args, **kwargs)[source]¶
Bases:
ProtocolConstruct a context manager that supervises an MCP bridge during invocation.
- class ralph.pipeline.factory.PhasePromptMaterializerFn(*args, **kwargs)[source]¶
Bases:
ProtocolMaterialize a phase prompt and return its dump path.
- class ralph.pipeline.factory.PipelineCore(display_context, model_identity=None, master_prompt_materializer=<function _materialize_master_prompt>, phase_prompt_materializer=<function _materialize_prompt_for_phase>, artifact_requirements_resolver=<function _resolve_phase_required_artifact>)[source]¶
Bases:
objectThe 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.master_prompt_materializer— master-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:
display_context (DisplayContext)
model_identity (MultimodalModelIdentity | None)
master_prompt_materializer (MaterializeMasterPromptFn)
phase_prompt_materializer (PhasePromptMaterializerFn)
artifact_requirements_resolver (ArtifactRequirementsResolverFn)
- class ralph.pipeline.factory.PipelineDeps(*, core=None, display_context=<object object>, model_identity=<object object>, master_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, connectivity_monitor=None, catchup_worker_factory=None, startup_rebase_resolver=None, auto_integrate_resolver=None, commit_effect_executor=None, has_uncommitted_changes=None, agy_agents_probe=None)[source]¶
Bases:
objectInjectable 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 sevenProPipelineHooksoverrides, and the recovery sleep seam.For backward compatibility, the four collaborators may still be passed directly to
__init__; they are composed into the embeddedPipelineCore. Callers that already have aPipelineCoreshould passcore=...instead.- Parameters:
core (PipelineCore | None)
display_context (DisplayContext | object)
model_identity (MultimodalModelIdentity | None | object)
master_prompt_materializer (MaterializeMasterPromptFn | object)
phase_prompt_materializer (PhasePromptMaterializerFn | object)
artifact_requirements_resolver (ArtifactRequirementsResolverFn | object)
registry_factory (Callable[[UnifiedConfig], object] | None)
bridge_factory (_session_bridge.BridgeFactory)
mcp_supervisor_factory (McpSupervisorFactoryFn)
heartbeat_policy_from_env_fn (HeartbeatPolicyFromEnvFn)
check_mcp_bridge_health_fn (CheckMcpBridgeHealthFn)
policy_bundle (PolicyBundle | None)
policy_bundle_factory (PolicyBundleFactory | None)
state_factory (StateFactory | None)
recovery_controller_factory (RecoveryControllerFactory | None)
marker_watcher_factory (MarkerWatcherFactory | None)
snapshot_registry (SnapshotRegistry | None)
recovery_sleep (Callable[[float], None] | None)
connectivity_state_provider (Callable[[], str | None] | None)
is_waiting_state_provider (Callable[[], bool] | None)
process_teardown (Callable[[], None] | None)
connectivity_monitor (ConnectivityMonitorLike | None)
catchup_worker_factory (Callable[[UnifiedConfig, Path], AutoIntegrateCatchupWorker | None] | None)
startup_rebase_resolver (Callable[[UnifiedConfig, WorkspaceScope], RebaseState | None] | None)
auto_integrate_resolver (Callable[[UnifiedConfig, WorkspaceScope, RebaseState], RebaseState | None] | None)
commit_effect_executor (Callable[[object, Path], object] | None)
has_uncommitted_changes (Callable[[Path], bool] | None)
agy_agents_probe (Callable[[], str] | None)
- 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 master_prompt_materializer: MaterializeMasterPromptFn¶
Backward-compatible accessor for
core.master_prompt_materializer.
- 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.
- class ralph.pipeline.factory.PipelineFactory(*args, **kwargs)[source]¶
Bases:
ProtocolFactory that builds a
PipelineDepsfor a given config.
- ralph.pipeline.factory.apply_pro_hooks_to_deps(deps, pro_hooks, config)[source]¶
Return a new
PipelineDepswithProPipelineHooksoverrides applied.- Parameters:
deps (PipelineDeps)
pro_hooks (ProPipelineHooks)
config (UnifiedConfig)
- Return type:
- 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
PipelineDepswired to production defaults.model_identitydefaults toNoneso callers that do not have a resolved identity reproduce the pre-refactorUNKNOWN_IDENTITYbehavior; callers that already know the effective identity (e.g. plumbing commands with a single selected agent) can inject it here.policy_bundlelets the main pipeline load the policy once and inject it into the shared bundle instead of passing it as a separate runner argument.recovery_sleeplets callers replace the wall-clock sleep used during recovery backoff;pro_hooks.recovery_sleeptakes precedence over this argument when both are provided.- Parameters:
config (UnifiedConfig)
display_context (DisplayContext)
model_identity (MultimodalModelIdentity | None)
policy_bundle (PolicyBundle | None)
recovery_sleep (Callable[[float], None] | None)
pro_hooks (ProPipelineHooks | None)
- Return type:
- 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 acceptpro_hooks,policy_bundle,recovery_sleep, or any other extended field; callers that need the extended bundle should usebuild_default_pipeline_deps().- Parameters:
config (UnifiedConfig)
display_context (DisplayContext)
model_identity (MultimodalModelIdentity | None)
- Return type:
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
verbosityis 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
ProPipelineHookscarrying Pro overrides. Ignored whenpipeline_depsis provided; prefer passingpro_hookstobuild_default_pipeline_deps()instead.pipeline_deps (PipelineDeps | None) – Optional
PipelineDepscarrying injected collaborators. This is the single authoritative injection surface for the run loop. When provided, its values take precedence overpro_hooksand 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 passrecovery_sleeptobuild_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 aspolicy_bundle_factory,registry_factory, etc. These are now deprecated. Construct aPipelineDepsbundle viabuild_default_pipeline_deps()(optionally passingpro_hooksorrecovery_sleepto it) and pass onlypipeline_deps. Passing any deprecated factory kwarg alongsidepipeline_depsraisesValueError. Callers using only the old factory kwargs (withoutpipeline_deps) continue to work for backward compatibility.
- Returns:
Exit code (0 for success, non-zero for failure).
- Parameters:
config (UnifiedConfig)
initial_state (PipelineState | None)
display (ParallelDisplay | None)
pipeline_subscriber (_PipelineSubscriberProtocol | None)
dashboard_subscriber (_PipelineSubscriberProtocol | None)
verbosity (Verbosity | 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)
_recovery_sleep (Callable[[float], None] | None)
pro_hooks (ProPipelineHooks | None)
policy_bundle_factory (Callable[[WorkspaceScope, UnifiedConfig], PolicyBundle] | None)
registry_factory (Callable[[UnifiedConfig], _RegistryLike] | None)
state_factory (Callable[[UnifiedConfig, AgentsPolicy, PipelinePolicy, dict[str, int] | None], PipelineState] | None)
recovery_controller_factory (Callable[[PipelineState, PolicyBundle, UnifiedConfig], tuple[RecoveryController, int]] | None)
marker_watcher_factory (Callable[[Path], ProMarkerWatcher] | None)
snapshot_registry (SnapshotRegistry | None)
pipeline_deps (PipelineDeps | None)
- Return type:
int
ralph.pipeline.run_time_report¶
Build and persist the bounded, comparable end-of-run time report.
- ralph.pipeline.run_time_report.emit_run_time_report_safely(workspace_root, *, state, outcome, elapsed_seconds, getenv=<bound method Mapping.get of 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': 'BUNDLER_ENVIRONMENT_PRESERVER_INTENTIONALLY_NIL', 'BUNDLER_ORIG_PATH': '/Users/mistlight/.rbenv/versions/4.0.1/bin:/opt/homebrew/Cellar/rbenv/1.3.2/libexec:/Users/mistlight/.kimi-code/bin:/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', 'CODEX_POOLER_API_KEY': 'sk-cxp-930c8a01993e-gk5LmCr2IrU3kNgyvr7ON2uKFTGRCUAylCL_PJDWwF8', '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': '79116', 'KITTY_PUBLIC_KEY': '1:8G@;+<|Y(Q^z~!*M#f{mb+B!B4u>FDNLU<+1##6+', 'KITTY_WINDOW_ID': '30', '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', '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', 'OMNIROUTE_API_KEY': 'omni_sk_451f344986e3634b951bd357d5a55f4c4c5856810fd80d52b4e3ccd3dd4c3b86', '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/.kimi-code/bin:/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/.kimi-code/bin:/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.iUw2KSKA6x/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', 'VIPSHOME': '/opt/homebrew/Cellar/vips/8.18.4_1', 'VIRTUAL_ENV': '/Volumes/Crucial X9/ext-Projects/Ralph-Site/tmp/docs_build/ralph-workflow/.venv', 'WINDOWID': '21445', '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]¶
Write the report while preserving the original pipeline exit status.
- Parameters:
workspace_root (Path)
state (PipelineState)
outcome (str)
elapsed_seconds (float)
getenv (Callable[[str], str | None])
- Return type:
None
- ralph.pipeline.run_time_report.render_run_time_report(*, state, outcome, elapsed_seconds, getenv=<bound method Mapping.get of 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': 'BUNDLER_ENVIRONMENT_PRESERVER_INTENTIONALLY_NIL', 'BUNDLER_ORIG_PATH': '/Users/mistlight/.rbenv/versions/4.0.1/bin:/opt/homebrew/Cellar/rbenv/1.3.2/libexec:/Users/mistlight/.kimi-code/bin:/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', 'CODEX_POOLER_API_KEY': 'sk-cxp-930c8a01993e-gk5LmCr2IrU3kNgyvr7ON2uKFTGRCUAylCL_PJDWwF8', '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': '79116', 'KITTY_PUBLIC_KEY': '1:8G@;+<|Y(Q^z~!*M#f{mb+B!B4u>FDNLU<+1##6+', 'KITTY_WINDOW_ID': '30', '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', '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', 'OMNIROUTE_API_KEY': 'omni_sk_451f344986e3634b951bd357d5a55f4c4c5856810fd80d52b4e3ccd3dd4c3b86', '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/.kimi-code/bin:/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/.kimi-code/bin:/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.iUw2KSKA6x/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', 'VIPSHOME': '/opt/homebrew/Cellar/vips/8.18.4_1', 'VIRTUAL_ENV': '/Volumes/Crucial X9/ext-Projects/Ralph-Site/tmp/docs_build/ralph-workflow/.venv', 'WINDOWID': '21445', '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]¶
Return the stable, bounded Markdown report for one pipeline execution.
- Parameters:
state (PipelineState)
outcome (str)
elapsed_seconds (float)
getenv (Callable[[str], str | None])
- Return type:
str
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:
config (UnifiedConfig)
agents_policy (AgentsPolicy | None)
pipeline_policy (PipelinePolicy)
counter_overrides (dict[str, int] | None)
- 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:
event (object)
subscriber (PipelineSubscriber | None)
unit_id (str)
agent_name (str)
- 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_idis 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:
BaseModelSingle 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:
- ralph.pipeline.agent_retry_intent.cleared_agent_retry_intent()[source]¶
Return the empty intent used to clear next-attempt session state.
- Return type:
ralph.pipeline.retryable_failure¶
Shared retryable agent-failure classification helpers.
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:
BaseModelState 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:
- with_retry_increment()[source]
Return a copy with retries incremented by 1.
- Return type:
- class ralph.pipeline.state.CommitState(*, message_prepared=False, diff_prepared=False, agent_invoked=False)[source]
Bases:
BaseModelState 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:
BaseModelA 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, post_commit_phase_override=None, work_units=<factory>, worker_states=<factory>, phase_timings=<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:
_FrozenPipelineStateModelImmutable 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)
post_commit_phase_override (str | None)
work_units (tuple[WorkUnit, ...])
worker_states (dict[str, WorkerState])
phase_timings (tuple[PhaseTimingRecord, ...])
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:
policy (PipelinePolicy)
overrides (object)
- 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_withdeliberately refuses to mutate non-emptywork_unitsso 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:
phase (str)
chain (AgentChainState)
- Return type:
PipelineState
- with_phase_timing(record)[source]
Return a copy carrying one completed phase timing record.
- Parameters:
record (PhaseTimingRecord)
- Return type:
PipelineState
- class ralph.pipeline.state.RebaseState(*, pending=False, in_progress=False, completed=False, last_action=None, last_reason=None, last_target=None, fast_forwarded=False, last_refresh=None, last_push=None, last_remote_sync=None, last_remote=None, last_push_status=None, consecutive_conflicts=0, last_conflict_feature_sha=None, last_conflict_target_sha=None, recovery_record_retained=False)[source]
Bases:
BaseModelState for git rebase operations.
The legacy
pending/in_progress/completedfields are preserved so existing checkpoints deserialize cleanly. The auto-integration fields (last_action/last_reason/last_target/fast_forwarded) are the surface the runner reads to thread the integration outcome into the persisted checkpoint, and the surface operators see in run state when a skip or conflict is recorded. All four new fields have defaults so legacy checkpoints load without modification.- Parameters:
pending (bool)
in_progress (bool)
completed (bool)
last_action (str | None)
last_reason (str | None)
last_target (str | None)
fast_forwarded (bool)
last_refresh (str | None)
last_push (str | None)
last_remote_sync (str | None)
last_remote (str | None)
last_push_status (str | None)
consecutive_conflicts (int)
last_conflict_feature_sha (str | None)
last_conflict_target_sha (str | None)
recovery_record_retained (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:
BaseModelRun-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:
- with_fallback_increment()[source]
Return a copy with total_fallbacks incremented by 1.
- Return type:
- with_retry_increment()[source]
Return a copy with total_retries incremented by 1.
- Return type:
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>, step_ids=<factory>)[source]¶
Bases:
BaseModelSingle 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])
step_ids (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:
BaseModelTyped 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:
ValueErrorRaised 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:
BaseModelImmutable 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.
- 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:
- model_config = {'extra': 'ignore', 'frozen': True}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
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:
objectResolved exhausted-analysis bypass outcome for a phase handoff.
- Parameters:
state (PipelineState)
target_phase (PipelinePhase)
skipped (tuple[ExhaustedAnalysisSkip, ...])
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:
objectDetails for a single exhausted analysis phase that was bypassed.
- Parameters:
phase (PipelinePhase)
target_phase (PipelinePhase)
iteration_field (str)
iteration_value (int)
max_iterations (int)
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:
BaseModelState 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:
- class ralph.pipeline.state_models.AgentRetryIntent(*, action=None, session_id=None, reset_tool_registry=False, failure_reason='', skip_same_agent_retries=False)[source]¶
Bases:
BaseModelSingle 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:
BaseModelState 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:
BaseModelA 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, last_action=None, last_reason=None, last_target=None, fast_forwarded=False, last_refresh=None, last_push=None, last_remote_sync=None, last_remote=None, last_push_status=None, consecutive_conflicts=0, last_conflict_feature_sha=None, last_conflict_target_sha=None, recovery_record_retained=False)[source]¶
Bases:
BaseModelState for git rebase operations.
The legacy
pending/in_progress/completedfields are preserved so existing checkpoints deserialize cleanly. The auto-integration fields (last_action/last_reason/last_target/fast_forwarded) are the surface the runner reads to thread the integration outcome into the persisted checkpoint, and the surface operators see in run state when a skip or conflict is recorded. All four new fields have defaults so legacy checkpoints load without modification.- Parameters:
pending (bool)
in_progress (bool)
completed (bool)
last_action (str | None)
last_reason (str | None)
last_target (str | None)
fast_forwarded (bool)
last_refresh (str | None)
last_push (str | None)
last_remote_sync (str | None)
last_remote (str | None)
last_push_status (str | None)
consecutive_conflicts (int)
last_conflict_feature_sha (str | None)
last_conflict_target_sha (str | None)
recovery_record_retained (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:
BaseModelRun-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:
- with_fallback_increment()[source]¶
Return a copy with total_fallbacks incremented by 1.
- Return type:
ralph.pipeline.work_unit¶
Single planning work unit declaration.
- class ralph.pipeline.work_unit.WorkUnit(*, unit_id, description, allowed_directories=<factory>, dependencies=<factory>, step_ids=<factory>)[source]¶
Bases:
BaseModelSingle 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])
step_ids (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.
ralph.pipeline.worker_status¶
Worker status enum for parallel execution.
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:
objectManages 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-skillsto apply the update; this method records the signal in state forralphruns and_print_user_global_update_hintto 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-skillsto 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 machine-local excludes and safely untrack recognized secrets.
A tracked file is removed from the index only when both its basename and the requested exclude pattern identify a recognized secret filename. This deliberately prevents broad or project-file patterns such as
*orsettings.inifrom silently untracking source files. The working-tree secret is retained.- 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.is_recognized_secret_path(path)[source]¶
Return whether a repository-relative path names a recognized secret.
- Parameters:
path (str)
- Return type:
bool
- ralph.git.commit_cleanup.stage_commit_changes_safely(repo_root)[source]¶
Stage all non-secret work after applying canonical secret cleanup.
Recognized tracked secrets are removed from the index while their working-tree copies remain. The same narrow patterns are persisted in the repository-local exclude file before
git add -A, so recognized untracked secrets never enter the index and all other untracked work keeps the normal stage-all behavior.- Parameters:
repo_root (Path | str)
- Return type:
None
- ralph.git.commit_cleanup.untrack_engine_internal_files(repo_root, is_internal_path)[source]¶
Pre-emptively
git rm --cachedevery tracked engine-internal file.This is the rock-solid safety net that the
commit_cleanupphase calls BEFORE the agent runs: any tracked file that matchesis_internal_path(the canonicalis_agent_internal_pathpredicate) is removed from the index so it cannot enter the diff the agent sees. Without this step, adelete_fileaction 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 --cachedis used (NOTgit rm) – the working-tree files remain on disk so the agent can decide whether to follow up with a separatedelete_fileaction.Symlinks are rejected BEFORE
git rm --cachedby checkingPath(repo_root / path).is_symlink().git rmfollows 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 commite4b47d2fb), so a symlink under any of those roots is intentional and the WARNING is noise. This does NOT widen the safety surface: the canonicalis_agent_internal_pathpredicate is unchanged andis_internal_pathstill gates thegit rm --cachedcall.Repois opened in atry/finallyand closed on every exit path, mirroringdelete_file_from_repo.Per-path failures are wrapped in
try/except Exceptionso 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_phaseis pinned by the_check_pre_emptive_untrack_placementAST helper inralph/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
Pathandstrfor 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_pathfromralph.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_rootis not a git repository, or when every match was a symlink and got rejected beforegit rm --cached.- Return type:
list[str]
ralph.git.scoped_auto_commit¶
Generic deterministic auto-commit over an explicit path scope.
Shared machinery behind Ralph’s engine-owned chore commits — the skill-tree
sync (ralph.skills._auto_commit) and the project-policy readiness sync
(ralph.project_policy._auto_commit). Each caller supplies its path
scopes, deterministic subject, and body builder; this module owns the git
mechanics that make the commit safe:
dirty-path discovery per scope via
git status --porcelain --untracked-files=all -- <scope>with a defensive re-filter;preservation of the user’s exact staged state for paths OUTSIDE the scope (snapshot via
git ls-files --stage, restore viagit update-index --cacheinfo) so a partially staged file is never silently committed or corrupted;best-effort semantics: a non-git workspace, a clean scope, or any
OSError/GitCommandErrorreturnsNone— a broken git state must never block the pipeline.
A scope string ending in / matches every path under that directory;
any other scope string matches that exact repo-relative file.
- ralph.git.scoped_auto_commit.commit_scoped_updates(repo_root, *, scopes, subject, body_builder, create_commit_fn, stage_fn, path_filter=None, exclude=frozenset({}))[source]¶
Create one deterministic chore commit for dirty paths inside
scopes.path_filter(optional) further narrows the in-scope dirty set — a path is committed only when the filter returns True. Callers use it for content-conditional scopes (e.g. commit a migration candidate only when it carries the migrated marker).excludeis subtracted from the dirty set BEFORE anything else. Callers pass the paths that were already dirty before the automated work began, so a file the user was mid-edit on is never swept into an automated commit — even when it sits inside one of thescopes. Being Ralph-owned (AGENTS.md, the canonical policy dir) makes a file in-scope; it does NOT make the user’s uncommitted edit to it ours to commit.Returns the commit SHA, or
Nonewhen no commit was created (non-git workspace, clean scope, or anyOSError/GitCommandError).- Parameters:
repo_root (Path | str)
scopes (tuple[str, ...])
subject (str)
body_builder (Callable[[list[str]], str])
create_commit_fn (Callable[[Path | str, str], str])
stage_fn (Callable[[Path | str, list[str]], None])
path_filter (Callable[[str], bool] | None)
exclude (frozenset[str])
- Return type:
str | None
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.
Import ordering note: register_role_handlers must be importable before any
module that ultimately loads ralph.policy.loader (which imports this
function during policy loading). Concretely, ralph.policy.loader is reached
via ralph.phases.analysis -> ralph.phases.artifacts -> ralph.pipeline ->
ralph.config -> ralph.policy. To break that cycle, the phase-module imports
are deferred to call time inside register_role_handlers. The phase modules
were already imported once during the policy load chain, so they are available
when register_role_handlers is actually invoked.
PhaseContext is exposed at the package level via the __getattr__ lazy
loader below so that from ralph.phases import PhaseContext still works
after the cycle has unwound.
InvokeAgentEffect and PreparePromptEffect are similarly imported
lazily inside handle_phase because ralph.pipeline.effects also sits
on the same import cycle (via ralph.pipeline -> ralph.config ->
ralph.policy -> ralph.policy.loader). They are runtime values for
an isinstance check, so a lazy import inside the function keeps the
top-of-file declarations cycle-free without changing observable behavior.
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_analysisorreview_analysis. The handler:Uses
effect.phaseas the pipeline policy phase name.Uses
effect.drain(if set) oreffect.phaseas the drain name for artifact path and vocabulary lookup.Emits
AnalysisDecisionEventwith the raw decision string, letting the reducer route directly throughphase_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_nameis used to locate the artifact and vocabulary; thephase_name(defaults todrain_namewhen 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:
ValueErrorRaised 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:
- 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.legacy_json_rejection_detail(workspace, markdown_path)[source]¶
Return the Markdown-only diagnostic when a legacy sibling exists.
- Parameters:
workspace (Workspace)
markdown_path (str)
- Return type:
str | None
- ralph.phases.artifacts.load_phase_artifact(workspace, path, *, artifact_type=None)[source]¶
Validate a Markdown artifact and return its phase-consumer envelope.
artifact_typeselects the markdown spec explicitly for documents whose frontmattertypeis not the artifact type (commit_message declares its commit/skip variant there); when omitted the document’s frontmatter decides.- Parameters:
workspace (Workspace)
path (str)
artifact_type (str | None)
- 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:
workspace (Workspace)
required_artifact (RequiredArtifact)
- 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_SKIPPEDso 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 the phase’s structured retry hint.
- Parameters:
skipped_paths (list[str])
safe_applied_count (int)
- Return type:
str
- ralph.phases.commit_cleanup.handle_commit_cleanup_phase(effect, ctx)[source]¶
Handle the commit cleanup phase.
Behavior summary:
PreparePromptEffectreturnsPROMPT_PREPARED.non-agent effects return
[].InvokeAgentEffectensures git exists, auto-seeds canonical.gitignoreand.git/info/excludepatterns on every entry, validates the commit-cleanup artifact, applies cleanup actions best-effort, and returnsAGENT_SUCCESSwhenanalysis_complete=TrueorPHASE_LOOPBACKotherwise.Cleanup is best-effort: a single unsafe
delete_filedoes not abort the phase. The phase only fails when EVERY delete action was unsafe AND no safe action was applied.Missing artifacts return
PhaseFailureEventwithrecoverable=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:
objectPer-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:
objectSession 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:
- 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:
- 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:
- 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, *, output_artifact_path=None, assigned_work_unit_id=None)[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.
output_artifact_path (str | None)
assigned_work_unit_id (str | None)
- 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:
objectResult 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_pathisNonethe effective path is resolved throughralph.pro_support.prompt.resolve_effective_prompt_path().- Parameters:
workspace (Workspace)
phase (str)
iteration (int)
prompt_path (str | None)
backup_paths (tuple[str, ...])
- Return type:
- 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_pathisNonethe effective path is resolved throughralph.pro_support.prompt.resolve_effective_prompt_path()so thePROMPT_PATHenv 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:
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, canonical artifact path, markdown handoff path, and 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, artifact_path, markdown_path, normalizer, artifact_required=True)[source]¶
Bases:
objectMetadata 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)
artifact_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, unsubmitted_draft=False)[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_nameare 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 canonical 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.
unsubmitted_draft (bool)
- 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:
pipeline_policy (PipelinePolicy)
artifacts_policy (ArtifactsPolicy)
phase (str)
drain (str | None)
- 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:
artifacts_policy (ArtifactsPolicy)
drain (str)
- Return type:
RequiredArtifact | None
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.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 withPhaseTimer.start(phase)and stop it withtimer.finish()to get aPhaseTimingRecord.PhaseTimingRecord- frozen record holding the phase name, iteration number, start timestamp, and elapsedtimedelta/ 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:
objectSimple helper for measuring phase execution durations.
- Parameters:
phase (str)
iteration (int)
started_at (float)
- class ralph.phases.timing.PhaseTimingRecord(phase, iteration, started_at, elapsed, elapsed_seconds)[source]¶
Bases:
objectStructured 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
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 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:
StrEnumKinds of agent activity that can reset the idle watchdog.
- class ralph.agents.activity.AgentActivitySignal(kind, raw='', error_message=None)[source]¶
Bases:
objectSmall transport-neutral signal consumed by timeout control flow.
error_messagelets one line feed BOTH repetition dimensions. A failed tool call is genuinely both things at once – a tool call and an error – and each dimension catches a wedge the other cannot: the tool dimension collapses a repeated call whose failure TEXT varies (a failing test run, where the pytest counts differ each attempt), while the error dimension collapses a repeated failure whose ARGS vary (anMCP error -32001: Request timed outstorm across different tools, the exact incident the repeated-error breaker was written for). Forcing the classifier to pick one kind meant whichever it picked, the other wedge was invisible. Set it alongsidekind=TOOL_USEto feed both.- Parameters:
kind (AgentActivityKind)
raw (str)
error_message (str | None)
ralph.agents.agent_activity_kind¶
Watchdog-relevant agent activity kind enumeration.
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:
objectManages 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.
ralph.agents.agent_entry¶
Minimal agent config protocol for availability checks.
ralph.agents.clock¶
Clock protocol for the agent timeout subsystem.
- class ralph.agents.clock.Clock(*args, **kwargs)[source]¶
Bases:
ProtocolProtocol for wall-clock operations used by the timeout subsystem.
- 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:
ExceptionRaised 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.
ralph.agents.system_clock¶
Production clock for the agent timeout subsystem.
- class ralph.agents.system_clock.SystemClock[source]¶
Bases:
ClockProduction Clock: uses real wall-clock time.
- 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.
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:
objectDeclarative 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:
- 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:
- cmd¶
Executable command; defaults to
nameon 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 byAgentSupport.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=Trueis always set.- Parameters:
name (str) – Agent name to assign to the resulting support.
- Returns:
The materialized
AgentSupportwithis_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 viaRALPH_AGY_BINARY)pi(Pi.dev CLI)cursor(Cursor Agent CLI; binary overridable viaRALPH_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.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:
ExceptionThe 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_bysignal 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, orSTALE_LABEL_ONLY). Normally dead code: the gate refinement inIdleWatchdog._is_no_progress_quietdefers theNO_PROGRESS_QUIETfire when the corroborator reports any alive_by signal. This path is defense-in-depth.False– the corroborator returnedalive_by=None(no live signal — i.e. the child is truly dead or missing). The conservative policy routes this tois_unavailable=Truewithunavailability_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 originalSTALE_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:
objectImmutable result returned by an executor after a work unit finishes.
exit_codemirrors the subprocess exit status; 0 indicates success.final_messageis the last status line emitted by the agent.duration_msis 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:
ProtocolMinimal agent config interface for availability checks.
- class ralph.agents.availability.HasListAgents(*args, **kwargs)[source]¶
Bases:
ProtocolProtocol 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.agent_install_links¶
Installation links for bundled coding-agent CLIs.
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:
objectManages 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.
- class ralph.agents.chain.ChainManager(agents_policy)[source]¶
Bases:
objectManages 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:
- 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:
- 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:
- exception ralph.agents.chain.DrainNotBoundError(drain, available_drains)[source]¶
Bases:
ExceptionRaised 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:
ExceptionRaised 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¶
Durable completion-signal evaluation shared by every agent transport.
Required-artifact phases complete only when two independent, run-scoped facts
exist: a canonical artifact receipt and the sentinel written by
declare_complete. Optional-artifact and artifact-free phases still require
the sentinel, but do not require a receipt. Plain transcript markers are useful
for diagnostics only and never replace durable evidence.
- class ralph.agents.completion_signals.CompletionSignals(explicit_complete, required_artifact_present, artifact_types, completion_sentinel_present=False, artifact_required=False, unsubmitted_draft_present=False)[source]¶
Bases:
objectSignals that indicate whether an agent run actually completed its work.
- Parameters:
explicit_complete (bool)
required_artifact_present (bool)
artifact_types (tuple[str, ...])
completion_sentinel_present (bool)
artifact_required (bool)
unsubmitted_draft_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, …]
- 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
- artifact_required¶
True when policy requires a receipt for the phase’s artifact before the durable completion sentinel can terminate the run.
- Type:
bool
- unsubmitted_draft_present¶
True when a retained draft differs from the canonical artifact, proving authored content was not submitted.
- Type:
bool
- ralph.agents.completion_signals.completion_signals_terminal(signals)[source]¶
Return whether durable evidence satisfies the phase completion contract.
declare_completeis mandatory for every completion-enforced phase, so a valid sentinel is always required. Required-artifact phases additionally require their canonical submission receipt. Optional-artifact and artifact-free phases require no receipt.- Parameters:
signals (CompletionSignals)
- Return 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]¶
Collect durable artifact and completion evidence for one agent run.
explicit_complete is set from scanning raw_output for the declare_complete MCP tool marker for diagnostics, independently of artifact presence. required_artifact_present is True only when a run-scoped canonical submission receipt exists. completion_sentinel_present is the authoritative declaration signal. Call
completion_signals_terminalto apply the required receipt-plus-sentinel conjunction.- 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.
Nonefalls 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 (seehandle_declare_completeinralph.mcp.tools.coordination).receipt_secret (str | None) – RFC-013 P3: broker-owned secret for HMAC verification of the artifact submission receipt.
Nonefalls 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 (seehandle_submit_md_artifactinralph.mcp.tools.md_artifact).
- Returns:
CompletionSignals reflecting current artifact state and explicit completion.
- Return type:
- ralph.agents.completion_signals.extract_explicit_completion(raw_output)[source]¶
Return True if raw output contains the completion-response marker.
This transcript signal is diagnostic only: model-authored output can echo the marker. Terminal decisions must use the independently persisted completion sentinel through
completion_signals_terminal.- Parameters:
raw_output (list[str]) – Raw NDJSON lines from the agent subprocess stdout.
- Returns:
True if the completion-response 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, artifact_path=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 markdown document written by the agent (
.agent/tmp/<type>.md) 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.
Nonefalls back to the pre-P3 contract (no HMAC verification).workspace (Path)
run_id (str)
artifact_type (str)
deps (ArtifactHandlerDeps | None)
artifact_path (str | 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.
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:
CompletionEnforcingStrategy,GenericExecutionStrategyClaude-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:
ClaudeExecutionStrategyInteractive 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:
BaseExecutionStrategyDefault strategy: single-process lifetime, exit 0 is terminal success.
Used only by transports whose registered contract does not opt into session continuation or durable completion enforcement.
- 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:
BaseExecutionStrategyOpenCode-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:
durable phase-completion evidence -> TERMINAL_COMPLETE
fresh progress in registry -> WAITING_ON_CHILD
live OS descendants with no fresh progress -> RESUMABLE_CONTINUE (stale)
else -> RESUMABLE_CONTINUE
label_scopenarrows the Ralph-tracked liveness check to processes whose labels start withagent:{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_sinkis 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:
ProtocolProtocol that every agent executor implementation must satisfy.
Implementors receive a
WorkUnit, stream output viaon_output, report status transitions viaon_status, and return aWorkerResultwhen the unit completes or fails.
- exception ralph.agents.executor.ExecutorError[source]¶
Bases:
ExceptionRaised when an executor encounters an unrecoverable failure.
- class ralph.agents.executor.WorkerResult(unit_id, exit_code, final_message, duration_ms)[source]¶
Bases:
objectImmutable result returned by an executor after a work unit finishes.
exit_codemirrors the subprocess exit status; 0 indicates success.final_messageis the last status line emitted by the agent.duration_msis 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 canfrom 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:
objectPer-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_secondsis below the configuredactivity_evidence_ttl_secondsTTL. A channel withlast_at is Nonehas never been observed and is treated as stale.channel_name: Canonical name of the channel (seeChannelName).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 whenlast_atis 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 theworkspacechannel. None when the channel has no kind breakdown or when no workspace activity has been observed.alive_by: For thesubagent_livenesschannel, theAliveByclassification 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)
- class ralph.agents.idle_watchdog.corroboration_snapshot.ChannelName(*values)[source]¶
Bases:
StrEnumCanonical 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:
objectAdvisory 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:
Exponential backoff to the next agent – driven by
AgentUnavailabilityTracker.mark_unavailableinralph/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. Thewrap=Truere-arming inRecoveryController._next_available_agent_indexreconsiders earlier agents whose cooldown has expired.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, orraise 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
AgentUnavailabilityTrackerand 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) returningStuckKind.STUCK. The absoluteSESSION_CEILING_EXCEEDEDreason is the ONLY reason that bypasses the gate (it is an operator-set hard cap, not a stuck-detection signal). Every other reason – includingCHILDREN_PERSIST_TOO_LONG– is gated: the watchdog consultsclassify_stuckand 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;
PostExitWatchdogis the sole owner of post-exit fire decisions. The import-time assertion onWatchdogFireReason.__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_stateis 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_toolorsubagent_output) is fresher thanactivity_evidence_ttl_seconds.The subagent-liveness side-channel is fresh.
The
classify_quietstrategy returnsWAITING_ON_CHILDorRESUMABLE_CONTINUE(these branches are evaluated by the liveclassify_quietcallable the watchdog receives fromevaluate()– the watchdog stores the most recent callable inself._classify_quiet_providerso the gate can consult it on every_classify_stuck_nowcall).
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:
objectTracks 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_eventcallback passed toWorkspaceMonitor.set_on_event(the monitor is constructed ininvoke_agentbefore the per-run watchdog exists, so the readers register the callback on the monitor immediately after the watchdog is created inread_lines; the binding is cleared in thefinallyblock 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_attimestamps and counters. The verdict hook inevaluate()defers a NO_OUTPUT_DEADLINE fire when ANY non-stdout channel is fresher thanactivity_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 is_stalled: bool¶
Whether the watchdog currently considers the run stalled.
wt-047-stall-label: the watchdog is the sole owner of the STALLED label. The flag here is the runtime stall state and the Status Bar’s
STALLEDslot reads the watchdog-sourced event stream (NOT a display-side 30s gap derivation). The flag transitions only via_set_stallso theSTALLED/STALL_RESUMEDevents fire exactly once per transition.
- property last_alive_by: AliveBy | None¶
The corroborator’s
alive_bysignal at the most recent fire.Nonewhen the watchdog has not fired yet OR when the most recent fire was notNO_PROGRESS_QUIET(the live-child vs dead-child differentiation only matters for the NO_PROGRESS_QUIET path; other fire helpers do not capturealive_by).Consumed by
IdleWatchdogKilledError.child_aliveso 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
StuckKindthat deferred the most recent would-be fire.Nonewhen the watchdog has not deferred a fire yet OR when the most recent fire actually FIREd (the gate only sets this when it returnsWatchdogVerdict.CONTINUEto defer).The diagnostic surface for the SILENT_SUBAGENT label described in AC-05:
last_fire_reasoncollapses every non-FIRE deferral toWatchdogFireReason.DEFERRED_BY_STUCK_CLASSIFIER, butlast_deferred_kindretains the preciseStuckKind(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”). Seetests/agents/idle_watchdog/test_silent_subagent_runtime.pyfor 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_workand reset toNonebyrecord_invocation_start. Surfaced publicly so operators and tooling can see what the subagent was doing at any moment without needing to supply a fullWaitingStatusListener.
- 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_invocation_end()[source]¶
Clear an active stall as this watchdog’s invocation ends.
- 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_attimestamp. Does NOT touch_last_activity(the stdout baseline) — the existing ‘stdout only resets idle baseline’ invariant is preserved. The verdict hook inevaluate()consults the per-channel_last_atvia_channel_evidence_activeand defers a NO_OUTPUT_DEADLINE fire while the channel is fresher than the configuredactivity_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_progressheartbeat 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_outputfirst-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
_emitfor everyWaitingStatusEventwhosesubagent_activityfield 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 fullWaitingStatusListener.The listener is reset to
Noneonrecord_invocation_startso 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_statevalue. 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
WorkspaceChangeKindstring 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_thresholdidentical fingerprints in a row with no interveningnote_progress().window:
window_countoccurrences of one fingerprint within the trailingwindow_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:
objectCounts 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
maxlencap.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.Bashwith 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).
Noneis 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; resets the CONSECUTIVE streaks only.
Deliberately does NOT clear the time-windowed event deques. The two rules answer different questions and must stay independent:
consecutive asks “is the agent emitting the same fingerprint back-to-back with nothing productive in between?” — forward progress is exactly the thing that breaks that streak, so it resets here.
window asks “has the same fingerprint recurred N times in the trailing window regardless of what interleaved?” — its whole purpose is to catch a wedge whose interleaved cosmetic output keeps resetting the consecutive streak. Clearing the deque here would make the window rule a strictly weaker duplicate of the consecutive rule and it could never fire on the loop it exists to catch.
Regression guard: a retry storm that emits ordinary content (or a tool result) between each identical error previously wiped both deques on every iteration, so neither rule could ever accumulate and an agent could churn indefinitely. Window entries now age out only via
_prune/_prune_toolonwindow_seconds.- 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(orREPEATED_IDENTICAL_TOOL_CALLbased 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
evaluatemethod so it can emitREPEATED_IDENTICAL_TOOL_CALLrather thanREPEATED_ERROR_LOOPwhen 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:
objectConsolidated 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):
SESSION_CEILING_EXCEEDED — absolute wall-clock cap; activity cannot reset it.
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.
NO_OUTPUT_DEADLINE (+ drain window) — idle deadline since last output. When
activity_evidence_ttl_secondsis 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.PROCESS_EXIT_HANG — subprocess closed stdout but did not exit within budget.
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_secondsthreshold 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, *, stall_active)[source]¶
Bases:
objectStructured watchdog status event emitted by IdleWatchdog.
IdleWatchdog is the sole owner of in-stream stall/liveness decisions, and this dataclass is the public, single-channel surface through which it publishes its assessment to subscribers (the waiting-status listener, the
PipelineSubscriberwaiting-dispatch path, and the Status Bar’sSTALLEDslot). Although several transition kinds historically lined up with the WAITING_ON_CHILD branch, the event itself is not scoped to that branch: every transition listed inWaitingStatusKind(waiting, stall, and per-tick progress) flows through this single dataclass, and thekindfield is the discriminator.This dataclass is frozen so subscribers cannot accidentally mutate shared state.
The
diagnosticdict 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.Transition kinds (see
WaitingStatusKindfor the canonical enumeration and per-kind docstring):ENTERED– transition into a WAITING_ON_CHILD deferral run.PROGRESS– periodic status update while still waiting (rate-limited).SUBAGENT_PROGRESS– per-subagent progress heartbeat for the waiting-status stream; rate-limited independently ofPROGRESSso the live subagent’s activity is visible without inflating the existing PROGRESS cadence.SUSPECTED_FROZEN– cumulative wait crossed the suspect threshold; the child may be frozen.EXITED– transition out of a WAITING_ON_CHILD run. It remains a waiting-run marker; consumers clear the Status Bar slot when this event’s requiredstall_activeassessment is false.HARD_STOP– cumulative ceiling crossed; the watchdog is about to fireCHILDREN_PERSIST_TOO_LONG.STALLED– the watchdog’s stall state has transitioned ON. It is a transition breadcrumb; every event’s requiredstall_activeassessment is the single source of truth consumers mirror for the Status Bar’sSTALLEDslot. Emitted exactly once per transition into a stall (no per-tick spam) and deduped by the watchdog’s runtime stall-state flag.STALL_RESUMED– the watchdog’s stall state has transitioned OFF. Emitted exactly once per transition out of a stall (driven byrecord_activity/record_invocation_start/EXITED/ a later tick where the SILENT_SUBAGENT gate no longer defers). MirrorsSTALLEDso subscribers can render explicit lines for both transition breadcrumbs without falling through to any generic template; consumers clear fromstall_active=False.
- 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)
stall_active (bool)
- kind¶
The type of event (one of the
WaitingStatusKindvalues).- Type:
- 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).Nonewhen 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’slast_subagent_progress_atchannel-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 fromsubagent_activity(the current tool call the subagent is executing). Mirrors the watchdog’sdiagnostic_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
- stall_active¶
Required keyword-only authoritative watchdog stall assessment at emission time. Consumers mirror this value rather than deriving independent stall state.
- Type:
bool
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:
StrEnumKind 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_secondsso the new event does NOT introduce additional churn versus the existing PROGRESS cadence (both default to 30 s). STALLED: transition breadcrumb for entry into a stall. The required per-eventWaitingStatusEvent.stall_activeassessment is the sole source of truth for the Status Bar’sSTALLEDlabel. Emitted once on entry into a stall via the watchdog’s own assessment (SUSPECTED_FROZEN, HARD_STOP, or a FIRE verdict for a non-absolute reason). Emitted ONLY on the initial transition (no per-tick spam); the watchdog’s_set_stallhelper dedupes by the runtime stall-state flag. STALL_RESUMED: transition OUT of a stall. Emitted exactly once on the OFF transition (record_activity / record_invocation_start / EXITED). Mirrors STALLED so downstream consumers can render explicit lines for both transition breadcrumbs; they mirrorstall_activefor label state without falling through to the HARD_STOP template.
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:
StrEnumWhy 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.
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:
AgentInvocationErrorRaised 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:
ExceptionRaised 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:
objectOptional 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:
AgentInvocationErrorRaised 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, process_teardown=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, master_prompt_file=None, waiting_listener=None, pre_output_listener=None, permission_prompt_listener=None, required_artifact=None, requires_completion_evidence=True, 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:
objectOptions 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)
process_teardown (ProcessTeardown | 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)
master_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)
requires_completion_evidence (bool)
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, master_prompt_file=None, waiting_listener=None, pre_output_listener=None, permission_prompt_listener=None, required_artifact=None, requires_completion_evidence=True, connectivity_state_provider=None, is_waiting_state_provider=None)[source]
Bases:
objectNon-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)
master_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)
requires_completion_evidence (bool)
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:
AgentInvocationErrorRaised 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_idso the failure classifier’s typed-cause branch can thread it through theFailureCategory.AGENTresume 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, andtranscript_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_callandelapsed_seconds) are ALSO surfaced in the exception message so a logged traceback is actionable. The verbose fields (last_evidence_summaryandtranscript_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:
AgentInvocationErrorRaised when Pi exits cleanly after a length-limited model turn.
- Parameters:
agent_name (str)
- Return type:
None
- exception ralph.agents.invoke.PiProviderFailureExitError(agent_name, reason)[source]
Bases:
AgentInvocationErrorRaised when Pi exits rc=0 after its retry ladder failed outright.
Pi reports an unreachable provider on the message object (
stopReason='error'pluserrorMessage) and then exhausts its own bounded retry ladder before exiting cleanly. Without this error the completion gate reported only “no completion evidence”, which is a resumable verdict – so Ralph relaunched the same session against the same dead provider indefinitely.skip_same_agent_retriesis set because retrying the same agent is futile while its provider is down: the runner should fall over to the next agent instead.- Parameters:
agent_name (str)
reason (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:
objectResolved runtime configuration for a single agent invocation.
The optional
cleanuphook is invoked byinvoke_agentin itsfinallyblock after the agent subprocess has finished (success, failure, or cancellation). It is the documented release path for transport-specific resources allocated duringresolve()— the primary example is the per-invocation CodexCODEX_HOMEdirectory allocated byCodexRuntimeResolver(seeralph.mcp.transport.codex.release_codex_home).Lifetime contract:
cleanupMUST be safe to call exactly ONCE;invoke_agenttreats it as idempotent only via the implementation (e.g.release_codex_homereturnsFalseon a second call without raising, so it is safe even if a caller races the finally block).cleanupMAY beNonefor resolvers that allocate no per-invocation resources (Claude, OpenCode, Nanocoder, Agy, Generic, Pi). Theinvoke_agentfinally block tolerates aNonehook.cleanupis 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_homepath 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:
RuntimeErrorRaised when MCP-backed execution is requested for an unsupported transport.
- class ralph.agents.invoke.WatchdogFireReason(*values)[source]
Bases:
StrEnumWhy 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:
objectMonitors 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
OTHERwith weight1.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]
- dispatch_event(event)[source]
Dispatch a watchdog-style event through the per-monitor handler.
Exposed for black-box tests that drive the handler directly with a synthetic event (FakeEvent). Production callers do not need this – the watchdog backend dispatches events into the handler returned by
_make_change_tracker(self)itself.The handler routes every event through
record_event(if it hassrc_path), mirroring the production watchdog dispatch path.- Parameters:
event (object) – A watchdog-style event object. The handler duck-types via the
_HasSrcPathprotocol so any object with the expected attribute is accepted.- Return type:
None
- 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_atcall). The watchdog’s per-channel evidence surface consumes this value via thelast_workspace_event_atfield onCorroborationSnapshot; a fresh workspace channel defers the NO_OUTPUT_DEADLINE verdict while the channel age is belowactivity_evidence_ttl_seconds.
- record_event(src_path)[source]
Record a file change event.
Classifies the event via the configured
WorkspaceChangeClassifier(or the legacyOTHER / 1.0fallback when no classifier is configured). Events with weight0.0are dropped without updatinglast_event_at, the counter, or invokingon_event. Events with weight1.0update the timestamp and counter and invokeon_eventwith 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_atfield onCorroborationSnapshotso a workspace-event channel is fresh exactly as long as the production clock is recent.When an
on_eventcallback has been registered (via the constructor orset_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 atry/exceptso 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
WorkspaceMonitorin a test fixture may have observed events from a prior case; calling this clears the timestamp so the nextrecord_eventproduces 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
WorkspaceMonitorBEFORE the per-run watchdog is created (the monitor is built ininvoke_agentwhile the watchdog lives inside the reader’sread_linesgenerator), so the constructor cannot bind the watchdog’srecord_workspace_eventdirectly. 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
Noneto 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 byrecord_eventso a buggy callback cannot break the file-event path.- Return type:
None
- start()[source]
Start monitoring the workspace for file changes.
Schedules exactly ONE recursive watchdog watch on the workspace root. A single recursive root watch is the minimal-stream option for macOS fseventsd: watchdog’s fsevents backend is OS-recursive (see watchdog/observers/fsevents.py lines 85-87 –
"fsevents defaults to be recursive, so if the watch was meant to be non-recursive then we need to drop all the events here"), so non-recursive subscriptions cannot reduce fseventsd delivery and only multiply overlapping streams. Activity-counting correctness is preserved byrecord_event’sweight == 0.0classify-drop backstop, which is independent of how many watchdog watches are scheduled.- 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.Lockand writes the merged config atomically (viaos.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.
A session that opts out via
requires_completion_evidence=Falseskips both checks: it holds neither an artifact contract nor theartifact.submitcapability behinddeclare_complete, so a clean exit is terminal.- 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
InvokeOptionsinstance withsession_idcleared.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_idand threaded an id forward.The
prior_session_idparameter is accepted for forward compatibility but MUST NOT be written back intosession_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
UpstreamMcpServerobjects found in Cursor config files. The Ralph entry is filtered out so it does not collide with the run-scopedralphinjection.- 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:
- ralph.agents.invoke.prepare_codex_home_with_upstreams(endpoint, *, workspace_path, existing_home, master_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)
master_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
resumethe prior session, request anew_session_with_id(e.g. on a stale-session error), or startfresh(no prior session to resume).The mapping is intentionally narrow and explicit:
AgentInactivityTimeoutError(with a prior session) ->resumeOpenCodeResumableExitError(with a prior session) ->resumetool-availability failure (with a prior session AND
reset_tool_registry=True) ->resume(NEW BEHAVIOR; the pre-fix code returnedfreshhere, which made every tool-availability retry re-read the prompt).stale/invalid session id family (with a prior session) ->
fresheverything 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 viaRestartAwareMcpBridge.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, master_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
configandextra_env. Returns aResolvedInvocationRuntimewhose 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)
master_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_flagtemplate; 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_sessionis False; must be non-empty whenhas_prior_sessionis 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_actionis unknown orhas_prior_session=Truebutprior_session_idis 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:
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. RaisesValueErrorfor unknown names.AgentParser— the protocol that all parsers implement; definesparse.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.1.9 --output-format stream-json 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. The smoke harness instead treats the expected workspace file
write as authoritative AGY tool activity; model-authored artifact claims are
never trusted for that check.
Behaviour specifics:
A single plain-text line is buffered, then emitted at iterator exhaustion (or at the next paragraph-boundary flush) as a single
textevent. 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 requires the durable run-scoped sentinel written bydeclare_complete, so transcript text cannot spoof completion.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:
NdjsonParserBaseParser for AGY stream-json output with a plain-text compatibility fallback.
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 astype='text'and coalesced viaTextAccumulatorinto 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, data.message, or name field 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_typeis 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.claude¶
Parser for Claude’s NDJSON streaming format.
- class ralph.agents.parsers.claude.ClaudeParser(subagent_pid_registry=None, subagent_source_label=None)[source]¶
Bases:
NdjsonParserBaseParser 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\nparagraph boundary (incremental surfacing of long responses)Thinking deltas (
thinking_delta) are accumulated separately from text deltas and emitted astype="thinking"lines.Inherits from
NdjsonParserBaseand delegates the NDJSON scaffolding (data:strip,[DONE]short-circuit, JSON parse, error extraction, lifecycle interception) to the base layer viaclassify_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 returnsNone._handle_lifecycle_event()carries the claude-specific lifecycle side effects (message_startrecording,message_stopflush,content_block_stopflush) and returnsNonefor 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) toAgentOutputLinetypes 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:
Strip the line and short-circuit on empty.
Try the claude-specific prefixed-transcript parser (e.g.
claude/sonnet: hello). If it returns a non-None list, yield from it and return.Delegate the remaining NDJSON path to
NdjsonParserBasewhich owns thedata: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’sassistant/user/thinkingevents 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:
NdjsonParserBaseParser 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\nparagraph boundary (incremental surfacing of long responses) - Iterator exhaustion (final flush viaflush_accumulators())Inherits from
NdjsonParserBasewhich owns thedata:strip,[DONE]short-circuit, JSON parse dispatch, lifecycle suppression, and error extraction. The subclass_dispatch_json_objectdelegates to_CodexDispatchfor 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 themessageorcontentfield. Surfaced asAgentOutputLine(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 amessageobject with acontentarray of typed blocks. Blocks of typetextsurface astype='text'viaTextAccumulatorcoalescing; blocks of typethinkingsurface astype='thinking'.
thinking- standalone thinking event carrying atextorthinkingdelta. Surfaced astype='thinking'.
tool_call- tool invocation event carrying atoolName(orname) andargs. Surfaced astype='tool_use'so the watchdog sees real tool activity.
tool_result- tool result event carrying the tool name and aresult(oroutputorcontent) field. Surfaced astype='tool_result'for the success path; when the event carriesis_error=true(orerror) it is surfaced astype='error'so the watchdog can see the failure.
result- the documented end-of-turn marker. Surfaced astype='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:
NdjsonParserBaseParser for the Cursor Agent CLI
--output-format stream-jsonwire 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
NdjsonParserBasewhich 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_objectdelegates to_CursorDispatchfor 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:
NdjsonParserBaseParser 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\nparagraph boundary (incremental surfacing of long responses) - Iterator exhaustion (final flush viaflush_accumulators())Inherits from
NdjsonParserBasewhich owns thedata:strip,[DONE]short-circuit, JSON parse dispatch, lifecycle suppression, and error extraction. The subclass_dispatch_json_objectdelegates to_GeminiDispatchfor 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:
NdjsonParserBaseGeneric 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\nparagraph boundary (incremental surfacing of long responses) - Stop/done markers (end of message) - Iterator exhaustion (final flush viaflush_accumulators())Short content (below threshold) that doesn’t end with
\n\nis 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
NdjsonParserBasewhich owns thedata: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: NAMEconvention 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:
GenericParserParser 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
rawevents, 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:
NdjsonParserBaseParser 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\nparagraph boundary (incremental surfacing of long responses) - Iterator exhaustion (final flush viaflush_accumulators())Inherits from
NdjsonParserBasewhich owns thedata:strip,[DONE]short-circuit, JSON parse dispatch, lifecycle suppression, and error extraction. The subclass_dispatch_json_objectdelegates to_OpenCodeDispatchfor 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_endturn lifecycle:
turn_start,turn_endmessage lifecycle:
message_start(in LIFECYCLE_EVENT_TYPES so the base would suppress it; PiParser overrides_handle_lifecycle_event()to fall through to_dispatch_json_objectfor every event type, and the dispatcher marksmessage_startsilent),message_update(carriesassistantMessageEvent),message_endtool execution:
tool_execution_start,tool_execution_update,tool_execution_end(withisErrorboolean)queue:
queue_updatecompaction:
compaction_start,compaction_endauto-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 totype='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:
NdjsonParserBaseParser for pi.dev’s AgentSessionEvent NDJSON streaming format.
Text deltas are accumulated into coherent blocks before emission. The terminal snapshot (
text_endcontent or themessage_endmessage.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 bycontentIndex) to avoid duplicate emissions when streaming deltas,text_end, and themessage_endsnapshot all reference the same content.Flushing happens on:
message_updatewithassistantMessageEvent.type == 'text_end'message_end(when notext_endwas 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_endcontent ormessage_endmessage.content thinking block).The single consistent isError rule:
tool_execution_end.isError=truemaps totype='error';isError=false(or absent) maps totype='tool_result'.The
message_endcontent array is walked for text, thinking, toolCall, and toolResult blocks. Text and thinking blocks honor the per-block terminal-snapshot rule: if the corresponding*_endsnapshot was already emitted for a givencontentIndex, themessage_endblock at that block index is skipped. Other text/thinking blocks (whosecontentIndexhas 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 frommessage_end.Inherits from
NdjsonParserBasewhich owns the 6 shared NDJSON behaviors. The subclass_dispatch_json_objectdelegates to_PiDispatchfor 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 aftermessage_endso the next message can emit its own terminal snapshots. The set is keyed by thecontentIndexof the streaming*_endevent so the nextmessage_endcan 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:
objectConvert 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 fromParserTemplateBase). The hook fires ONLY for the four_EMITTABLE_TYPESkinds 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, andthinking:<first-80-chars>for thinking blocks.- Parameters:
line (AgentOutputLine)
sink (Callable[[str], None])
- Return type:
None
- class ralph.agents.parsers.claude_interactive.ClaudeInteractiveTranscriptParser(*, max_buffer_chars=1048576)[source]¶
Bases:
objectExtract 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.
- Parameters:
max_buffer_chars (int)
ralph.agents.parsers.claude_interactive_transcript_parser¶
Semantic parser for VT-normalized Claude interactive transcripts.
- class ralph.agents.parsers.claude_interactive_transcript_parser.ClaudeInteractiveTranscriptParser(*, max_buffer_chars=1048576)[source]¶
Bases:
objectExtract 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.
- Parameters:
max_buffer_chars (int)
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>, timestamp=None)[source]¶
Bases:
objectLegacy 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])
timestamp (str | None)
- 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]
- timestamp¶
Optional ISO-8601 timestamp the parser extracted from the source event (DA-002 / wt-028-display S-2). When present, the downstream
ParallelDisplay.emit_parsed_event()uses it as the authoritative source-time stamp on the rendered record so a fixture or replay preserves the original event’s time end-to-end instead of falling back to the display clock.Nonekeeps the pre-fix behavior (the display clock stamps the line).- Type:
str | None
ralph.agents.parsers.interactive_transcript_event¶
Semantic event model for interactive Claude transcript parsing.
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:
objectShared 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]
- append_delta(text, raw, *, kind='text')[source]¶
Append a delta and flush only when either accumulation cap is exceeded.
- Parameters:
text (str)
raw (str)
kind (str)
- 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 viaAgentRegistry.from_config()AgentRegistry.from_config— build a registry from aralph.config.models.UnifiedConfig, layering user-global, project-local, and CLI overrides in the correct precedence orderbuiltin_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 providedAgentSpec— the internal declarative record that backs everyAgentConfigin the registry (seeralph.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 anAgentRegistrywith the catalog injected. The registry does not auto-seed at module import; you opt in by callingAgentRegistry(...)orAgentRegistry.from_config(...).You are debugging a routing failure. The registry is what
ralph.pipeline.orchestratorconsults 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 probesPATH.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.6-flash-low). 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 constructedAgentRegistry(catalog=...)seeds from the injected catalog via_seed_catalog_with_builtins. A bareAgentRegistry()does not seed; pass a catalog or callfrom_config.
Testing notes:
ralph.testing.fake_agent_executor.FakeAgentExecutorswaps 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:
objectRegistry 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
SubagentPidRegistryis created per invocation and threaded into both the execution strategy (viasubagent_pid_source=) and the parser (viasubagent_pid_registry=) so any PID registered by either layer becomes visible toProcessMonitor.spawned_subagent_count().The per-transport factory helpers in
ralph.process.monitor._subagent_pid_source_providerswrap the shared registry to expose aSubagentPidSourcethat filters by transport source label. OpenCode’sChildLivenessSubagentPidSourcecontinues to use its ownChildLivenessRegistry(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
AgentCatalogbound to this registry.When no catalog is injected at construction time, the registry falls back to
ralph.agents.catalog.default_catalog().register_agent_supportuses this property to write into the caller-owned catalog only, so a freshAgentRegistry(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:
- 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
- ralph.agents.registry.agy_alias_help()[source]¶
Return the actionable AGY alias vocabulary for operator-facing failures.
- Return type:
str
- ralph.agents.registry.agy_published_models()[source]¶
Return currently published AGY IDs, falling back to the measured v1.1.8 pin.
- Return type:
tuple[str, …]
- ralph.agents.registry.agy_reasoning_efforts()[source]¶
Return the effort vocabulary published by AGY’s v1.1.8 help output.
- Return type:
tuple[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:
objectAgentExecutor 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_logsso the memory the log holds (up toDEFAULT_MAX_OVERFLOW_FILE_BYTESper unit) is released when the unit is no longer needed. Callsclose()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:
ProtocolProtocol for wall-clock operations used by the timeout subsystem.
- 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:
objectTest Clock: advances logical time synchronously without real waits.
- Parameters:
start (float)
- class ralph.agents.timeout_clock.SystemClock[source]¶
Bases:
ClockProduction Clock: uses real wall-clock time.
- 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 transport and tool-bridge package.
This package exposes transport abstractions, tool registration, and access-mode
helpers used by both the pipeline and standalone ralph-mcp runtime.
ralph.mcp.artifacts¶
Markdown artifact validation and canonical persistence for MCP.
This sub-package contains the canonical Markdown submission backend, file backend, per-type validators, and audit adapter used by MCP tool handlers.
ralph.mcp.artifacts.audit_adapter¶
Audit adapter utilities for MCP records.
- class ralph.mcp.artifacts.audit_adapter.AgentSessionId(value)[source]¶
Bases:
objectConvenience 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:
objectCorrelation 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:
objectExtended metadata attached to an MCP audit record.
- Parameters:
event_type (McpAuditEventType)
details (str | None)
correlation (McpAuditCorrelation)
- class ralph.mcp.artifacts.audit_adapter.McpAuditCorrelation(run_id=None, generation=None, drain=None, policy_mode=None)[source]¶
Bases:
objectCorrelation 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:
StrEnumEnumeration 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:
objectAudit record emitted by the MCP server dispatch layer.
- Parameters:
timestamp_nanos (int)
session_id (str)
tool_name (str)
decision (AccessDecision)
path (str | None)
capability (McpCapability | None)
metadata (AuditMetadata)
- 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:
objectAudit 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:
objectAdapter that buffers Ralph audit records produced by MCP.
wt-024 memory-perf AC-02: the
_recordsbuffer is bounded by a constructor-injectedcap(default_DEFAULT_AUDIT_RECORD_CAP = 4096) usingcollections.deque(maxlen=cap).deque.appendhonorsmaxlenby evicting the OLDEST record FIFO — the same pattern already used byBoundedLinesQueue,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,69and any production wiring) keep working unchanged becausecapdefaults to the production cap.flush()is now Protocol-correct: it returnsNone(per theAuditSinkProtocoldef 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 adrain_records()call without any production caller callingdrain_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
Noneper theAuditSinkProtocol).Clears the FIFO buffer so buffered memory is released without requiring a caller to first
drain_records(). ReturnsNone(does NOT return records — that would violate the Protocol’sdef flush(self) -> Nonesignature).- 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:
- 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:
ralph.mcp.artifacts.canonical_submit¶
Canonical persistence for markdown artifact documents.
- class ralph.mcp.artifacts.canonical_submit.SubmitResult(artifact_path, receipt_path, handoff_path, artifact_type, run_id)[source]¶
Bases:
objectLocations produced by one canonical markdown submission.
- Parameters:
artifact_path (Path | None)
receipt_path (Path | None)
handoff_path (Path | None)
artifact_type (str)
run_id (str | None)
- ralph.mcp.artifacts.canonical_submit.promote_fallback_artifact(workspace_root, artifact_type, *, deps=None, run_id=None, receipt_secret=None, fallback_path=None, artifact_dir=None, handoff_dir=None)[source]¶
Promote an agent-written
.agent/tmp/<type>.mdfallback through canonical submit.Returns
Nonewhen no fallback document exists, the artifact type has no registered markdown spec, or the document fails markdown validation — an invalid fallback must not stamp a submission receipt.- Parameters:
workspace_root (Path)
artifact_type (str)
deps (ArtifactHandlerDeps | None)
run_id (str | None)
receipt_secret (str | None)
fallback_path (Path | None)
artifact_dir (Path | None)
handoff_dir (Path | None)
- Return type:
SubmitResult | None
- ralph.mcp.artifacts.canonical_submit.submit_artifact_canonical(workspace_root, artifact_type, parsed_content, *, markdown=None, deps=None, run_id=None, artifact_dir=None, handoff_dir=None)[source]¶
Persist validated markdown, handoff, and receipt as one logical transaction.
parsed_contentis retained only for callers that need to validate before persistence; the stored artifact is always.mdand never a JSON envelope. If receipt persistence fails, canonical files and any history snapshot are restored to their pre-submit state before the persistence error propagates. Phase completion remains a separate explicitdeclare_completeoperation.- Parameters:
workspace_root (Path)
artifact_type (str)
parsed_content (dict[str, object])
markdown (str | None)
deps (ArtifactHandlerDeps | None)
run_id (str | None)
artifact_dir (Path | None)
handoff_dir (Path | None)
- Return type:
ralph.mcp.artifacts.commit_message¶
Commit-message artifact helpers.
The canonical commit message is the markdown artifact the agent submits to
.agent/artifacts/commit_message.md (written by
ralph.mcp.artifacts.canonical_submit). The document declares its
commit or skip variant in frontmatter and is validated by the registered
commit_message markdown spec; these helpers read that document and render
the plain-text commit message consumers pass to git.
- ralph.mcp.artifacts.commit_message.commit_message_artifact_path(repo_root)[source]¶
Return the canonical markdown artifact path for the given repo root.
- 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 the canonical commit-message artifact.
- Parameters:
repo_root (Path)
backend (FileBackend)
- 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 (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 markdown artifact.
- Parameters:
repo_root (Path)
backend (FileBackend)
- 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 a markdown artifact document at an arbitrary path.
- Parameters:
message_file (Path)
backend (FileBackend)
- 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 a markdown artifact document.
- Parameters:
message_file (Path)
backend (FileBackend)
- Return type:
dict[str, object] | 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 remains both a read fallback during migration and a durable
write fallback when the database cannot persist the receipt.
- 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:
RuntimeErrorRaised when both the RunStateDB and legacy-file paths fail to persist a receipt.
Without this guard,
write_artifact_receiptreturns 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. Canonical submission propagates this exception upward and rolls back the in-flight artifact and handoff writes.
- 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.dbfirst (RFC-013 P3). When the DB has no row, falls back to the legacy file path.agent/receipts/<run_id>/<artifact_type>.jsonso receipts left behind by the pre-upgrade release are still honored during the dual-read window.When
receipt_secretis provided the stored HMAC is verified against(run_id, artifact_type); a receipt that exists but fails HMAC verification returnsFalse. 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_idnever 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.dbdoes not block the call (the legacy file cleanup still proceeds). The DB clear itself is also best-effort — a transientsqlite3.ErrorduringRunStateDB.clear_run_receiptsis 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_typewas durably persisted duringrun_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_secretis provided the receipt includes ahmacfield 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. When the DB write succeeds, no legacy file is created. The legacy.agent/receipts/<run_id>/<artifact_type>.jsonpath remains a migration read fallback and the durable write fallback when the database is unavailable.Durable-fallback: when
RunStateDBraisesOSError,RuntimeError, orsqlite3.Erroron 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 explicitbackendkwargs still works becausebackendcontinues to control where the legacy bytes land (seeFailingBackendpattern). The HMAC is included in both stores whenreceipt_secretis provided.- Returns:
the workspace
.agent/state.dbpath for a successful database write, or the legacy receipt file path when the durable fallback was required.- Return type:
The durable store that accepted the receipt
- Parameters:
workspace_root (Path)
run_id (str)
artifact_type (str)
backend (FileBackend)
receipt_secret (str | 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:
BaseModelEvidence 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:
BaseModelReference 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:
BaseModelValidated schema for a development_result artifact.
- Parameters:
status (Annotated[str, MinLen(min_length=1)])
summary (str)
files_changed (str)
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].
- validate_status_requirements()[source]¶
Enforce the reporting contract only for a
completedresult.statuskeeps its closed vocabulary because routing reads it, but a non-completedresult carries no completion claim to check: everything below the frontmatter is free-form prose the next iteration reads, never a structure this validator gates.- Return type:
- exception ralph.mcp.artifacts.development_result.DevelopmentResultValidationError[source]¶
Bases:
ValueErrorRaised when a development_result artifact is malformed.
- class ralph.mcp.artifacts.development_result.PlanItemProof(*, plan_item, proof)[source]¶
Bases:
BaseModelEvidence 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.file_backend¶
Shared file backend abstractions for MCP persistence.
- class ralph.mcp.artifacts.file_backend.FileBackend(*args, **kwargs)[source]¶
Bases:
ProtocolProtocol 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
- sync_directory(path)[source]¶
Synchronize a directory after publishing a renamed file.
- Parameters:
path (Path)
- Return type:
None
ralph.mcp.artifacts.format_docs¶
Bundled dumb-proof Markdown reference docs for artifact submission.
- ralph.mcp.artifacts.format_docs.EXAMPLE_ARTIFACT_TYPES: tuple[str, ...] = ('commit_message', 'commit_cleanup', 'development_result', 'issues', 'fix_result', 'development_analysis_decision', 'planning_analysis_decision', 'review_analysis_decision', 'policy_remediation_analysis_decision', 'smoke_test_result', 'run_time_report', 'product_spec', 'plan')¶
Artifact types that ship a full self-teaching example document alongside their format doc. Each example is a complete, validator-passing markdown artifact whose content also models the craft (a good plan, a good commit message, honest proof discipline, and so on).
- ralph.mcp.artifacts.format_docs.example_workspace_path(artifact_type)[source]¶
Return the workspace-relative path for a bundled example artifact.
- Parameters:
artifact_type (str)
- Return type:
str
- 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.load_bundled_example(artifact_type)[source]¶
Load a bundled example artifact document, or None if unknown.
- Parameters:
artifact_type (str)
- Return type:
str | None
- 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, examples, and the index into the workspace.
- Parameters:
workspace_root (Path)
backend (FileBackend)
- Return type:
list[str]
- ralph.mcp.artifacts.format_docs.materialize_example(workspace_root, artifact_type, *, backend=<ralph.mcp.artifacts._path_file_backend.PathFileBackend object>)[source]¶
Write a bundled example artifact 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_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:
workspace_root (Path)
backend (FileBackend)
- Return type:
str
ralph.mcp.artifacts.idempotent_write¶
Idempotent write primitive for byte-identical filesystem mutations.
Exposes two behavior-preserving helpers:
write_text_if_changed()— direct write with read-compare skip.atomic_write_text_if_changed()— atomic temp + replace with read-compare skip on the destination.
Both helpers skip a byte-identical rewrite of an existing file,
keeping the filesystem mutation rate down (notably macOS fseventsd
load) without altering any observable behavior: the file still
contains content after the call.
The helpers are intentionally fail-open: any read uncertainty
(OSError on read_text, a missing path, or a content
mismatch) falls through to a real write so the post-condition
“file contains content” always holds and a partial or corrupt
prior file self-heals on the next write.
The helpers do NOT create parent directories — mkdir stays
the caller’s responsibility so existing directory-creation
semantics are unchanged at every converted call site.
The atomic helper writes content to a caller-supplied temporary
path and then delegates to backend.replace to move it on top of
destination. A byte-identical destination short-circuits
both the temp write and the replace so no filesystem mutation
occurs.
- ralph.mcp.artifacts.idempotent_write.atomic_write_text_if_changed(backend, destination, content, *, tmp_path, encoding='utf-8', sync_directory=False)[source]¶
Atomic temp+replace write of
contenttodestination, skipping on identity.Writes
contenttotmp_paththen delegates tobackend.replace(tmp_path, destination). Mirrorswrite_text_if_changed()on the destination so a byte-identical existing destination short-circuits both the temp write and the replace.- Returns:
Truewhen a physical write (and replace) was performed,Falsewhendestinationalready contained byte-identical content and the helper skipped.- Parameters:
backend (FileBackend)
destination (Path)
content (str)
tmp_path (Path)
encoding (str)
sync_directory (bool)
- Return type:
bool
- Behavior:
If
backend.exists(destination)isFalse: writestmp_path, replaces ontodestination, returnsTrue.If
backend.read_text(destination)raisesOSError: writestmp_path, replaces ontodestination, returnsTrue(fail-open — partial/corrupt prior files self-heal on the next call).If the read-back content equals
content: returnsFalsewithout callingbackend.write_textorbackend.replace(the skip).Otherwise: writes
tmp_path, replaces ontodestination, returnsTrue.
The helper never creates parent directories.
mkdiris the caller’s responsibility so directory-creation semantics at every converted call site are unchanged.
- ralph.mcp.artifacts.idempotent_write.write_text_if_changed(backend, path, content, *, encoding='utf-8')[source]¶
Write
contenttopathonly when it differs from the existing bytes.- Returns:
Truewhen a physical write was performed,Falsewhen the existing file already contained byte-identical content and the write was skipped.- Parameters:
backend (FileBackend)
path (Path)
content (str)
encoding (str)
- Return type:
bool
- Behavior:
If
backend.exists(path)isFalse: writes and returnsTrue.If
backend.read_text(path)raisesOSError: writes and returnsTrue(fail-open — partial/corrupt prior files self-heal on the next call).If the read-back content equals
content: returnsFalsewithout callingbackend.write_text(the skip).Otherwise: writes and returns
True.
The helper never creates parent directories.
mkdiris the caller’s responsibility so directory-creation semantics at every converted call site are unchanged.
ralph.mcp.artifacts.handoffs¶
Agent/user-facing Markdown handoff path mapping.
Markdown artifacts are Ralph’s source of truth. Submission
(ralph.mcp.artifacts.canonical_submit) writes the validated markdown
document to .agent/artifacts/<type>.md and byte-copies it to the handoff
path declared here, so downstream agents and users read the submitted document
directly — there is no derivation or rendering step.
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 Markdown artifact 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>.md – archived canonical Markdown index.md – human-readable summary of archived entries
The canonical latest files (.agent/artifacts/plan.md, .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 Markdown artifact and its 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 (Markdown 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.md_draft_io¶
On-disk persistence for staged markdown artifact drafts.
A draft is one plain markdown file per artifact type at
<artifact_dir>/.<artifact_type>.draft.md that accumulates staged
content during incremental authoring. Reads and writes go through a
FileBackend so production code can be unit-tested with an in-memory
backend, and the draft survives an MCP server restart (resumability).
Writes are atomic (temp write + replace).
- ralph.mcp.artifacts.md_draft_io.DEFAULT_MD_DRAFT_CHARACTER_CAP: int = 4000000¶
Fallback character cap for artifact types whose markdown spec does not pin
max_characters. MatchesPlanSizeLimits.DEFAULT.max_total_bytes(the 4 MB artifact payload cap) so staging cannot outgrow submission.
- ralph.mcp.artifacts.md_draft_io.delete_md_draft(artifact_dir, artifact_type, *, backend=<ralph.mcp.artifacts._path_file_backend.PathFileBackend object>)[source]¶
Remove the staged draft. Returns True if one existed.
- Parameters:
artifact_dir (Path)
artifact_type (str)
backend (FileBackend)
- Return type:
bool
- ralph.mcp.artifacts.md_draft_io.is_md_draft_seeded(artifact_dir, artifact_type, *, backend=<ralph.mcp.artifacts._path_file_backend.PathFileBackend object>)[source]¶
Return whether a draft was reconstructed from canonical content.
- Parameters:
artifact_dir (Path)
artifact_type (str)
backend (FileBackend)
- Return type:
bool
- ralph.mcp.artifacts.md_draft_io.load_md_draft(artifact_dir, artifact_type, *, backend=<ralph.mcp.artifacts._path_file_backend.PathFileBackend object>)[source]¶
Read the staged draft if present and readable. None otherwise.
- Parameters:
artifact_dir (Path)
artifact_type (str)
backend (FileBackend)
- Return type:
str | None
- ralph.mcp.artifacts.md_draft_io.mark_md_draft_seeded(artifact_dir, artifact_type, *, backend=<ralph.mcp.artifacts._path_file_backend.PathFileBackend object>)[source]¶
Mark a draft copied from canonical content rather than authored incrementally.
- Parameters:
artifact_dir (Path)
artifact_type (str)
backend (FileBackend)
- Return type:
None
- ralph.mcp.artifacts.md_draft_io.md_draft_character_cap(spec)[source]¶
Return the staging cap: the spec’s own cap when set, else the 4 MB default.
- Parameters:
spec (MdArtifactSpec)
- Return type:
int
- ralph.mcp.artifacts.md_draft_io.md_draft_path(artifact_dir, artifact_type)[source]¶
Return the canonical draft path for one artifact type.
- Parameters:
artifact_dir (Path)
artifact_type (str)
- Return type:
Path
- ralph.mcp.artifacts.md_draft_io.save_md_draft(artifact_dir, artifact_type, content, *, backend=<ralph.mcp.artifacts._path_file_backend.PathFileBackend object>)[source]¶
Atomically persist the staged draft (temp write + replace).
- Parameters:
artifact_dir (Path)
artifact_type (str)
content (str)
backend (FileBackend)
- Return type:
None
- ralph.mcp.artifacts.md_draft_io.seeded_draft_workspace_path(artifact_type)[source]¶
Return the workspace-relative provenance marker for a seeded draft.
- Parameters:
artifact_type (str)
- Return type:
str
- ralph.mcp.artifacts.md_draft_io.unsubmitted_draft_divergence(artifact_dir, artifact_type, canonical_path, *, backend=<ralph.mcp.artifacts._path_file_backend.PathFileBackend object>)[source]¶
Return divergence when authored draft content was never submitted.
Missing drafts and canonical files are handled by their existing completion gates, so this comparison only reports a readable draft ahead of a readable canonical document.
- Parameters:
artifact_dir (Path)
artifact_type (str)
canonical_path (Path)
backend (FileBackend)
- Return type:
UnsubmittedDraftDivergence | None
ralph.mcp.artifacts.plan¶
Structured planning artifact validation helpers.
- Module family (reading order):
_section_models->_section_registry->_validation->_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, expected_outcome=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)])
expected_outcome (Annotated[str | None, MaxLen(max_length=8000)])
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=<factory>, reference_files=<factory>)[source]¶
Bases:
BaseModelPrimary and reference files that define the plan’s surface area.
- Parameters:
primary_files (Annotated[list[CriticalPrimaryFile], 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 (Annotated[str, MinLen(min_length=1), MaxLen(max_length=200)])
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:
BaseModelDesign 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.
planning_profileis descriptive and never fabricates missing sub-sections or project-specific execution requirements.- Parameters:
planning_profile (Annotated[PlanningProfile | None, MinLen(min_length=1), MaxLen(max_length=200)])
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:
BaseModelA 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='action', priority=None, targets=<factory>, location=None, rationale=None, depends_on=<factory>, satisfies=<factory>, expected_evidence=<factory>, verify_command=None, expected_outcome=None)[source]¶
Bases:
BaseModelOne 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_typefield determines which extra fields are required: afile_changestep must declaretargets, while averifystep must declare eitherverify_commandorlocation.- Parameters:
number (Annotated[int, Ge(ge=1)])
title (Annotated[str, MinLen(min_length=1), MaxLen(max_length=500)])
content (Annotated[str, MaxLen(max_length=20000)])
step_type (Annotated[str, MinLen(min_length=1), MaxLen(max_length=200)])
priority (Annotated[str | None, MinLen(min_length=1), MaxLen(max_length=200)])
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)])
expected_outcome (Annotated[str | None, MaxLen(max_length=8000)])
- 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:
BaseModelA single identified risk and its mitigating action.
- Parameters:
risk (Annotated[str, MinLen(min_length=1), MaxLen(max_length=8000)])
mitigation (Annotated[str, MaxLen(max_length=8000)])
severity (Annotated[str | None, MinLen(min_length=1), 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.ScopeItem(*, text, count=None, category=None)[source]¶
Bases:
BaseModelA 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 (Annotated[ScopeCategory | None, MinLen(min_length=1), 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.SkillsMcp(*, skills=<factory>, mcps=<factory>)[source]¶
Bases:
BaseModelRequired skills and optional MCP servers for executing the plan.
- Parameters:
skills (Annotated[list[str], 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 (Annotated[str, MinLen(min_length=1), 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.Summary(*, context='', intent='', intent_verb='', scope_items=<factory>, coverage_areas=<factory>)[source]¶
Bases:
BaseModelSummary section of a plan artifact.
Captures the user-facing context, descriptive intent hints, scope items, and coverage areas. None of these vocabulary choices controls execution.
- Parameters:
context (Annotated[str, MaxLen(max_length=8000)])
intent (Annotated[str, MaxLen(max_length=500)])
intent_verb (Annotated[str, MaxLen(max_length=200)])
scope_items (Annotated[list[ScopeItem], MaxLen(max_length=200)])
coverage_areas (Annotated[list[CoverageArea], 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.smoke_test_result¶
Structured smoke_test_result artifact validation helpers.
- exception ralph.mcp.artifacts.smoke_test_result.SmokeTestResultValidationError[source]¶
Bases:
ValueErrorRaised 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
Nonewhen the artifact file does not exist, cannot be read, or fails schema validation againstSmokeTestResult. 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:
BaseModelValidated 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:
ExceptionRaised 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 and validate the canonical Markdown product specification.
- 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:
ValueErrorRaised 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.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 Markdown, 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.hmacwhen adelete_completion_sentinelcall raisessqlite3.Errorso 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:
objectHandle 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_sentinelraisessqlite3.Error(locked / corrupt / unsupported WAL): physically removing the row is best-effort, but the read path must observe the cleared state so a reusedrun_idcannot inherit a previous run’s “completed” verdict._db_sentinel_lookuprecognisesCLEARED_SENTINEL_HMACand returns(False, None)so_check_completion_sentinelrejects completion without consulting a stale legacy file. A successful upsert here replaces any existing row (including a valid HMAC row), so even if a future retry ofdelete_completion_sentinelfails 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_idis provided, rows for that run are skipped regardless of age — mirrors the file-pathkeep_run_idcontract 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.artifacts.markdown¶
Closed, pure markdown grammar support for MCP artifacts.
ralph.mcp.artifacts.markdown.registry¶
Explicit registry shared by markdown submission and check-only callers.
- ralph.mcp.artifacts.markdown.registry.get_spec(artifact_type)[source]¶
Return the registered spec or a clear unsupported-type error.
- Parameters:
artifact_type (str)
- Return type:
MdArtifactSpec
ralph.mcp.artifacts.markdown.specs¶
Registered markdown artifact specifications.
Markdown specs for planning, development, and review analysis decisions.
Consumed structure (stays strict): frontmatter type and status.
status keeps its closed decision vocabulary (completed |
request_changes | failed — it routes the pipeline, so a wrong
status is a hard error naming the valid values), and How To Fix item
IDs feed downstream proof references. Section bodies are descriptive
and tolerate multi-line prose and unknown continuation lines.
Markdown specification for commit-cleanup artifacts.
Markdown specification for commit-message artifacts.
Markdown mapping and validation rules for development_result artifacts.
Frontmatter status always has the closed vocabulary completed |
partial (routing and continuation prompts read it — a wrong status
such as done is a hard error naming the valid values). Everything
below the frontmatter is validated only for a completed result,
because only a completion claim is checkable: the required-section
skeleton, the Plan Items Proven / Analysis Items Addressed item
IDs (proof gating cross-references them) and the Continuation
session ID. A non-completed result is free-form prose — no section
is required or shaped, and it is mapped best-effort so the next
iteration can read whatever the agent managed to write.
Within a completed body the rest stays descriptive: sections
tolerate multi-line prose and unknown Key: value continuation lines
under items.
Markdown spec for fix results.
Consumed structure (stays strict): frontmatter type and the
required-section skeleton. All section content is descriptive (no
pipeline consumer reads individual fields), so bodies tolerate
multi-line prose and unknown continuation lines under items.
Markdown spec for review issues.
Consumed structure (stays strict): frontmatter type and status
(closed vocabulary issues_found | no_issues — review routing
reads it, so a wrong status is a hard error naming the valid values),
the required-section skeleton, and the path | severity | summary
shape of Issues items. Per-issue severity is descriptive (no
consumer gates on it), so unknown severities are preserved.
Section bodies tolerate multi-line prose and unknown continuation
lines under items.
ralph.mcp.artifacts.markdown.specs.run_time_report¶
Markdown specification for a bounded, comparable run-time report.
Markdown mapping and validation rules for plan artifacts.
The plan grammar is deliberately shape-free. Conventional headings, order,
and prose are recommendations rather than validation requirements. PLAN001
is the sole blocking diagnostic: it identifies a submission that is not a plan,
so analysis and execution have nothing to consume. All other findings,
including parsed anchors and consumed references, are advisory.
Grammar summary (three field kinds, closed key sets per context — see
ralph.mcp.artifacts.markdown._fields):
Field: value— single-value field.Field: a, b— inline comma-separated list (IDs, names, enum words).Field:followed by- entrybullets — list of prose/path entries.
Steps are ### [S-n] Title blocks under any ## heading. Work-unit
sections may repeat so each unit can own a complete nested mini-plan.
Consumed-structure map:
PARSED AND CONSUMED —
### [S-n]step IDs,Depends on:/Satisfied by:references, step-type fields, and## Parallel Plan/## Work Unitsmarkers are mapped best-effort for proof and fan-out consumers. Violations remain actionable advisory diagnostics, never blockers.DESCRIPTIVE — headings, ordering, prose, labels, and recommended vocabularies are likewise advisory. Unknown or repeated conventional sections remain valid.
- ralph.mcp.artifacts.markdown.specs.plan.analyze_plan_document(text)[source]¶
Parse and analyze a plan markdown document, returning the override ledger.
Companion to
parse_and_validate()that also surfaces the validation override ledger. Tool handlers that needcountsandoverriddenin the response payload use this entry point. The diagnostic list reflects post-override state: matched advisory diagnostics are removed and the recorded reason appears inoverriddeninstead. Stale and error-targeted overrides are surfaced back into the diagnostic list as PLAN025/PLAN026.Parser-originated diagnostics (MD005, MD006, MD007) are retained in the returned list so a malformed document fails before canonical mapping the same way
parse_and_validate()does; the closed vocabulary teach pass runs here too sotypefrontmatter duplicates carry the consumer phrase the rest of the message convention uses. MD002 and MD001 are demoted to warning by the plan-scoped severity policy so a malformed document that is recognizably a plan still reaches the canonical mapper.The closed-list not-a-plan detector runs early: if the document is empty, markup-only, under the 100-character content floor, or recognizably some other kind of text that arrived in the plan’s place, the only diagnostics in the returned list are PLAN001 errors and the canonical content is
{}.- Parameters:
text (str)
- Return type:
tuple[Content, list[Diagnostic], list[_OverrideMatch]]
Markdown spec for product specifications.
Markdown spec for smoke-test results.
Consumed structure (stays strict): frontmatter type, status
(closed vocabulary passed | failed | partial — a wrong
status is a hard error naming the valid values), output_file, and
the required-section skeleton. Section bodies are descriptive and
tolerate multi-line prose and unknown continuation lines under items.
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:
objectResult of an MCP access decision.
- Parameters:
allowed (bool)
reason (str | None)
code (AccessDeniedCode | None)
- classmethod deny(reason, code)[source]¶
Build a deny decision.
- Parameters:
reason (str)
code (AccessDeniedCode)
- Return type:
- class ralph.mcp.protocol.capability_mapping.AccessDeniedCode(*values)[source]¶
Bases:
StrEnumCategorical access-denial codes.
- class ralph.mcp.protocol.capability_mapping.AccessMode(*values)[source]¶
Bases:
StrEnumServer access mode for MCP tool dispatch.
- class ralph.mcp.protocol.capability_mapping.Capability(*values)[source]¶
Bases:
StrEnumInternal Ralph capability vocabulary.
- class ralph.mcp.protocol.capability_mapping.DrainClass(*values)[source]¶
Bases:
StrEnumDrain class used for capability defaults.
- class ralph.mcp.protocol.capability_mapping.McpCapability(*values)[source]¶
Bases:
StrEnumTyped MCP capability vocabulary.
- class ralph.mcp.protocol.capability_mapping.PolicyMode(*values)[source]¶
Bases:
StrEnumRuntime policy mode enforced by the MCP server.
- class ralph.mcp.protocol.capability_mapping.PolicyOutcome(status, reason=None, restriction=None)[source]¶
Bases:
objectNormalized policy outcome payload.
- Parameters:
status (PolicyOutcomeStatus)
reason (str | None)
restriction (str | None)
- class ralph.mcp.protocol.capability_mapping.PolicyOutcomeStatus(*values)[source]¶
Bases:
StrEnumNormalized policy outcome status.
- class ralph.mcp.protocol.capability_mapping.SessionDrain(*values)[source]¶
Bases:
StrEnumPipeline 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:
- 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_policyand the drain must be declared there with an explicitdrain_class.- Parameters:
drain (SessionDrain | str)
agents_policy (AgentsPolicy | None)
- Return type:
- ralph.mcp.protocol.capability_mapping.drain_to_access_mode(drain, agents_policy=None)[source]¶
Determine the MCP access mode for a session drain.
- Parameters:
drain (SessionDrain | str)
agents_policy (AgentsPolicy | None)
- Return type:
- 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:
drain (SessionDrain | str)
agents_policy (AgentsPolicy | None)
- Return type:
- 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:
- ralph.mcp.protocol.capability_mapping.evaluate_workspace_write(ephemeral, tracked)[source]¶
Evaluate the composite workspace-write policy.
- Parameters:
ephemeral (object)
tracked (object)
- Return type:
- 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.env¶
Shared Ralph MCP environment variable names.
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, explore_index=None, exec_resource_resolver=None)[source]¶
Bases:
objectLightweight 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)
explore_index (object | None)
exec_resource_resolver (ExecResourceResolverLike | None)
- broker_secret: str | None = None¶
broker-owned secret threaded into the run-scoped receipt / completion sentinel HMAC.
Nonemeans 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
- exec_resource_resolver: ExecResourceResolverLike | None = None¶
optional resolver for
ralph://exec/<spill-name>resource URIs returned byformat=summaryexec calls. WhenNonetheresources/readhandler reports an unsupported URI error and the legacy contract is preserved. Production bridges attach one resolver inralph.pipeline.session_bridge.build_session_bridge()so spill files can be replayed throughresources/read.- Type:
AC-11
- explore_index: object | None = None¶
Optional handle to the per-workspace
ralph.mcp.explore.handlers.ExploreIndex. WhenNonethe workspace handlers fall back to the legacy live implementation; production bridges attach one inralph.pipeline.session_bridge.build_session_bridge()so all indexed read/search/grep/list/edit operations share the same generation and dirty-path state.
- 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:
ProtocolFull structural contract for MCP server session objects.
Both implementations — the in-memory
AgentSession(used by tests) and the productionFileBackedSession(standalone server viasession_from_env) — must satisfy this protocol.session_from_envreturns this type, somypy ralph/(run bymake 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.
Nonemeans the pre-P3 contract (no HMAC enforcement).Both implementations expose this as a read-only attribute:
AgentSessiondeclares it as a dataclass field (with a default ofNone) andFileBackedSessionexposes 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_identitywhen 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.
- property exec_resource_resolver: ExecResourceResolverLike | None¶
Optional
ralph.mcp.tools._exec_resource_uri.ExecResourceResolver.Nonekeeps the legacy contract:resources/readfor aralph://exec/<spill-name>URI returns a structured “unsupported resource URI” error. Production sessions built byralph.pipeline.session_bridge.build_session_bridge()attach one resolver so the AC-11 replayable stdout/stderr resource IDs returned byformat=summaryexec calls can be re-read throughresources/read.
- property explore_index: object | None¶
Optional
ralph.mcp.explore.handlers.ExploreIndexhandle.Nonekeeps the legacy contract: handlers fall back to the live implementation. Production sessions built byralph.pipeline.session_bridge.build_session_bridge()attach one handle so all indexed read/search/grep/list/edit operations share the same generation + dirty-path state.
- 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:
objectSession-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/listoutput 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:
- get(artifact_id)[source]¶
Retrieve a manifest entry by artifact_id, or None if not found.
- Parameters:
artifact_id (str)
- Return type:
ManifestEntry | None
- 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
Nonemeans “any thread” — used by single-tenant embeddings where the exec reader threads run on a different thread than the request thread; the production_FallbackHttpHandlerstamps the real request-thread ident.alias of
tuple[int|None, Callable[[dict[str, object]], None]]
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:
objectSupervision interval configuration for active MCP health monitoring.
- Parameters:
interval (timedelta)
- class ralph.mcp.protocol.startup.HttpEndpointTarget(address, host_header, path)[source]¶
Bases:
objectResolved 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:
ExceptionBase class for MCP preflight failures.
- exception ralph.mcp.protocol.startup.SessionBridgeError[source]¶
Bases:
ExceptionRaised 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 theralph.mcp.protocol.startupnamespace. 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
SessionDraininstance or a string. Unknown drain names fall through toREAD_ONLYbecause the resolver cannot determine that a write-capable class is allowed.agents_policy (AgentsPolicy | None) – Optional
AgentsPolicywhose declared drains should be consulted. WhenNone(the default), the resolver falls back to the default agent policy loaded byralph.mcp.protocol.capability_mapping, which is the conservative choice for callers that have not parsed anagents.tomlyet.
- Returns:
READ_WRITEwhen the drain’s class explicitly allows writes;READ_ONLYfor every other case (read-only drains, unknown drains, or an emptyagents_policy).- Return type:
- 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
PermanentPreflightErrorwhenerroris 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/listresult 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:
- ralph.mcp.protocol.startup.initialize_request()[source]¶
Build the JSON-RPC
initializerequest payload.- Return type:
dict[str, object]
- ralph.mcp.protocol.startup.initialized_notification()[source]¶
Build the JSON-RPC
notifications/initializedpayload.- 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
endpointends with the legacy/ssepath.- 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:
- 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:
endpoint_or_target (str | HttpEndpointTarget)
target_or_payload (HttpEndpointTarget | dict[str, object])
payload (dict[str, object] | None)
- 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
HttpEndpointTargetplus payload. Propagates themcp-session-idheader 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:
endpoint (str)
target (HttpEndpointTarget)
required_tools (Iterable[str])
remaining (timedelta)
post_with_session_fn (HttpJsonRpcWithSessionFn | None)
- 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
endpointwith a bounded timeout and no required tools.Wraps
preflight_http_attemptso the timeout is applied to every underlying HTTP call. Raises the same errors aspreflight_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.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:
objectRepresents 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:
objectMCP 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 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
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.
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:
McpServerFactoryBuild MCP server handles with dynamically allocated localhost endpoints.
- Parameters:
workspace (WorkspaceLike)
reserve_port (Callable[[], int] | None)
start_server (StartServer)
lifecycle_deps (lifecycle.LifecycleDeps | None)
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:
objectInjectable 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:
objectBounded restart policy for the MCP server bridge.
- Parameters:
max_restarts (int)
- exception ralph.mcp.server.lifecycle.McpServerError(message, *, restart_count)[source]¶
Bases:
ExceptionRaised 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:
objectOptional 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:
ProtocolSubset 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:
objectSessionBridgeLike 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:
inner (StandaloneMcpProcess)
restart_fn (Callable[[], StandaloneMcpProcess])
restart_policy (McpRestartPolicy)
run_id (str)
probe_fn (Callable[[str, timedelta], None] | None)
probe_timeout_fn (Callable[[], timedelta] | None)
- 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
_BridgeWithProcessprotocol 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 methodnotifications/reset_wrapupover HTTP; the McpServer handles it viaMcpServer._dispatch_requestby callingMcpServer.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_Ssoaudit_mcp_timeoutstays 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._lockto serialize with concurrentcheck_health_and_restart_if_neededandreset_tool_registrycalls so a respawn and a reset cannot interleave.Note: the intra-bridge respawn paths (
check_health_and_restart_if_needed()andreset_tool_registry()) do NOT call this method. A fresh subprocess starts withelapsed=0by 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 aFakeProcess(which haspoll()but no real HTTP endpoint). The attempt-boundary wire-up at the top ofeffect_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’stools/listsnapshot lost the alias after a prior restart, retry, or transient recovery).This is a separate counter from
_restart_countso 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.SessionBridgeLikefor 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:
ProtocolProtocol describing the session bridge interface used here.
- 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.
- class ralph.mcp.server.lifecycle.SpawnProcess(*args, **kwargs)[source]¶
Bases:
ProtocolCallable that spawns the MCP server subprocess.
The
phasekeyword argument, when set, is used to label the processphase:<phase>:mcp-serverso it is reaped by the phase-scope cleanup.
- class ralph.mcp.server.lifecycle.StandaloneMcpProcess(endpoint, process, session_file)[source]¶
Bases:
objectA 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
bridgeis aRestartAwareMcpBridge. RaisesMcpServerErrorwhen 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
RestartAwareMcpBridgethat can auto-restart the server on crash up to theextras.restart_policybudget (default: 20 restarts, defined byMcpRestartPolicy).- Parameters:
session (SessionLike)
workspace (WorkspaceLike)
upstream_registry (UpstreamRegistry | None)
deps (LifecycleDeps | None)
extras (McpServerExtras | None)
- Return type:
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; callstart(config)to launch andstop()to shut down gracefully. A health-check endpoint listens on/health; liveness is polled byralph.process.mcp_supervisor.Environment handshake: the server reads
MCP_SESSION(session JSON) andMCP_SESSION_FILEenv 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
McpCapabilityset so each agent only sees the tools it needs.Upstream MCP registry:
load_upstream_mcp_serversdiscovers additional MCP servers fromUPSTREAM_MCP_CONFIGand 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, exec_resource_resolver=None, exec_spill_roots=None)[source]¶
Bases:
objectSession 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)
exec_resource_resolver (ExecResourceResolverLike | None)
exec_spill_roots (tuple[Path, ...] | None)
- property broker_secret: str | None¶
broker-owned secret threaded into the run-scoped receipt / completion sentinel HMAC. The value is set at
FileBackedSessionconstruction 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 exec_resource_resolver: ExecResourceResolverLike | None¶
Lazily resolve the AC-11
ralph://exec/...resolver.Order of preference:
A resolver injected at
__init__time (production tests and the in-process path that owns the resolver object).Spill roots carried by the on-disk session payload’s
exec_spill_rootskey (subprocess MCP server path restored viasession_from_env). The resolver is re-constructed once and cached for the lifetime of the session so repeatedresources/readcalls do not pay the construction cost.
- 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:
objectParsed 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:
objectLightweight MCP server that dispatches JSON-RPC requests to Ralph tools.
Per-invocation reset contract: a single
McpServeris 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 callreset_session_budget()(in-process) or send the wire-levelnotifications/reset_wrapupJSON-RPC method (over HTTP fromRestartAwareMcpBridge) so the budget is re-armed. Seeralph.mcp.server._session_wrapupfor the underlying contract andralph.pipeline.effect_executor._run_attemptfor 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_attemptinralph.pipeline.effect_executorso a retried agent (e.g. after an artifact-missing failure) starts withelapsed=0on the very first tool result instead of inheriting the prior attempt’s elapsed time.The reset creates a fresh
SessionWrapupBudgetbacked by the productionSystemClockand the canonicalSESSION_SOFT_WRAPUP_SECONDS/MAX_SESSION_SECONDSdefaults fromralph.timeout_defaults. The previous budget is replaced in-place; the new provider retains the sameCallable[[], str | None]signature so no caller signature changes.No-op when
wrapup_providerwas 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 thenotifications/reset_wrapupJSON-RPC method (see_dispatch_request()).- Return type:
None
- class ralph.mcp.server.runtime.McpServerExtras(session=None, upstream_registry=None, mcp_config=None)[source]¶
Bases:
objectOptional DI parameters for building standalone MCP servers.
The dataclass is the dependency-injection bundle threaded through
build_standalone_http_serverandrun_standalone_server. Every field is optional (Nonemeans “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 (McpSession | None)
upstream_registry (UpstreamRegistry | None)
mcp_config (McpConfig | None)
- session¶
Pre-built
McpSession(handshake, capabilities, upstream mounts). WhenNonethe runtime readsMCP_SESSION/MCP_SESSION_FILEfrom the environment viasession_from_env.- Type:
McpSession | None
- upstream_registry¶
Pre-loaded
UpstreamRegistryfor upstream MCP servers. WhenNonethe runtime loads the registry fromUPSTREAM_MCP_CONFIG/UPSTREAM_MCP_TOOL_CATALOG_ENVviaload_runtime_upstream_servers.- Type:
UpstreamRegistry | None
- class ralph.mcp.server.runtime.ServerState(*values)[source]¶
Bases:
StrEnumLifecycle 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-mcpconsole script entry point declared inpyproject.toml(ralph-mcp = ralph.mcp.server.runtime:main). It parses--workspace,--host, and--portviaparse_argsand delegates torun_standalone_serverwith the productionDEFAULT_TRANSPORT(streamable-http). The environment handshake (MCP_SESSION/MCP_SESSION_FILE) is performed insiderun_standalone_server; this entry point does not touch the environment directly.- Parameters:
argv (Sequence[str] | None) – Optional argv override. When
None,argparsereads fromsys.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:--portand a/healthendpoint; the liveness probe is polled byralph.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
McpSessionprotocol — NOT a cast. mypy verifies both return branches (FileBackedSessionandAgentSession) against the full session contract, so a public member added to one implementation but not the other is a type error inmake 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, exec_resource_resolver=None, exec_spill_roots=None)[source]¶
Bases:
objectSession 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)
exec_resource_resolver (ExecResourceResolverLike | None)
exec_spill_roots (tuple[Path, ...] | None)
- property broker_secret: str | None¶
broker-owned secret threaded into the run-scoped receipt / completion sentinel HMAC. The value is set at
FileBackedSessionconstruction 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 exec_resource_resolver: ExecResourceResolverLike | None¶
Lazily resolve the AC-11
ralph://exec/...resolver.Order of preference:
A resolver injected at
__init__time (production tests and the in-process path that owns the resolver object).Spill roots carried by the on-disk session payload’s
exec_spill_rootskey (subprocess MCP server path restored viasession_from_env). The resolver is re-constructed once and cached for the lifetime of the session so repeatedresources/readcalls do not pay the construction cost.
- 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
McpSessionprotocol — NOT a cast. mypy verifies both return branches (FileBackedSessionandAgentSession) against the full session contract, so a public member added to one implementation but not the other is a type error inmake 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:
request (JsonRpcRequest)
session (McpSession)
handle_request (Callable[[JsonRpcRequest, ServerState], tuple[JsonRpcResponse | None, ServerState]])
state (ServerState)
write_frame (Callable[[bytes], None])
- Return type:
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:
objectTyped audio content block referencing a manifest artifact.
- Parameters:
uri (str)
mime_type (str)
title (str)
type (str)
delivery (str)
- class ralph.mcp.multimodal.artifacts.DocumentContent(uri, mime_type, title, type='document', delivery='typed_block')[source]¶
Bases:
objectTyped document content block referencing a manifest artifact.
- Parameters:
uri (str)
mime_type (str)
title (str)
type (str)
delivery (str)
- class ralph.mcp.multimodal.artifacts.ImageContent(data, mime_type, type='image', delivery='inline_image')[source]¶
Bases:
objectInline image content block delivered as base64-encoded bytes.
- Parameters:
data (str)
mime_type (str)
type (str)
delivery (str)
- class ralph.mcp.multimodal.artifacts.PdfContent(uri, mime_type, title, type='pdf', delivery='typed_block')[source]¶
Bases:
objectTyped PDF content block referencing a manifest artifact.
- Parameters:
uri (str)
mime_type (str)
title (str)
type (str)
delivery (str)
- class ralph.mcp.multimodal.artifacts.ResourceReferenceContent(uri, mime_type, title, modality, type='resource_reference', delivery='resource_reference')[source]¶
Bases:
objectContent block representing a media artifact via resource reference.
The
deliveryfield distinguishes two cases:'resource_reference_replay': a Ralph-ownedralph://media/...artifact stored in the session manifest. The agent callsread_mediawith the URI to retrieve or replay the artifact. Always passdelivery=DeliveryMode.RESOURCE_REFERENCE_REPLAYexplicitly 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)
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:
objectResult 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_resource_reference()[source]¶
Return True if resource-reference replay delivery will be used.
- Return type:
bool
- class ralph.mcp.multimodal.capabilities.DeliveryMode(*values)[source]¶
Bases:
StrEnumHow a multimodal artifact will be delivered to the model.
- class ralph.mcp.multimodal.capabilities.MultimodalModelIdentity(provider, model_id=None, transport=None)[source]¶
Bases:
objectIdentifies the provider and model for capability detection.
- Parameters:
provider (str)
model_id (str | None)
transport (str | None)
- class ralph.mcp.multimodal.capabilities.ResolvedCapabilityProfile(identity, verdicts)[source]¶
Bases:
objectPre-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:
identity (MultimodalModelIdentity)
verdicts (dict[str, CapabilityVerdict])
- to_payload()[source]¶
Serialize to a JSON-compatible dict for session payload persistence.
- Return type:
dict[str, object]
- 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:
identity (MultimodalModelIdentity)
modality (str)
- Return type:
- 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:
- ralph.mcp.multimodal.capabilities.resolve_capability_profile(identity)[source]¶
Build a pre-computed capability profile for all supported modalities.
- Parameters:
identity (MultimodalModelIdentity)
- Return type:
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:
objectA 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)
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:
objectAn 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:
objectOptional 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:
objectSession-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/listoutput 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:
- get(artifact_id)[source]¶
Retrieve a manifest entry by artifact_id, or None if not found.
- Parameters:
artifact_id (str)
- Return type:
ManifestEntry | None
- 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:
objectSource 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.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:
objectCanonical effective MCP inventory for one agent session.
- Parameters:
custom_servers (tuple[UpstreamMcpServer, ...])
agent_upstream_servers (tuple[UpstreamMcpServer, ...])
effective_servers (tuple[UpstreamMcpServer, ...])
provider_visible_server_names (tuple[str, ...])
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:
objectCanonical effective MCP inventory for one agent session.
- Parameters:
custom_servers (tuple[UpstreamMcpServer, ...])
agent_upstream_servers (tuple[UpstreamMcpServer, ...])
effective_servers (tuple[UpstreamMcpServer, ...])
provider_visible_server_names (tuple[str, ...])
- 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:
objectResolved MCP plan capturing capability grants and server environment for a session.
- Parameters:
capabilities (frozenset[str])
server_env (dict[str, str] | None)
model_identity (MultimodalModelIdentity)
capability_profile (ResolvedCapabilityProfile | None)
- class ralph.mcp.session_plan.SessionModelOpts(model_identity=None, model_flag=None)[source]¶
Bases:
objectOptional model resolution parameters for build_session_mcp_plan.
- Parameters:
model_identity (MultimodalModelIdentity | None)
model_flag (str | None)
- 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_flagresolved viaresolve_model_identity(transport, model_flag)3.UNKNOWN_IDENTITYfallback- Parameters:
transport (AgentTransport | None)
drain (str)
workspace_path (Path | None)
agents_policy (AgentsPolicy | None)
model_opts (SessionModelOpts | None)
model_flag (str | None)
- Return type:
- 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:
custom_servers (tuple[UpstreamMcpServer, ...])
agent_upstream_servers (tuple[UpstreamMcpServer, ...])
provider_visible_server_names (tuple[str, ...])
- Return type:
- 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_namescaptures the direct provider-visible MCP entries (typically justralph), whileeffective_serverscaptures 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:
- 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:
transport (AgentTransport | None)
model_flag (str | None)
- Return type:
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¶
Shared dependencies for markdown artifact submission.
JSON artifact authoring was removed. The markdown handlers own the public MCP surface; this module deliberately keeps only the small persistence seam they share with canonical submission.
- 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:
objectInjectable filesystem and timestamp dependencies for artifact writes.
- Parameters:
backend (FileBackend)
now_iso (Callable[[], str])
history_enabled (bool)
receipt_secret (str | None)
- property artifact_persistence: ArtifactPersistence¶
Return the matching persistence bundle for compatibility callers.
ralph.mcp.tools.md_artifact¶
MCP handlers for validated markdown artifact documents.
- ralph.mcp.tools.md_artifact.handle_discard_md_draft(session, workspace, params, *, deps=None)[source]¶
Drop the persisted markdown draft for one artifact type.
Warning
This discards the whole document and forces a full retype. It is not the way to recover from validation errors, nor to make a revision whose content is substantially similar to the draft —
ralph_edit_md_artifactrepairs in place and submits once the draft validates. Reserve discard for a genuine wholesale restart where almost nothing survives.- Parameters:
session (CoordinationSessionLike)
workspace (WorkspaceLike)
params (dict[str, object])
deps (ArtifactHandlerDeps | None)
- Return type:
- ralph.mcp.tools.md_artifact.handle_finalize_md_artifact(session, workspace, params, *, deps=None)[source]¶
Validate the assembled draft with the submission gate and submit it canonically.
The draft is retained either way: on failure so it can be repaired, and on success so the submitted document remains the editable substrate for a later revision within the same phase attempt. Fresh phase entry — not submission — is what clears it.
- Parameters:
session (CoordinationSessionLike)
workspace (WorkspaceLike)
params (dict[str, object])
deps (ArtifactHandlerDeps | None)
- Return type:
- ralph.mcp.tools.md_artifact.handle_get_md_draft(session, workspace, params, *, deps=None)[source]¶
Return the persisted draft and its diagnostics for resumable authoring.
- Parameters:
session (CoordinationSessionLike)
workspace (WorkspaceLike)
params (dict[str, object])
deps (ArtifactHandlerDeps | None)
- Return type:
- ralph.mcp.tools.md_artifact.handle_stage_md_artifact(session, workspace, params, *, deps=None)[source]¶
Append to (or replace) a persisted markdown draft without gating on validity.
- Parameters:
session (CoordinationSessionLike)
workspace (WorkspaceLike)
params (dict[str, object])
deps (ArtifactHandlerDeps | None)
- Return type:
- ralph.mcp.tools.md_artifact.handle_submit_md_artifact(session, workspace, params, *, deps=None)[source]¶
Validate and canonically persist a markdown artifact atomically.
The submitted text is staged as the artifact’s draft first, whether or not it validates, so a rejected document can be repaired in place with
ralph_edit_md_artifactrather than re-transcribed in full.- Parameters:
session (CoordinationSessionLike)
workspace (WorkspaceLike)
params (dict[str, object])
deps (ArtifactHandlerDeps | None)
- Return type:
- ralph.mcp.tools.md_artifact.handle_verify_md_artifact(session, _workspace, params)[source]¶
Check a markdown artifact without writing it.
- Parameters:
session (CoordinationSessionLike)
_workspace (WorkspaceLike)
params (dict[str, object])
- Return type:
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:
objectLazy 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:
objectA registered tool and its executable handler.
- Parameters:
metadata (ToolMetadata)
handler (RegistrationHandler)
- class ralph.mcp.tools.bridge.RegistrationHandler(*args, **kwargs)[source]¶
Bases:
ProtocolCallable protocol for MCP tool handler functions registered in the tool bridge.
- class ralph.mcp.tools.bridge.ToolBridge(session=None)[source]¶
Bases:
objectRegistry 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:
- 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:
metadata (ToolMetadata)
handler (RegistrationHandler)
- Return type:
None
- exception ralph.mcp.tools.bridge.ToolBridgeError[source]¶
Bases:
ExceptionBase exception for tool bridge failures.
- class ralph.mcp.tools.bridge.ToolDefinition(name, description, input_schema)[source]¶
Bases:
objectImmutable description of one tool that the MCP bridge exposes to agents.
A
ToolDefinitionis the wire-format description a Ralph MCP bridge returns for every registered tool when an agent asks the server for its capabilities (the MCPtools/listresponse). 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 viadataclasses.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 thetoolfield 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 withadditionalProperties: falseand explicitrequiredarrays are preferred because they make agent-prompted errors unambiguous.- Type:
JsonObject
- Invariants:
nameis treated as part of the public API; renaming breaks any agent prompt that calls the tool by name.input_schemais 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:
ToolBridgeErrorRaised when tool dispatch fails.
- class ralph.mcp.tools.bridge.ToolMetadata(definition, required_capability, is_mutating=None, is_multimodal=False)[source]¶
Bases:
objectRalph-side metadata paired with a
ToolDefinitionfor registration.Where
ToolDefinitionis the public agent-facing shape, aToolMetadatacarries 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_capabilityagainst 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
ToolDefinitionadvertised bytools/list. Carries the public name, description, and JSON schema;ToolMetadataadds the Ralph-side annotations.- Type:
- 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 seesPermissionDeniedinstead of a dispatch. The default-deny invariant inralph.mcpdocuments the policy that every Ralph-managed tool must declare one.- Type:
str
- is_mutating¶
Truewhen the tool can change workspace or artifact-store state,Falsefor strictly read-only tools.Noneis treated as conservative (True) so an unknown tool is assumed to mutate until the registration proves otherwise. The bridge uses this flag together withralph.mcp.protocol.startup.access_mode_for_drain()to decide whether a read-only session can still call the tool.- Type:
bool | None
- is_multimodal¶
Truewhen the tool accepts binary content (images, audio) alongside its text schema. The bridge surfaces multimodal tools through a differentcallToolcontent shape and tags them so agents can discover them via the metadata flag.- Type:
bool
- Invariants:
required_capabilitymust be a non-empty capability name recognized byralph.mcp.capabilities; unknown capabilities cause the dispatcher to reject every call.The combination of
is_mutating=Falseandrequired_capabilitygranting write access is allowed but downgrades the runtime access mode on a read-only session.ToolMetadatais constructed once, at registry build time, and is then immutable for the lifetime of the bridge.
- exception ralph.mcp.tools.bridge.ToolRegistrationError[source]¶
Bases:
ToolBridgeErrorRaised when tool registration is invalid.
- class ralph.mcp.tools.bridge.ToolSpec(metadata, module_name, handler_name)[source]¶
Bases:
objectFull 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:
objectProxy handler that forwards tool calls to an upstream MCP registry.
- Parameters:
alias (str)
upstream_registry (UpstreamRegistry)
- 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:
session (object)
workspace (object)
upstream_registry (UpstreamRegistry | None)
mcp_config (McpConfig | None)
- Return type:
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 arun.report_progressevent to the pipeline. Used by the agent to publish heartbeat / status updates while it is still working. The text response is suffixed withPROGRESS_PIPELINE_MARKERso the idle watchdog can key on it.handle_declare_complete— finalizes the run with anartifact.submit-gated completion sentinel. Persists it in.agent/state.dbor the durable.agent/completion_seen_<run_id>.jsonfallback (HMAC-signed when the broker secret is provided) so the failure classifier / recovery controller can verify completion even if the MCP JSON-RPC envelope is lost.handle_coordinate—artifact.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_env—env.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 forexec.py,git_read.py,websearch.py, andwebvisit.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 persists a completion sentinel
under .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:
ToolErrorRaised when a required session capability is not available.
- class ralph.mcp.tools.coordination.CoordinationSessionLike(*args, **kwargs)[source]¶
Bases:
ProtocolMinimum 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.Nonemeans 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
Nonedefault.
- check_capability(capability)[source]¶
Return a policy outcome for the requested capability.
- Parameters:
capability (str)
- Return type:
object
- explore_index: object | None¶
Optional
ralph.mcp.explore.handlers.ExploreIndexhandle attached by the production session bridge. The protocol stays permissive (object | None) so legacy sessions that never opted into indexed exploration remain compatible without an explicit cast.
- exception ralph.mcp.tools.coordination.InvalidParamsError[source]¶
Bases:
ToolErrorRaised 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:
objectSingle 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 withsort_keys=Falseso 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 parsetextfields keep working; agents that need a typed object canjson.loads(content[0].text).- Parameters:
payload (Mapping[str, object] | list[object])
- Return type:
- exception ralph.mcp.tools.coordination.ToolError[source]¶
Bases:
ExceptionBase error raised by MCP tool handlers.
- class ralph.mcp.tools.coordination.ToolResult(content, is_error=None)[source]¶
Bases:
objectSerializable MCP tool result.
- Parameters:
content (list[ContentBlock])
is_error (bool | None)
- class ralph.mcp.tools.coordination.WorkspaceLike(*args, **kwargs)[source]¶
Bases:
ProtocolPlaceholder workspace protocol for handler parity.
- 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), optionalwork_unit_id(string) andpayload(object).now_fn (Callable[[], int]) – Optional injected wall-clock provider for the timestamp in the formatted text. Defaults to
_timestamp.
- Returns:
A
ToolResultwhose 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:
- 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.submitand carry a validrun_idused 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
ToolResultwhose 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:
- Side effects:
Persists the run-scoped sentinel in
.agent/state.dbor, when database persistence fails, the legacy.agent/completion_seen_<run_id>.jsonfallback. If neither store succeeds, the handler returnsToolResult(is_error=True)and does not claim completion.
- 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': 'BUNDLER_ENVIRONMENT_PRESERVER_INTENTIONALLY_NIL', 'BUNDLER_ORIG_PATH': '/Users/mistlight/.rbenv/versions/4.0.1/bin:/opt/homebrew/Cellar/rbenv/1.3.2/libexec:/Users/mistlight/.kimi-code/bin:/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', 'CODEX_POOLER_API_KEY': 'sk-cxp-930c8a01993e-gk5LmCr2IrU3kNgyvr7ON2uKFTGRCUAylCL_PJDWwF8', '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': '79116', 'KITTY_PUBLIC_KEY': '1:8G@;+<|Y(Q^z~!*M#f{mb+B!B4u>FDNLU<+1##6+', 'KITTY_WINDOW_ID': '30', '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', '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', 'OMNIROUTE_API_KEY': 'omni_sk_451f344986e3634b951bd357d5a55f4c4c5856810fd80d52b4e3ccd3dd4c3b86', '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/.kimi-code/bin:/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/.kimi-code/bin:/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.iUw2KSKA6x/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', 'VIPSHOME': '/opt/homebrew/Cellar/vips/8.18.4_1', 'VIRTUAL_ENV': '/Volumes/Crucial X9/ext-Projects/Ralph-Site/tmp/docs_build/ralph-workflow/.venv', 'WINDOWID': '21445', '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-onlyos._Environ[str]).
- Returns:
A
ToolResultwhose 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:
- Side effects:
Pure read of the injected
envmapping. 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 optionalnote(string, defaults to empty).now_fn (Callable[[], int]) – Optional injected wall-clock provider for the
unix_tssuffix in the formatted text. Defaults to_timestamp.
- Returns:
A
ToolResultwhose text content is the formatted progress line (suffixed withPROGRESS_PIPELINE_MARKERso the idle watchdog can key on it).- Raises:
CapabilityDeniedError – When the session does not declare
run.report_progress.- Return type:
- Side effects:
Pure with respect to the workspace. Emits a
run.report_progressevent 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:
session (CoordinationSessionLike)
capability (str)
action (str)
- 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 theProcessExecBoundedcapability 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 isEXEC_MAX_TIMEOUT_MSinralph.timeout_defaults).
A plain command runs argv-direct (no shell). A compound shell string —
one carrying an unquoted | & ; < > operator — is run through
sh -c so pipes, redirections, and &&/; sequences work; before
it runs, the blacklist below is enforced against EVERY command in the
pipeline (echo hi; sudo rm -rf / is denied on its sudo segment).
An argv LIST is always explicit argv and is never shell-interpreted.
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 applied to every shell pipeline segment, including a VCS scanner (
_scan_text_for_vcs_violation) that denieshg/svnunconditionally andgitunless the subcommand is in the read-only whitelist (_GIT_READ_ONLY_SUBCOMMANDS). The whitelist preservesdiffonly when the flag guard rejects output-writing and external-helper flags — see_scan_diff_flags_for_writes.A bounded per-call timeout (
timeout_mscapped atEXEC_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_BYTESis 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, shell_command=None)[source]¶
Bases:
objectParsed parameters for the MCP exec tool.
command/argsare the argv of the (first) command. When the caller passed a compound shell string (an unquoted| & ; < >operator), the raw string is preserved inshell_commandso the handler runs it throughsh -cafter the blacklist has been enforced against every command in the pipeline;command/argsthen hold the FIRST pipeline segment (for a readable result header).shell_commandisNonefor a plain, single-command invocation, which runs argv-direct with no shell.- Parameters:
command (str)
args (list[str])
timeout_ms (int)
shell_command (str | None)
- class ralph.mcp.tools.exec.ExecRunDeps(runner=None, cwd_provider=None, process_manager=None, on_output_chunk=None, spill_dir=None)[source]¶
Bases:
objectInjectable 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:
ToolErrorRaised 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:
ProtocolWorkspace 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.check_version_control(command, args)[source]¶
Return a denial reason if the command invokes or references a VCS tool.
hg / svn are denied unconditionally.
gitis denied unless its subcommand is in_GIT_READ_ONLY_SUBCOMMANDSand (fordiff) the flag guard passes; see_scan_text_for_vcs_violationfor the full policy. The textual scan walks the WHOLE joined text so a VCS call hidden in a quotedsh -cstring, a$(...)/ backtick substitution, or a newline-separated sequence is still caught.- Parameters:
command (str)
args (list[str])
- Return type:
str | None
- ralph.mcp.tools.exec.exec_usage_hints(segments)[source]¶
Return human-readable hint strings to append to the exec result text.
Two hints can be emitted, in order:
git-hint — when any segment ran a whitelisted read-only git subcommand, append a note pointing at the dedicatedgit_status/git_diff/git_log/git_showMCP read tools. They are the preferred surface for repository-state reads (typed schemas, bounded output, dedicated capabilities);execis for the case where shell-level processing is genuinely needed.grep-hint — when any segment head isgrep/egrep/fgrep, append a warning that the MCP explore endpoint (grep_files/ indexed search) is more efficient for workspace-wide content searches.grepis never denied (the tooling exists, the hint is informational).
Returns an empty list when no hint applies. The hints are joined with a blank line onto the standard
format_exec_resulttext so they surface inside the same ToolContent block.- Parameters:
segments (list[tuple[str, list[str]]])
- Return type:
list[str]
- ralph.mcp.tools.exec.find_vcs_usage_in_scripts(segments, workspace_root)[source]¶
Return
(script_token, vcs_word)when an executed shell script uses VCS.Best-effort static check: each candidate script token is resolved against the workspace root; a readable file that looks like a shell script (a known extension or a
#!shebang) has its first_SCRIPT_SCAN_LIMIT_BYTESscanned by the same whitelist scanner thatcheck_version_controluses.hg/svnand any non-whitelistedgitsubcommand trip the scan; a script running onlygit statusis allowed. Unreadable or non-file tokens are skipped — the textual match on the command line remains the primary net.- Parameters:
segments (list[tuple[str, list[str]]])
workspace_root (Path)
- Return type:
tuple[str, 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
ProcessExecBoundedcapability 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_rootis the cwd for the spawned subprocess.Path-like is required.params (Mapping[str, object]) – Mapping with
command(string) and optionalargs(list of strings),timeout_ms(int, bounded byEXEC_MAX_TIMEOUT_MS).deps (ExecRunDeps | None) – Optional dependency-injection bundle (custom
runner,cwd_provider,process_manager,on_output_chunk,spill_dir). WhenNone,DEFAULT_EXEC_RUN_DEPSis used.
- Returns:
A
ToolResultwhose text content is the formatted command output (returncode+ stdout/stderr). Output aboveSPILL_OUTPUT_LIMIT_BYTESis 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
paramsfails theExecParamsparser (missingcommand, wrong types, etc.).ExecutionError – When the subprocess fails to launch (not on non-zero return; non-zero return is preserved as text).
- Return type:
- Side effects:
Spawns a subprocess registered with the global
ProcessManagerand 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-retryableis_errorToolResult(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:
- 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 injecteddeps.spill_dir(tests, custom deployments) wins.- Parameters:
workspace (object)
deps (ExecRunDeps | None)
- 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. The
only policy enforcement left on this surface is the VCS policy:
hg and svn are never allowed; git is allowed only for a
fixed read-only subcommand whitelist (status, diff, log,
show, grep, …). State-mutating VCS is blocked; read-only git
via the dedicated git_* MCP read tools is preferred.
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:
session (CoordinationSessionLike)
workspace (object)
params (Mapping[str, object])
deps (ExecRunDeps | None)
- Return type:
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— runsgit statusin the workspace root. Capability:GitStatusRead.handle_git_diff— runsgit 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— runsgit log -<count> --oneline(default count =DEFAULT_LOG_COUNT= 10). Capability:GitStatusRead.handle_git_show— runsgit 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 successfulgitexit code unless the lenient variant is used. They are the only call sites of the internal_run_git_subprocesshelper, which always carries the fixed_GIT_READ_TIMEOUT_SECONDS = 30.0bound.
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:
ToolErrorRaised when a git subprocess cannot be started or fails.
timed_outmarks 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:
objectParsed parameters for the git diff tool.
- Parameters:
args (list[str])
- class ralph.mcp.tools.git_read.GitLogParams(count, format='raw')[source]¶
Bases:
objectParsed parameters for the git log tool.
- Parameters:
count (int)
format (Literal['raw', 'summary'])
- class ralph.mcp.tools.git_read.GitShowParams(git_ref, format='raw')[source]¶
Bases:
objectParsed parameters for the git show tool.
- Parameters:
git_ref (str)
format (Literal['raw', 'summary'])
- class ralph.mcp.tools.git_read.WorkspaceWithRoot(*args, **kwargs)[source]¶
Bases:
ProtocolWorkspace 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.
AC-11:
format=raw(default) preserves the legacy text output.format=summaryreturns a compact summary card with changed files, insertion/deletion counts, and an output byte cap so agents do not pay for unread diff bodies.max_bytescaps the returned text; default 50_000. AC-06:max_bytesis strictly bounded to a positive integer in[1, 50_000]so callers cannot bypass the excerpt cap with zero, negative, malformed, or non-integer values.- Parameters:
session (CoordinationSessionLike)
workspace (object)
params (Mapping[str, object])
- Return type:
- 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 toDEFAULT_LOG_COUNT = 10) and optionalformat('raw'|'summary', default'raw').
- Returns:
A
ToolResultwhose text content is thegit log -<count> --onelineoutput whenformat='raw'(default), or a compact JSON envelope{format, count, commits: [{short_sha, sha, subject}], bytes_in, bytes_out}whenformat='summary'.- Raises:
CapabilityDeniedError – When the session does not declare
GitStatusRead.InvalidParamsError – When
paramsfailsparse_git_log_params.
- Return type:
- Side effects:
Spawns a
git logsubprocess registered with the globalProcessManager. 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) perparse_git_show_paramsand optionalformat('raw'|'summary', default'raw').
- Returns:
A
ToolResultwhose text content is thegit show <ref>output whenformat='raw'(default), or a compact header-only JSON envelope{format, ref, kind, sha, short_sha, author_name, author_email, author_date, subject, parents, bytes_in, bytes_out, truncated:false}whenformat='summary'.- Raises:
CapabilityDeniedError – When the session does not declare
GitStatusRead.InvalidParamsError – When
paramsfailsparse_git_show_params.
- Return type:
- Side effects:
Spawns a
git showsubprocess registered with the globalProcessManager. 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.
AC-11: the optional
formatargument selects betweenraw(default; unchanged legacy output) andcompact(ranked changed paths with role tags + byte budget).- Parameters:
session (CoordinationSessionLike)
workspace (object)
params (Mapping[str, object])
- Return type:
- ralph.mcp.tools.git_read.parse_git_diff_params(params)[source]¶
Parse git diff params, keeping only string arguments.
AC-06: read-only contract. The git diff MCP tool is a read-only
GitDiffReadoperation, so theargsallowlist MUST reject output-writing flags (--output=...,--output ...) and external-helper flags (--ext-diff,--textconv,--convience-diffetc.). These flags would let a caller make git write to the workspace or invoke an external helper, bypassing the intended capability separation. Malformed types (non-string args, non-list containers) are silently dropped so the legacy parsing contract is preserved.- Parameters:
params (Mapping[str, object])
- Return type:
- ralph.mcp.tools.git_read.parse_git_log_params(params)[source]¶
Parse git log params with the Rust default count.
Phase 4:
formatis optional and closed to{'raw', 'summary'}.rawis the default (preserved legacy output);summaryreturns a compact JSON envelope with one entry per commit. Any other value raisesInvalidParamsErrornaming the closed enum so a malformed selector never reaches the git subprocess.- Parameters:
params (Mapping[str, object])
- Return type:
- ralph.mcp.tools.git_read.parse_git_show_params(params)[source]¶
Parse git show params.
Phase 4:
formatis optional and closed to{'raw', 'summary'}.rawis the default (preserved legacy output);summaryreturns a compact header-only envelope without the patch body. Any other value raisesInvalidParamsError.- Parameters:
params (Mapping[str, object])
- Return type:
- 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 a
CompletedProcessregardless of exit code.Callers that want a single text blob can use
lenient_stdoutor joinresult.stdoutandresult.stderrthemselves.- Parameters:
workspace (object)
args (list[str])
runner (GitRunner | None)
cwd_provider (CwdProvider)
- Return type:
CompletedProcess[bytes]
ralph.mcp.tools.names¶
Canonical Ralph MCP tool naming helpers.
- class ralph.mcp.tools.names.RalphToolName(*values)[source]¶
Bases:
StrEnumCanonical 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 only the provider-visible prompt name for this tool.
- Parameters:
tool_name_prefix (str)
- Return type:
tuple[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:
tool_name (str | RalphToolName)
server_name (str)
- 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. anangularserver exposesangular_ai_tutor). Ralph’s server is registered asralph(seetransport/opencode.pyand itsralph_*permission), so its tools are exposed asralph_<tool>. This is the canonical builder so prompts match the names OpenCode actually exposes.- Parameters:
tool_name (str | RalphToolName)
server_name (str)
- 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:
tool_name (str | RalphToolName)
tool_name_prefix (str)
- 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.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.
Prompt rendering asks this question repeatedly for the same static capability profile. Cache only the immutable catalog and return a fresh list so callers cannot mutate the shared value.
- 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 theWebSearchcapability on the session, parses a stringquery(and optional boundedlimitclamped 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 aWebSearchBackendinstance for a given backend name and config. The factory always uses a resolvedtimeout_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.
- ralph.mcp.tools.websearch.handle_web_search(session, _workspace, params, *, web_search_config=None)[source]¶
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), optionallimit(int, clamped to[MIN_LIMIT, MAX_LIMIT] = [1, 25]), and optionalformat('raw'|'summary', default'raw').web_search_config (WebSearchConfig | None) – Optional injected
WebSearchConfigfor the dispatch order and per-backend overrides. Defaults toWebSearchConfig().
- Returns:
A
ToolResultwhose text content is the formatted backend result list (Title / URL / Snippetblocks joined by blank lines) whenformat='raw'(default), or a compact JSON envelope with bounded snippets,bytes_in/bytes_outsize counters, and thebackend_chain_usedlist whenformat='summary'. Falls back through the configured backend order and only returnsis_error=Trueafter every backend has failed.- Raises:
CapabilityDeniedError – When the session does not declare
WebSearch. The handler enforces default-deny.InvalidParamsError – When
paramsis missingqueryor carries an unknownformatvalue.
- Return type:
- Side effects (network contract):
Every backend implementation uses an injected
timeout_secondson the network call so a misbehaving upstream cannot hang the MCP server thread.loguruwarnings 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 theWebVisitcapability on the session, fetches the URL throughralph.mcp.webvisit.fetcher.fetch_url(boundedtimeout_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 withstatus,title,effective_url,content_type, the readabletext(clamped tomax_bytes // 4characters), and the extractedlinkswhenwith_linksis requested.handle_download_url— the public download handler. Requires theWebDownloadcapability, fetches the URL with the same bounded network contract, and writes the response body to a workspaceoutput_path(UTF-8 witherrors="replace"). Returns a JSON payload withstatus,effective_url,content_type,output_path, andbytes_written. A write failure is converted into a non-retryableis_errorresult so a model that sees the error does not loop re-issuing the call._error_result/_MAX_TEXT_CHARS_DIVISOR— internal helper for theFetchOutcome->ToolResulttranslation, 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),output_path(relative path inside the workspace), and optionalformat('raw'|'summary', default'raw').web_visit_config (WebVisitConfig | None) – Optional injected
WebVisitConfigprovidingtimeout_ms,max_bytes,user_agent, andallow_private_networks. Defaults toWebVisitConfig().
- Returns:
A
ToolResultwhose text content is a JSON payload withstatus,effective_url,content_type,output_path, andbytes_writtenwhenformat='raw'(default). Whenformat='summary'the response adds asha256fingerprint (first 16 hex chars) and a boundedhead_preview(first 240 bytes rendered as base64-safe ASCII) but does NOT echo the downloaded body inline. Callers needing the full body should useformat='raw'(orread_fileon the persistedoutput_path).- Raises:
CapabilityDeniedError – When the session does not declare
WebDownload. The handler enforces default-deny.InvalidParamsError – When
paramsis missingurl,output_path, or carries an unknownformatvalue.
- Return type:
- Side effects (network + filesystem contract):
Performs an HTTP/HTTPS fetch bounded by
WebVisitConfig.timeout_msandmax_bytes. Writes the response body tooutput_pathas UTF-8 witherrors="replace". A write failure (OSError) is captured and returned as a non-retryableis_errorresult 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), optionalwith_linksboolean (overridesWebVisitConfig.extract_links), and optionalformat('raw'|'metadata', default'raw').web_visit_config (WebVisitConfig | None) – Optional injected
WebVisitConfigprovidingtimeout_ms,max_bytes,user_agent, andallow_private_networks. Defaults toWebVisitConfig().
- Returns:
A
ToolResultwhose text content is a JSON payload withstatus,title,effective_url,content_type,text(clamped tomax_bytes // 4chars), and thelinksarray whenwith_linkswas requested (format='raw', default). Whenformat='metadata'the full text body is dropped from the inline payload and a boundedhead_previewplusbyte_countcounters are returned instead.with_links=truewith metadata mode attaches up to 10 outbound link cards after the metadata envelope.- Raises:
CapabilityDeniedError – When the session does not declare
WebVisit. The handler enforces default-deny.InvalidParamsError – When
paramsis missingurlor carries an unknownformatvalue.
- Return type:
- Side effects (network contract):
Performs an HTTP/HTTPS fetch bounded by
WebVisitConfig.timeout_msandmax_bytes. Private network ranges are opt-in viaallow_private_networks. Readability extraction is best-effort; an extraction exception is converted tois_error=Truewithstatus="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:
session (CoordinationSessionLike)
workspace (Workspace)
params (dict[str, object])
- Return type:
- ralph.mcp.tools.workspace.handle_copy_file(session, workspace, params)[source]¶
Copy a workspace file or directory to a new location.
- Parameters:
session (CoordinationSessionLike)
workspace (Workspace)
params (dict[str, object])
- Return type:
- ralph.mcp.tools.workspace.handle_create_directory(session, workspace, params)[source]¶
Create a directory (and parents) within the workspace.
- Parameters:
session (CoordinationSessionLike)
workspace (Workspace)
params (dict[str, object])
- Return type:
- ralph.mcp.tools.workspace.handle_delete_path(session, workspace, params)[source]¶
Delete a workspace file or directory.
- Parameters:
session (CoordinationSessionLike)
workspace (Workspace)
params (dict[str, object])
- Return type:
- ralph.mcp.tools.workspace.handle_directory_tree(session, workspace, params)[source]¶
Return a nested JSON directory tree for a workspace path.
AC-09 indexed args mirror
list_directory:view,include_counts,include_symbols,limit_children,use_index. Raw + never preserve the legacy tree shape.- Parameters:
session (CoordinationSessionLike)
workspace (Workspace)
params (dict[str, object])
- Return type:
- ralph.mcp.tools.workspace.handle_edit_file(session, workspace, params)[source]¶
Apply structured oldText/newText replacements to a workspace file.
AC-10 indexed args:
expected_content_hash– fail closed when the file’s current SHA-256 does not match.target(evidence_id/span_id/symbol) – anchor the edit to an indexed span. Resolution happens after workspace path normalization; unknown symbols return a structuredambiguous_targeterror before any mutation.match_strategyexact|within_target|all_in_target– constrains how the edit anchors inside the target span.exactrequires the edit’s oldText to equal the indexed span;within_targetaccepts an occurrence inside; the default isexactso callers can opt in.reindexauto|skip|changed_blocking– controls the dirty marking + blocking refresh behavior.automarks the path dirty and lets the lifecycle handle it.impact_preview– only valid withdry_run=true. Returns conservative graph impact when the explore index is available; otherwise returnsimpact_preview_unavailable.return_evidence_updates– include the post-mutation generation + freshness in the response.
- Parameters:
session (CoordinationSessionLike)
workspace (Workspace)
params (dict[str, object])
- Return type:
- ralph.mcp.tools.workspace.handle_grep_files(session, workspace, params)[source]¶
Search file contents for a pattern and return line-level matches.
- Parameters:
session (CoordinationSessionLike)
workspace (Workspace)
params (dict[str, object])
- Return type:
- 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:
session (CoordinationSessionLike)
workspace (Workspace)
params (dict[str, object])
- Return type:
- ralph.mcp.tools.workspace.handle_list_directory(session, workspace, params)[source]¶
List entries in a workspace directory.
AC-09 indexed args:
viewraw|compact|ranked|outline— raw preserves the legacy shape; compact adds counts; ranked sorts by symbol count; outline includes top-level symbols/headings.include_counts,include_symbols,changed_only,limit_childrenfilter the indexed view.use_indexauto|always|never— always fails closed when required indexed metadata cannot be supplied.
- Parameters:
session (CoordinationSessionLike)
workspace (Workspace)
params (dict[str, object])
- Return type:
- ralph.mcp.tools.workspace.handle_list_directory_recursive(session, workspace, params)[source]¶
Return a flat listing of all entries under a workspace directory.
- Parameters:
session (CoordinationSessionLike)
workspace (Workspace)
params (dict[str, object])
- Return type:
- ralph.mcp.tools.workspace.handle_move_file(session, workspace, params)[source]¶
Move or rename a workspace file or directory.
- Parameters:
session (CoordinationSessionLike)
workspace (Workspace)
params (dict[str, object])
- Return type:
- 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.
Phase 1 indexed args:
evidence_id— resolve the indexed evidence handle and read the exact span +context_linesof context.span_id— resolve the indexed span from the structure rows and read the exact span + bounded context. Behavior is fail-closed: a stale span, missing generation, or unknown span id produces a structuredunknown_evidenceorstale_evidenceresponse.symbol— resolve the indexed symbol span from the structure rows and read the exact span + bounded context. Behavior is fail-closed: a stale symbol, missing generation, or unknown symbol produces a structuredunknown_evidenceorstale_evidenceresponse.expected_content_hash— fail closed if the current file’s content hash does not match.context_lines— bounded context around the resolved span.return_metadata— include content hash, generation, and freshness.
- Parameters:
session (CoordinationSessionLike)
workspace (Workspace)
params (dict[str, object])
- Return type:
- 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).
The optional
formatargument ('inline'|'metadata') selects between the legacy image content block (format='inline', default) and a bounded metadata envelope (format='metadata'). The metadata envelope drops the image bytes inline and exposes size, sha256, width, height, and aninline_onlyflag.handle_read_imagenever persists aralph://media/{artifact_id}artifact, so the envelope’sresource_handleis alwaysNone.This is a compatibility alias over
_handle_workspace_mediathat restricts inputs to image formats only while preserving the same truthful delivery contract asread_media.- Parameters:
session (CoordinationSessionLike)
workspace (Workspace)
params (dict[str, object])
max_inline_bytes (int)
- Return type:
- 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) - aralph://media/{artifact_id}replay handle from a prior sessionWhen given a replay handle, rehydrates the artifact from the live session manifest and returns the same typed block that was originally emitted (
format='inline') or a bounded metadata envelope (format='metadata'). 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.
The optional
formatargument ('inline'|'metadata') selects between the legacy block shape and a bounded metadata envelope. The metadata envelope drops inline media bytes and exposes a replayableresource_handlewhen the underlying delivery registered one.- Parameters:
session (CoordinationSessionLike)
workspace (Workspace)
params (dict[str, object])
max_inline_bytes (int)
- Return type:
- ralph.mcp.tools.workspace.handle_read_multiple_files(session, workspace, params)[source]¶
Read multiple workspace files in one call and return per-file results.
Phase 1 indexed args:
items(list of dicts): each item may be one of{"path": "...", "line_start": N, "line_end": N},{"evidence_id": "..."},{"span_id": "..."}, or{"symbol": "..."}.per_item_max_bytes: cap each returned item.return_metadata: include freshness per item.fail_fast: a stale indexed item fails only that item unlessfail_fast=true(Phase 1 keeps the defaultfail_fast=trueso callers can opt into partial results explicitly).
- Parameters:
session (CoordinationSessionLike)
workspace (Workspace)
params (dict[str, object])
- Return type:
- ralph.mcp.tools.workspace.handle_search_files(session, workspace, params)[source]¶
Search for files matching a glob pattern within a workspace directory.
Indexed args:
ranked: rank paths by deterministic index signals.rolein {source, test, docs, config, generated, any}.contains_symbol: filter to files that define or mention the named symbol via the indexed structure rows. The contract ships: absence of the symbol in the index reports+0 component:no_indexed_datareasons, not a deferred placeholder.changed_only: filter to git-changed paths.return_evidence_ids: attach handles to matched indexed records.
- Parameters:
session (CoordinationSessionLike)
workspace (Workspace)
params (dict[str, object])
- Return type:
- ralph.mcp.tools.workspace.handle_stat(session, workspace, params)[source]¶
Return file metadata (type, size, timestamps) for a workspace path.
- Parameters:
session (CoordinationSessionLike)
workspace (Workspace)
params (dict[str, object])
- Return type:
- ralph.mcp.tools.workspace.handle_write_file(session, workspace, params)[source]¶
Write UTF-8 content to a workspace file, creating it if necessary.
- Parameters:
session (CoordinationSessionLike)
workspace (Workspace)
params (dict[str, object])
- Return type:
- 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
pathshould be treated as git-tracked output.The path must exist in the workspace and must not live in ephemeral directories such as
.agent/,target/, ornode_modules/.- Parameters:
workspace (Workspace)
path (str)
- 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
pathand surface workspace errors as ToolError.- Parameters:
workspace (Workspace)
path (str)
- 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.capability_denied_error¶
Capability-denied MCP tool error.
ralph.mcp.tools.coordination_session_like¶
Protocol for coordination tool session access.
- class ralph.mcp.tools.coordination_session_like.CoordinationSessionLike(*args, **kwargs)[source]¶
Bases:
ProtocolMinimum 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.Nonemeans 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
Nonedefault.
- check_capability(capability)[source]¶
Return a policy outcome for the requested capability.
- Parameters:
capability (str)
- Return type:
object
- explore_index: object | None¶
Optional
ralph.mcp.explore.handlers.ExploreIndexhandle attached by the production session bridge. The protocol stays permissive (object | None) so legacy sessions that never opted into indexed exploration remain compatible without an explicit cast.
ralph.mcp.tools.invalid_params_error¶
Invalid MCP tool parameter error.
ralph.mcp.tools.text_edits¶
Shared oldText/newText replacement engine for Ralph’s editing tools.
edit_file (workspace files) and ralph_edit_md_artifact (persisted
markdown artifact drafts) offer the same editing contract, so the semantics
live here once rather than in each handler:
edits apply sequentially against the running content, each replacing the first occurrence of its
oldText;a single miss rejects the whole batch — callers write nothing, so an edit call is all-or-nothing;
rejections carry a unified diff of the work completed before the miss, so the caller can see how far the batch got.
The engine is pure: it neither reads nor writes storage. Callers supply the current text and persist the result, which keeps the semantics testable independently of the workspace and draft backends.
Line-anchored matching (anchor) exists for edit_file’s indexed
evidence/span/symbol targets. Draft editing passes anchor=None: a draft
is not in the explore index, so there is no span to anchor to.
- class ralph.mcp.tools.text_edits.AppliedTextEdits(original, content, applied, label)[source]¶
Bases:
objectEvery edit matched;
contentis the fully edited text.- Parameters:
original (str)
content (str)
applied (tuple[TextEdit, ...])
label (str)
- property diff: str¶
Return the unified diff from the original to the edited text.
- class ralph.mcp.tools.text_edits.RejectedTextEdits(payload)[source]¶
Bases:
objectAn edit missed;
payloadis the structured tool-error body.- Parameters:
payload (dict[str, object])
- class ralph.mcp.tools.text_edits.TextEdit(old_text, new_text)[source]¶
Bases:
objectOne first-occurrence replacement of
old_textbynew_text.- Parameters:
old_text (str)
new_text (str)
- class ralph.mcp.tools.text_edits.TextEditAnchor(start_line, end_line, match_strategy)[source]¶
Bases:
objectA 1-based inclusive line span constraining where edits may match.
- Parameters:
start_line (int)
end_line (int)
match_strategy (str)
- ralph.mcp.tools.text_edits.apply_text_edits(original, edits, *, label, anchor=None)[source]¶
Apply
editssequentially tooriginal.Returns
AppliedTextEditswhen every edit matched, otherwiseRejectedTextEditscarrying the structured rejection payload. The caller persists nothing on rejection — the batch is atomic.- Parameters:
original (str)
edits (list[TextEdit])
label (str)
anchor (TextEditAnchor | None)
- Return type:
- ralph.mcp.tools.text_edits.line_range_to_byte_offsets(text, start_line, end_line)[source]¶
Convert
(start_line, end_line)(1-based, inclusive) to byte offsets.The mapping is conservative: missing line boundaries fall back to
0andlen(text)so anchored matching never crashes on edge cases.- Parameters:
text (str)
start_line (int)
end_line (int)
- Return type:
tuple[int, int]
- ralph.mcp.tools.text_edits.parse_text_edits(params)[source]¶
Read and validate the
editsparameter.- Raises:
InvalidParamsError – when
editsis absent, is not a non-empty list, or any entry lacks a non-emptyoldTextstring.newTextdefaults to the empty string so an edit can delete.- Parameters:
params (dict[str, object])
- Return type:
list[TextEdit]
ralph.mcp.tools.tool_content¶
MCP tool text content block.
- class ralph.mcp.tools.tool_content.ToolContent(type, text)[source]¶
Bases:
objectSingle 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 withsort_keys=Falseso 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 parsetextfields keep working; agents that need a typed object canjson.loads(content[0].text).- Parameters:
payload (Mapping[str, object] | list[object])
- Return type:
ralph.mcp.tools.tool_error¶
Base error raised by MCP tool handlers.
ralph.mcp.tools.tool_result¶
Serializable MCP tool result.
ralph.mcp.tools.workspace_like¶
Protocol for workspace access used by coordination tools.
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:
agent_native (tuple[UpstreamMcpServer, ...])
mcp_toml_servers (tuple[UpstreamMcpServer, ...])
- 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, master_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)
master_prompt_file (str | None)
unsafe_mode (bool)
- Return type:
str
- ralph.mcp.transport.prepare_codex_home_with_upstreams(endpoint, *, workspace_path, existing_home, master_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)
master_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:
runtime_env (dict[str, str])
upstreams (tuple[UpstreamMcpServer, ...])
- 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 still registered for atexit cleanup.
The capped
_all_allocated_codex_homesset retains outstanding homes up to_ALL_CODEX_HOMES_CAP. A path evicted beyond that cap loses only its atexit registration, never its directory: normal owners userelease_codex_home; orphaned system-temp homes rely on OS cleanup; workspace-scoped homes under.agent/tmprely on run-start aged reclamation bysweep_agent_dir.Standalone importable function so tests can invoke cleanup directly without depending on
atexittiming.ignore_errorsmakes 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, master_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)
master_prompt_file (str | None)
unsafe_mode (bool)
- Return type:
str
- ralph.mcp.transport.codex.prepare_codex_home_with_upstreams(endpoint, *, workspace_path, existing_home, master_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)
master_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 withignore_errors=Trueso the operation is idempotent.Returns
Trueif the home was in the bounded deque (active bookkeeping member);Falseif 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 returnsFalseeven 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:
agent_native (tuple[UpstreamMcpServer, ...])
mcp_toml_servers (tuple[UpstreamMcpServer, ...])
- 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:
runtime_env (dict[str, str])
upstreams (tuple[UpstreamMcpServer, ...])
- 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 onPATH)Headless flag:
--printwith--output-format stream-jsonAutonomy flag:
--yolo(or--auto-reviewwhen configured)MCP config path: workspace
.cursor/mcp.jsonAND 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 byCursorParser)
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
mcpServerscontaining the Ralph entry with theurlkey (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.jsonand the user-global~/.cursor/mcp.jsonso 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 (viaPath.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
UpstreamMcpServerobjects found in Cursor config files. The Ralph entry is filtered out so it does not collide with the run-scopedralphinjection.- 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.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.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 - Print output: stream-json emits NDJSON; AgyParser is selected by transport.
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.Lockand writes the merged config atomically (viaos.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:
objectResult 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:
RuntimeErrorRaised 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:
ProtocolProtocol for upstream clients that expose a media artifact manifest.
- class ralph.mcp.upstream.client.HttpUpstreamClient(server, *, caller=None)[source]¶
Bases:
objectUpstream MCP client that communicates over HTTP JSON-RPC.
- Parameters:
server (UpstreamMcpServer)
caller (JsonRpcCaller | None)
- class ralph.mcp.upstream.client.StdioUpstreamClient(server, *, caller=None)[source]¶
Bases:
objectUpstream MCP client that communicates over stdio with a subprocess.
- Parameters:
server (UpstreamMcpServer)
caller (JsonRpcCaller | None)
- exception ralph.mcp.upstream.client.UpstreamCallError[source]¶
Bases:
ExceptionRaised when a remote tool call or upstream server reachability check fails.
- class ralph.mcp.upstream.client.UpstreamMcpClient(*args, **kwargs)[source]¶
Bases:
ProtocolProtocol 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:
server (UpstreamMcpServer)
caller (JsonRpcCaller | None)
- Return type:
- 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:
result (JsonObject)
server_name (str)
tool_name (str)
session (HasMediaManifest | None)
workspace (Workspace | None)
- 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:
objectNormalized 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).
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:
objectA single upstream tool mapped to a stable proxy alias.
- Parameters:
alias (str)
server_name (str)
tool (UpstreamTool)
- exception ralph.mcp.upstream.registry.RegistryCollisionError[source]¶
Bases:
ValueErrorRaised when two upstream servers produce the same proxy alias for a tool.
- class ralph.mcp.upstream.registry.UpstreamRegistry(proxied_tools, clients)[source]¶
Bases:
objectAggregates 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:
servers (Iterable[UpstreamMcpServer])
tool_catalog (dict[str, list[UpstreamTool]])
client_factory (Callable[[UpstreamMcpServer], HttpUpstreamClient | StdioUpstreamClient] | None)
- Return type:
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
initialize → notifications/initialized → tools/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:
objectValidation 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:
RuntimeErrorRaised in strict mode when one or more upstream MCP servers fail validation.
- class ralph.mcp.upstream.validation.UpstreamValidationReport(servers)[source]¶
Bases:
objectAggregated 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 viaRALPH_MCP_PREFLIGHT_TIMEOUT_MS).strict (bool | None) – Override strict-mode autodetection. If unset, reads
RALPH_MCP_STRICTfrom 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 throughStdioUpstreamClient.
- Returns:
UpstreamValidationReportwith one entry per server. In soft mode failures are reported withok=Falseand a warning is logged per failure. In strict mode anUpstreamValidationErroris raised after all servers are inspected so the diagnostic listing names every problem at once.- Return type:
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:
objectResult of extracting readable content from an HTML page.
- Parameters:
title (str | None)
text (str)
links (tuple[str, ...])
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:
objectResult 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:
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:
objectNormalized search result shape shared by all backends.
- Parameters:
title (str)
url (str)
snippet (str)
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:
objectBackend 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:
objectIn-process default backend backed by the
ddgspackage.- 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_keydirectly, orset
api_key_envto an environment variable name that holds the key (resolved viaralph.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)
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)
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_keydirectly, orset
api_key_envto an environment variable name that holds the key (resolved viaralph.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)
ralph.mcp.websearch.secrets¶
Secret resolution helpers for web-search backends.
ralph.mcp.explore¶
Indexed exploration substrate for Ralph Workflow MCP tools.
This package owns the deterministic SQLite+FTS5 indexed exploration substrate and the supporting typed registers (audit, deferred phases) required by the MCP tool efficiency plan.
Phases 0-4 of the architecture finding are implemented here: the
Phase 0 MCP tool-efficiency audit register, Phase 1 lexical indexed
search/list/read plus ralph_index_status / ralph_reindex,
Phase 2 Python + Markdown structure extraction and ralph_graph
neighbors, Phase 3 impact-aware editing with ralph_graph
impact/tests, and Phase 4 non-index MCP remediation. The sole
remaining entry in ralph.mcp.explore.deferred_phases is
phase_5: optional adapters (NetworkX offline metrics, an
optional Kuzu adapter, FTS+graph+git hybrid ranking, and
Tree-sitter language parsers), all gated on measured SQLite
bottleneck / benchmark evidence. The optional ralph_explore
workflow wrapper is held implicitly under Phase 5 (it is not
registered as a separate deferred phase) until benchmarked flows
prove a compact task-oriented evidence packet is needed.
The substrate is a disposable, deterministic cache under
.agent/ralph-explore/index.sqlite. Deleting that directory forces a
cold rebuild and never affects source files or workflow artifacts.
- class ralph.mcp.explore.AuditEntry(tool, family, outcome, rationale, counters, risk='', source='seed')[source]
Bases:
objectA single audit entry: tool outcome, rationale, and counters.
AC-01 contract. The
rationalefield is required for every entry. Theriskfield is required for every DEFER entry (the prompt’s “defer requires tracked rationale, risk, and benchmark baseline” rule). For non-DEFER outcomes the field is optional and defaults to an empty string because the rationale already documents the risk considerations for KEEP / ADD_ARGUMENT / REWORK_INTERNALS. Thecountersfield must include a wall-time baseline.- Parameters:
tool (RalphToolName)
family (AuditFamily)
outcome (AuditOutcome)
rationale (str)
counters (AuditCounters)
risk (str)
source (str)
- class ralph.mcp.explore.AuditOutcome(*values)[source]
Bases:
StrEnumClosed vocabulary of audit outcomes for a single MCP tool.
- class ralph.mcp.explore.DeferredPhase(phase_id, title, deliverables, deferral_rationale, risk, baseline_counters=<factory>)[source]
Bases:
objectA single deferred phase tracked under the audit register.
- Parameters:
phase_id (str)
title (str)
deliverables (tuple[str, ...])
deferral_rationale (str)
risk (str)
baseline_counters (dict[str, int | float | None])
- class ralph.mcp.explore.DeferredPhaseRegistry[source]
Bases:
objectPublic read-only view of the deferred phase register.
Wrapped as a class to leave room for late-bound baseline updates without changing the call sites.
- ralph.mcp.explore.audit_register()[source]
Return the audit register (immutable snapshot).
Wrapped as a function so future phases can swap in a measured register without changing call sites. The default view returns the static seed; the bench-driven measured register is built by
refresh_audit_register().- Return type:
tuple[AuditEntry, …]
ralph.mcp.explore.audit_register¶
Per-tool audit register for every Ralph-owned MCP tool.
This module owns a typed, immutable register with one entry per
RalphToolName member. Each entry records the audit outcome
(keep / add_argument / rework_internals / defer), a
rationale, and required research-gate counters (transcript tokens,
returned bytes, tool calls, evidence recall, evidence precision,
stale/fallback events, parse count, changed file count, index
storage bytes).
Outcomes are seeded from the Phase 0 architecture finding audit
section. They can be updated by later phases after measurement
proves a deferral is unjustified or a keep requires rework.
AC-06 measured provenance: the seed entries are deterministic
Phase 0 measurements; they are not hard-coded placeholder
values. refresh_audit_register(measurements) accepts a
reproducible sequence of Measurement records (one per tool
or family) and rebuilds the per-tool AuditCounters by
overlaying the measured values on top of the seed baseline.
Tools without a measured value keep the seed baseline; the
provenance field on each entry records whether the counter
came from the seed or from a measurement. The default
audit_register() view still returns the static seed; tests
and the production audit gate should use
refresh_audit_register to read measured data.
The module is a pure data module with no I/O so it is fully black-box
testable. Tests in tests/test_explore_audit_register.py assert
that:
Every
RalphToolNamemember has exactly one entry.Every
deferentry has a non-empty rationale.Every entry has a non-null
AuditCountersrecord with non-negative integer values and a recall/precision in [0.0, 1.0].Outcome values are restricted to the closed vocabulary above.
refresh_audit_registeroverlays measured values deterministically.
- ralph.mcp.explore.audit_register.AUDIT_REGISTER: Final[tuple[AuditEntry, ...]] = (AuditEntry(tool=<RalphToolName.READ_FILE: 'read_file'>, family=<AuditFamily.WORKSPACE_READ: 'workspace_read'>, outcome=<AuditOutcome.ADD_ARGUMENT: 'add_argument'>, rationale="Add evidence_id / span_id / symbol selectors with content-hash preconditions so agents stop reading entire files when an indexed span or symbol lookup suffices. The contract ships via the structure rows; absent evidence surfaces structured 'unknown_evidence' / 'stale_evidence' responses rather than a deferred placeholder.", counters=AuditCounters(transcript_tokens=120, returned_bytes=512, tool_calls=1, evidence_recall=1.0, evidence_precision=1.0, stale_fallback_events=0, parse_count=0, changed_file_count=0, index_storage_bytes=0, wall_time_seconds=0.01), risk='', source='seed'), AuditEntry(tool=<RalphToolName.READ_MULTIPLE_FILES: 'read_multiple_files'>, family=<AuditFamily.WORKSPACE_READ: 'workspace_read'>, outcome=<AuditOutcome.ADD_ARGUMENT: 'add_argument'>, rationale='Allow mixed evidence/span/symbol items so a single call replaces many read_file calls; Phase-1 evidence items work, span/symbol items return structured phase-2 fallback.', counters=AuditCounters(transcript_tokens=192, returned_bytes=1024, tool_calls=1, evidence_recall=1.0, evidence_precision=1.0, stale_fallback_events=0, parse_count=0, changed_file_count=0, index_storage_bytes=0, wall_time_seconds=0.01), risk='', source='seed'), AuditEntry(tool=<RalphToolName.STAT_PATH: 'stat_path'>, family=<AuditFamily.WORKSPACE_READ: 'workspace_read'>, outcome=<AuditOutcome.KEEP: 'keep'>, rationale='stat_path is metadata-only and already returns type/size/timestamps; adding indexed arguments would not reduce agent decisions.', counters=AuditCounters(transcript_tokens=48, returned_bytes=128, tool_calls=1, evidence_recall=1.0, evidence_precision=1.0, stale_fallback_events=0, parse_count=0, changed_file_count=0, index_storage_bytes=0, wall_time_seconds=0.01), risk='', source='seed'), AuditEntry(tool=<RalphToolName.LIST_ALLOWED_ROOTS: 'list_allowed_roots'>, family=<AuditFamily.WORKSPACE_READ: 'workspace_read'>, outcome=<AuditOutcome.KEEP: 'keep'>, rationale='Configuration surface; no per-call optimization is appropriate.', counters=AuditCounters(transcript_tokens=24, returned_bytes=64, tool_calls=1, evidence_recall=1.0, evidence_precision=1.0, stale_fallback_events=0, parse_count=0, changed_file_count=0, index_storage_bytes=0, wall_time_seconds=0.01), risk='', source='seed'), AuditEntry(tool=<RalphToolName.GREP_FILES: 'grep_files'>, family=<AuditFamily.WORKSPACE_READ: 'workspace_read'>, outcome=<AuditOutcome.ADD_ARGUMENT: 'add_argument'>, rationale='Add use_index + rank_by + return_evidence_ids + dedupe_by_symbol + max_snippet_lines so indexed literal/phrase queries return ranked evidence handles. Eligibility contract preserves live grep semantics for regex/lookaround/backreferences.', counters=AuditCounters(transcript_tokens=288, returned_bytes=1536, tool_calls=1, evidence_recall=1.0, evidence_precision=1.0, stale_fallback_events=0, parse_count=0, changed_file_count=0, index_storage_bytes=0, wall_time_seconds=0.01), risk='', source='seed'), AuditEntry(tool=<RalphToolName.RALPH_REINDEX: 'ralph_reindex'>, family=<AuditFamily.WORKSPACE_READ: 'workspace_read'>, outcome=<AuditOutcome.KEEP: 'keep'>, rationale='Phase 1 ships the new tool with a bounded changed/full refresh; the schema is compact and stable so no rework is required in this slice.', counters=AuditCounters(transcript_tokens=128, returned_bytes=256, tool_calls=1, evidence_recall=1.0, evidence_precision=1.0, stale_fallback_events=0, parse_count=0, changed_file_count=0, index_storage_bytes=0, wall_time_seconds=0.01), risk='', source='seed'), AuditEntry(tool=<RalphToolName.RALPH_GRAPH: 'ralph_graph'>, family=<AuditFamily.WORKSPACE_READ: 'workspace_read'>, outcome=<AuditOutcome.ADD_ARGUMENT: 'add_argument'>, rationale='Phase 2/3 ships ralph_graph with prompt-exact neighbors, path, impact, hubs, and tests queries. Capability is WorkspaceRead; every response carries confidence, provenance, missing_data, freshness, truncation, and evidence_ids.', counters=AuditCounters(transcript_tokens=256, returned_bytes=1024, tool_calls=1, evidence_recall=1.0, evidence_precision=1.0, stale_fallback_events=0, parse_count=0, changed_file_count=0, index_storage_bytes=0, wall_time_seconds=0.01), risk='', source='seed'), AuditEntry(tool=<RalphToolName.SEARCH_FILES: 'search_files'>, family=<AuditFamily.WORKSPACE_SEARCH: 'workspace_search'>, outcome=<AuditOutcome.ADD_ARGUMENT: 'add_argument'>, rationale='Add ranked/role/changed_only/return_evidence_ids so agents stop walking 1000-path matches; ranking uses path/FTS/role/git-changed components only in Phase 1 (symbol/graph disabled).', counters=AuditCounters(transcript_tokens=160, returned_bytes=768, tool_calls=1, evidence_recall=1.0, evidence_precision=1.0, stale_fallback_events=0, parse_count=0, changed_file_count=0, index_storage_bytes=0, wall_time_seconds=0.01), risk='', source='seed'), AuditEntry(tool=<RalphToolName.LIST_DIRECTORY: 'list_directory'>, family=<AuditFamily.WORKSPACE_LIST: 'workspace_list'>, outcome=<AuditOutcome.ADD_ARGUMENT: 'add_argument'>, rationale='AC-09: add ``view=raw|compact|ranked|outline``, ``include_counts``, ``include_symbols``, ``changed_only``, ``limit_children``, and ``use_index=auto|always|never``. Compact/ranked/outline views are wired through the indexed handle in workspace/_read_handlers.py and the Phase-2 structure data is consumed when available. Raw/default behavior is preserved and the audit register now reflects the shipped behavior (the previous DEFER outcome was stale once the ranked view landed).', counters=AuditCounters(transcript_tokens=96, returned_bytes=384, tool_calls=1, evidence_recall=1.0, evidence_precision=1.0, stale_fallback_events=0, parse_count=0, changed_file_count=0, index_storage_bytes=0, wall_time_seconds=0.01), risk='', source='seed'), AuditEntry(tool=<RalphToolName.LIST_DIRECTORY_RECURSIVE: 'list_directory_recursive'>, family=<AuditFamily.WORKSPACE_LIST: 'workspace_list'>, outcome=<AuditOutcome.ADD_ARGUMENT: 'add_argument'>, rationale='Same as list_directory: the recursive sibling shares the compact/ranked/outline views and use_index fallbacks. The previous DEFER outcome is stale because the implementation in workspace/_read_handlers.py already routes through the indexed handle.', counters=AuditCounters(transcript_tokens=256, returned_bytes=2048, tool_calls=1, evidence_recall=1.0, evidence_precision=1.0, stale_fallback_events=0, parse_count=0, changed_file_count=0, index_storage_bytes=0, wall_time_seconds=0.01), risk='', source='seed'), AuditEntry(tool=<RalphToolName.DIRECTORY_TREE: 'directory_tree'>, family=<AuditFamily.WORKSPACE_LIST: 'workspace_list'>, outcome=<AuditOutcome.ADD_ARGUMENT: 'add_argument'>, rationale='AC-09: add ``view=raw|compact|ranked|outline``, ``include_counts``, ``include_symbols``, ``changed_only``, ``limit_children``, and ``use_index=auto|always|never``. directory_tree consumes the same Phase-2 structure data as list_directory; the previous DEFER outcome is stale now that the ranked/outline view is implemented and routed through the indexed handle.', counters=AuditCounters(transcript_tokens=384, returned_bytes=4096, tool_calls=1, evidence_recall=1.0, evidence_precision=1.0, stale_fallback_events=0, parse_count=0, changed_file_count=0, index_storage_bytes=0, wall_time_seconds=0.01), risk='', source='seed'), AuditEntry(tool=<RalphToolName.WRITE_FILE: 'write_file'>, family=<AuditFamily.WORKSPACE_MUTATE: 'workspace_mutate'>, outcome=<AuditOutcome.KEEP: 'keep'>, rationale='Write path is author-controlled; the index marks dirty paths after a successful write rather than changing write semantics.', counters=AuditCounters(transcript_tokens=80, returned_bytes=192, tool_calls=1, evidence_recall=1.0, evidence_precision=1.0, stale_fallback_events=0, parse_count=0, changed_file_count=0, index_storage_bytes=0, wall_time_seconds=0.01), risk='', source='seed'), AuditEntry(tool=<RalphToolName.EDIT_FILE: 'edit_file'>, family=<AuditFamily.WORKSPACE_MUTATE: 'workspace_mutate'>, outcome=<AuditOutcome.ADD_ARGUMENT: 'add_argument'>, rationale="Phase-1 adds expected_content_hash + reindex='auto' for safety. target/match_strategy/impact_preview are Phase 3 because impact needs ralph_graph neighbors/paths.", counters=AuditCounters(transcript_tokens=160, returned_bytes=512, tool_calls=1, evidence_recall=1.0, evidence_precision=1.0, stale_fallback_events=0, parse_count=0, changed_file_count=0, index_storage_bytes=0, wall_time_seconds=0.01), risk='', source='seed'), AuditEntry(tool=<RalphToolName.APPEND_FILE: 'append_file'>, family=<AuditFamily.WORKSPACE_MUTATE: 'workspace_mutate'>, outcome=<AuditOutcome.KEEP: 'keep'>, rationale='Append is a thin wrapper; dirty-path marking is wired in Phase 1.', counters=AuditCounters(transcript_tokens=72, returned_bytes=192, tool_calls=1, evidence_recall=1.0, evidence_precision=1.0, stale_fallback_events=0, parse_count=0, changed_file_count=0, index_storage_bytes=0, wall_time_seconds=0.01), risk='', source='seed'), AuditEntry(tool=<RalphToolName.CREATE_DIRECTORY: 'create_directory'>, family=<AuditFamily.WORKSPACE_MUTATE: 'workspace_mutate'>, outcome=<AuditOutcome.KEEP: 'keep'>, rationale='mkdirs is cheap and not agent-loop hot; dirty marking covers new directory contents downstream.', counters=AuditCounters(transcript_tokens=32, returned_bytes=64, tool_calls=1, evidence_recall=1.0, evidence_precision=1.0, stale_fallback_events=0, parse_count=0, changed_file_count=0, index_storage_bytes=0, wall_time_seconds=0.01), risk='', source='seed'), AuditEntry(tool=<RalphToolName.MOVE_FILE: 'move_file'>, family=<AuditFamily.WORKSPACE_MUTATE: 'workspace_mutate'>, outcome=<AuditOutcome.KEEP: 'keep'>, rationale='Move touches both src and dest; Phase-1 dirty marking handles both paths in a single call. No additional argument required.', counters=AuditCounters(transcript_tokens=64, returned_bytes=128, tool_calls=1, evidence_recall=1.0, evidence_precision=1.0, stale_fallback_events=0, parse_count=0, changed_file_count=0, index_storage_bytes=0, wall_time_seconds=0.01), risk='', source='seed'), AuditEntry(tool=<RalphToolName.COPY_FILE: 'copy_file'>, family=<AuditFamily.WORKSPACE_MUTATE: 'workspace_mutate'>, outcome=<AuditOutcome.KEEP: 'keep'>, rationale='Copy touches dest only; Phase-1 dirty marking handles the new path. Content-hash reuse during reindex is internal.', counters=AuditCounters(transcript_tokens=64, returned_bytes=128, tool_calls=1, evidence_recall=1.0, evidence_precision=1.0, stale_fallback_events=0, parse_count=0, changed_file_count=0, index_storage_bytes=0, wall_time_seconds=0.01), risk='', source='seed'), AuditEntry(tool=<RalphToolName.DELETE_PATH: 'delete_path'>, family=<AuditFamily.WORKSPACE_MUTATE: 'workspace_mutate'>, outcome=<AuditOutcome.KEEP: 'keep'>, rationale='Delete marks the path dirty so reindex can write a tombstone; no agent-visible argument change needed.', counters=AuditCounters(transcript_tokens=48, returned_bytes=96, tool_calls=1, evidence_recall=1.0, evidence_precision=1.0, stale_fallback_events=0, parse_count=0, changed_file_count=0, index_storage_bytes=0, wall_time_seconds=0.01), risk='', source='seed'), AuditEntry(tool=<RalphToolName.GIT_STATUS: 'git_status'>, family=<AuditFamily.GIT_READ: 'git_read'>, outcome=<AuditOutcome.ADD_ARGUMENT: 'add_argument'>, rationale='AC-11: ``format=compact`` returns a bounded summary card with changed-path ranking and unchanged-path elision. Raw behavior preserved for the default format.', counters=AuditCounters(transcript_tokens=64, returned_bytes=256, tool_calls=1, evidence_recall=1.0, evidence_precision=1.0, stale_fallback_events=0, parse_count=0, changed_file_count=0, index_storage_bytes=0, wall_time_seconds=0.01), risk='', source='seed'), AuditEntry(tool=<RalphToolName.GIT_DIFF: 'git_diff'>, family=<AuditFamily.GIT_READ: 'git_read'>, outcome=<AuditOutcome.ADD_ARGUMENT: 'add_argument'>, rationale='AC-11: ``format=summary`` returns a bounded card with changed files, plus per-file added/removed totals. Raw diff behavior preserved for the default format.', counters=AuditCounters(transcript_tokens=128, returned_bytes=512, tool_calls=1, evidence_recall=1.0, evidence_precision=1.0, stale_fallback_events=0, parse_count=0, changed_file_count=0, index_storage_bytes=0, wall_time_seconds=0.01), risk='', source='seed'), AuditEntry(tool=<RalphToolName.GIT_LOG: 'git_log'>, family=<AuditFamily.GIT_READ: 'git_read'>, outcome=<AuditOutcome.ADD_ARGUMENT: 'add_argument'>, rationale="Phase 4: ``format='summary'`` returns a compact JSON envelope ``{format, count, commits:[{short_sha, sha, subject}], bytes_in, bytes_out}`` for one bounded call. Default ``format='raw'`` preserves the legacy ``git log -<count> --oneline`` output byte-for-byte so existing callers are unaffected. Byte-savings on Phase 4 fixtures are >=30 percent versus the raw shape.", counters=AuditCounters(transcript_tokens=80, returned_bytes=320, tool_calls=1, evidence_recall=1.0, evidence_precision=1.0, stale_fallback_events=0, parse_count=0, changed_file_count=0, index_storage_bytes=0, wall_time_seconds=0.01), risk='', source='seed'), AuditEntry(tool=<RalphToolName.GIT_SHOW: 'git_show'>, family=<AuditFamily.GIT_READ: 'git_read'>, outcome=<AuditOutcome.ADD_ARGUMENT: 'add_argument'>, rationale="Phase 4: ``format='summary'`` returns a compact header-only JSON envelope ``{format, ref, kind, sha, short_sha, author_name, author_email, author_date, subject, parents, bytes_in, bytes_out, truncated:false}`` without the patch body. Default ``format='raw'`` preserves the legacy ``git show <ref>`` output byte-for-byte so existing callers are unaffected. Byte-savings on Phase 4 fixtures are >=30 percent versus the raw shape.", counters=AuditCounters(transcript_tokens=64, returned_bytes=256, tool_calls=1, evidence_recall=1.0, evidence_precision=1.0, stale_fallback_events=0, parse_count=0, changed_file_count=0, index_storage_bytes=0, wall_time_seconds=0.01), risk='', source='seed'), AuditEntry(tool=<RalphToolName.EXEC: 'exec'>, family=<AuditFamily.PROCESS: 'process'>, outcome=<AuditOutcome.ADD_ARGUMENT: 'add_argument'>, rationale='AC-11: ``format=summary`` returns a bounded JSON envelope with separate ``stdout_resource_id`` / ``stderr_resource_id`` replayable handles, per-stream spill paths, and head/tail previews. The bounded-timeout contract is preserved and raw behavior remains the default.', counters=AuditCounters(transcript_tokens=192, returned_bytes=768, tool_calls=1, evidence_recall=1.0, evidence_precision=1.0, stale_fallback_events=0, parse_count=0, changed_file_count=0, index_storage_bytes=0, wall_time_seconds=0.01), risk='', source='seed'), AuditEntry(tool=<RalphToolName.UNSAFE_EXEC: 'unsafe_exec'>, family=<AuditFamily.PROCESS: 'process'>, outcome=<AuditOutcome.KEEP: 'keep'>, rationale='Unsafe exec is unbounded by design; AC-11 summary mode is only on the bounded exec path. The unsafe variant keeps its current shape; the bounded exec summary is the production remediation.', counters=AuditCounters(transcript_tokens=192, returned_bytes=768, tool_calls=1, evidence_recall=1.0, evidence_precision=1.0, stale_fallback_events=0, parse_count=0, changed_file_count=0, index_storage_bytes=0, wall_time_seconds=0.01), risk='', source='seed'), AuditEntry(tool=<RalphToolName.RAW_EXEC: 'raw_exec'>, family=<AuditFamily.PROCESS: 'process'>, outcome=<AuditOutcome.KEEP: 'keep'>, rationale='Raw exec is the unfiltered escape hatch; the AC-11 summary mode is intentionally only on the bounded exec tool. The raw variant keeps its current shape so a single audit register can preserve the capability surface.', counters=AuditCounters(transcript_tokens=192, returned_bytes=768, tool_calls=1, evidence_recall=1.0, evidence_precision=1.0, stale_fallback_events=0, parse_count=0, changed_file_count=0, index_storage_bytes=0, wall_time_seconds=0.01), risk='', source='seed'), AuditEntry(tool=<RalphToolName.SUBMIT_MD_ARTIFACT: 'ralph_submit_md_artifact'>, family=<AuditFamily.ARTIFACT: 'artifact'>, outcome=<AuditOutcome.KEEP: 'keep'>, rationale='Markdown submission uses the shared line-anchored validator.', counters=AuditCounters(transcript_tokens=128, returned_bytes=256, tool_calls=1, evidence_recall=1.0, evidence_precision=1.0, stale_fallback_events=0, parse_count=0, changed_file_count=0, index_storage_bytes=0, wall_time_seconds=0.01), risk='', source='seed'), AuditEntry(tool=<RalphToolName.VERIFY_MD_ARTIFACT: 'ralph_verify_md_artifact'>, family=<AuditFamily.ARTIFACT: 'artifact'>, outcome=<AuditOutcome.KEEP: 'keep'>, rationale='Check-only validation calls the same markdown validator as submit.', counters=AuditCounters(transcript_tokens=96, returned_bytes=192, tool_calls=1, evidence_recall=1.0, evidence_precision=1.0, stale_fallback_events=0, parse_count=0, changed_file_count=0, index_storage_bytes=0, wall_time_seconds=0.01), risk='', source='seed'), AuditEntry(tool=<RalphToolName.STAGE_MD_ARTIFACT: 'ralph_stage_md_artifact'>, family=<AuditFamily.ARTIFACT: 'artifact'>, outcome=<AuditOutcome.KEEP: 'keep'>, rationale='Incremental markdown staging persists resumable drafts.', counters=AuditCounters(transcript_tokens=96, returned_bytes=192, tool_calls=1, evidence_recall=1.0, evidence_precision=1.0, stale_fallback_events=0, parse_count=0, changed_file_count=0, index_storage_bytes=0, wall_time_seconds=0.01), risk='', source='seed'), AuditEntry(tool=<RalphToolName.EDIT_MD_ARTIFACT: 'ralph_edit_md_artifact'>, family=<AuditFamily.ARTIFACT: 'artifact'>, outcome=<AuditOutcome.KEEP: 'keep'>, rationale='In-place draft edits repair a rejected document without re-transcribing it.', counters=AuditCounters(transcript_tokens=96, returned_bytes=256, tool_calls=1, evidence_recall=1.0, evidence_precision=1.0, stale_fallback_events=0, parse_count=0, changed_file_count=0, index_storage_bytes=0, wall_time_seconds=0.01), risk='', source='seed'), AuditEntry(tool=<RalphToolName.GET_MD_DRAFT: 'ralph_get_md_draft'>, family=<AuditFamily.ARTIFACT: 'artifact'>, outcome=<AuditOutcome.KEEP: 'keep'>, rationale='Draft retrieval supports resumable markdown authoring.', counters=AuditCounters(transcript_tokens=64, returned_bytes=256, tool_calls=1, evidence_recall=1.0, evidence_precision=1.0, stale_fallback_events=0, parse_count=0, changed_file_count=0, index_storage_bytes=0, wall_time_seconds=0.01), risk='', source='seed'), AuditEntry(tool=<RalphToolName.DISCARD_MD_DRAFT: 'ralph_discard_md_draft'>, family=<AuditFamily.ARTIFACT: 'artifact'>, outcome=<AuditOutcome.KEEP: 'keep'>, rationale='Explicit draft discard removes abandoned staged content.', counters=AuditCounters(transcript_tokens=64, returned_bytes=128, tool_calls=1, evidence_recall=1.0, evidence_precision=1.0, stale_fallback_events=0, parse_count=0, changed_file_count=0, index_storage_bytes=0, wall_time_seconds=0.01), risk='', source='seed'), AuditEntry(tool=<RalphToolName.FINALIZE_MD_ARTIFACT: 'ralph_finalize_md_artifact'>, family=<AuditFamily.ARTIFACT: 'artifact'>, outcome=<AuditOutcome.KEEP: 'keep'>, rationale='Finalization validates staged markdown before canonical submission.', counters=AuditCounters(transcript_tokens=96, returned_bytes=192, tool_calls=1, evidence_recall=1.0, evidence_precision=1.0, stale_fallback_events=0, parse_count=0, changed_file_count=0, index_storage_bytes=0, wall_time_seconds=0.01), risk='', source='seed'), AuditEntry(tool=<RalphToolName.REPORT_PROGRESS: 'report_progress'>, family=<AuditFamily.COORDINATION: 'coordination'>, outcome=<AuditOutcome.KEEP: 'keep'>, rationale='report_progress already emits a bounded text payload suffixed with ``PROGRESS_PIPELINE_MARKER`` so the idle watchdog can key on it. The structured marker matches the Phase-4 acceptance contract for coordination tools.', counters=AuditCounters(transcript_tokens=24, returned_bytes=48, tool_calls=1, evidence_recall=1.0, evidence_precision=1.0, stale_fallback_events=0, parse_count=0, changed_file_count=0, index_storage_bytes=0, wall_time_seconds=0.01), risk='', source='seed'), AuditEntry(tool=<RalphToolName.DECLARE_COMPLETE: 'declare_complete'>, family=<AuditFamily.COORDINATION: 'coordination'>, outcome=<AuditOutcome.KEEP: 'keep'>, rationale='declare_complete writes a HMAC-signed completion sentinel and emits a bounded text payload with the completion marker. The structured behavior matches the Phase-4 acceptance contract for coordination tools.', counters=AuditCounters(transcript_tokens=16, returned_bytes=32, tool_calls=1, evidence_recall=1.0, evidence_precision=1.0, stale_fallback_events=0, parse_count=0, changed_file_count=0, index_storage_bytes=0, wall_time_seconds=0.01), risk='', source='seed'), AuditEntry(tool=<RalphToolName.COORDINATE: 'coordinate'>, family=<AuditFamily.COORDINATION: 'coordination'>, outcome=<AuditOutcome.KEEP: 'keep'>, rationale='coordinate is artifact.plan_write-gated; the response is suffixed with the ``[Coordination event emitted to pipeline]`` marker so the parent process can observe structured events. The structured marker matches the Phase-4 acceptance contract for coordination tools.', counters=AuditCounters(transcript_tokens=96, returned_bytes=192, tool_calls=1, evidence_recall=1.0, evidence_precision=1.0, stale_fallback_events=0, parse_count=0, changed_file_count=0, index_storage_bytes=0, wall_time_seconds=0.01), risk='', source='seed'), AuditEntry(tool=<RalphToolName.READ_ENV: 'read_env'>, family=<AuditFamily.COORDINATION: 'coordination'>, outcome=<AuditOutcome.KEEP: 'keep'>, rationale='Single-string env read; current output is already bounded.', counters=AuditCounters(transcript_tokens=16, returned_bytes=32, tool_calls=1, evidence_recall=1.0, evidence_precision=1.0, stale_fallback_events=0, parse_count=0, changed_file_count=0, index_storage_bytes=0, wall_time_seconds=0.01), risk='', source='seed'), AuditEntry(tool=<RalphToolName.WEB_SEARCH: 'web_search'>, family=<AuditFamily.WEB: 'web'>, outcome=<AuditOutcome.ADD_ARGUMENT: 'add_argument'>, rationale="Phase 4: ``format='summary'`` returns a compact JSON envelope with truncated snippets (<=240 chars), a ``backend_chain_used`` counter, and ``bytes_in``/``bytes_out`` size counters. Default ``format='raw'`` preserves the legacy Title/URL/Snippet text shape byte-for-byte so existing callers are unaffected. Byte-savings on Phase 4 fixtures are >=30 percent versus the raw shape.", counters=AuditCounters(transcript_tokens=320, returned_bytes=1024, tool_calls=1, evidence_recall=1.0, evidence_precision=1.0, stale_fallback_events=0, parse_count=0, changed_file_count=0, index_storage_bytes=0, wall_time_seconds=0.01), risk='', source='seed'), AuditEntry(tool=<RalphToolName.VISIT_URL: 'visit_url'>, family=<AuditFamily.WEB: 'web'>, outcome=<AuditOutcome.ADD_ARGUMENT: 'add_argument'>, rationale="Phase 4: ``format='metadata'`` returns a bounded JSON envelope with ``head_preview``, ``byte_count``, and ``bytes_in``/``bytes_out`` size counters and DROPS the full text body inline. Default ``format='raw'`` preserves the legacy text body shape byte-for-byte so existing callers are unaffected. Byte-savings on Phase 4 fixtures are >=30 percent versus the raw shape.", counters=AuditCounters(transcript_tokens=512, returned_bytes=2048, tool_calls=1, evidence_recall=1.0, evidence_precision=1.0, stale_fallback_events=0, parse_count=0, changed_file_count=0, index_storage_bytes=0, wall_time_seconds=0.01), risk='', source='seed'), AuditEntry(tool=<RalphToolName.DOWNLOAD_URL: 'download_url'>, family=<AuditFamily.WEB: 'web'>, outcome=<AuditOutcome.ADD_ARGUMENT: 'add_argument'>, rationale="Phase 4: ``format='summary'`` returns metadata + a bounded ``head_preview`` (first 240 bytes) + a ``sha256`` fingerprint (first 16 hex chars) and DOES NOT echo the downloaded body inline. Default ``format='raw'`` preserves the legacy metadata-only envelope byte-for-byte so existing callers are unaffected. Byte-savings on Phase 4 fixtures are >=30 percent versus the raw shape.", counters=AuditCounters(transcript_tokens=512, returned_bytes=2048, tool_calls=1, evidence_recall=1.0, evidence_precision=1.0, stale_fallback_events=0, parse_count=0, changed_file_count=0, index_storage_bytes=0, wall_time_seconds=0.01), risk='', source='seed'), AuditEntry(tool=<RalphToolName.READ_IMAGE: 'read_image'>, family=<AuditFamily.MEDIA: 'media'>, outcome=<AuditOutcome.ADD_ARGUMENT: 'add_argument'>, rationale="Phase 4: ``format='metadata'`` returns a bounded JSON envelope with ``mime_type``, ``size_bytes``, ``sha256``, ``width``, ``height`` (PNG only), and an ``inline_only`` flag. Default ``format='inline'`` preserves the legacy image content block byte-for-byte. ``handle_read_image`` never persists a ``ralph://media/{artifact_id}`` artifact so the envelope's ``resource_handle`` is always ``None``.", counters=AuditCounters(transcript_tokens=128, returned_bytes=512, tool_calls=1, evidence_recall=1.0, evidence_precision=1.0, stale_fallback_events=0, parse_count=0, changed_file_count=0, index_storage_bytes=0, wall_time_seconds=0.01), risk='', source='seed'), AuditEntry(tool=<RalphToolName.READ_MEDIA: 'read_media'>, family=<AuditFamily.MEDIA: 'media'>, outcome=<AuditOutcome.ADD_ARGUMENT: 'add_argument'>, rationale="Phase 4: ``format='metadata'`` returns a bounded JSON envelope with ``media_kind``, ``mime_type``, ``size_bytes``, ``sha256``, and a replayable ``resource_handle`` (``ralph://media/{artifact_id}``) when the underlying delivery registered a Ralph-owned artifact. Default ``format='inline'`` preserves the legacy block shape byte-for-byte. Inline media bytes are dropped in metadata mode.", counters=AuditCounters(transcript_tokens=192, returned_bytes=768, tool_calls=1, evidence_recall=1.0, evidence_precision=1.0, stale_fallback_events=0, parse_count=0, changed_file_count=0, index_storage_bytes=0, wall_time_seconds=0.01), risk='', source='seed'), AuditEntry(tool=<RalphToolName.RALPH_INDEX_STATUS: 'ralph_index_status'>, family=<AuditFamily.METADATA: 'metadata'>, outcome=<AuditOutcome.KEEP: 'keep'>, rationale='Phase 1 ships the new tool with the documented health/freshness contract; the schema is compact and stable so no rework is required in this slice.', counters=AuditCounters(transcript_tokens=96, returned_bytes=384, tool_calls=1, evidence_recall=1.0, evidence_precision=1.0, stale_fallback_events=0, parse_count=0, changed_file_count=0, index_storage_bytes=4096, wall_time_seconds=0.01), risk='', source='seed'))¶
Immutable Phase 0 audit register; one entry per Ralph-owned MCP tool.
The static seed values are conservative baseline measurements gathered on the in-tree fixtures (see
tests/test_explore_audit_register.pyfor the per-family baseline flow contracts). They are not arbitrary placeholders: each entry’s counters are pinned to the smallest non-zero value that satisfies theAuditCountersvalidation contract.
- class ralph.mcp.explore.audit_register.AuditCounters(transcript_tokens, returned_bytes, tool_calls, evidence_recall, evidence_precision, stale_fallback_events, parse_count, changed_file_count, index_storage_bytes, wall_time_seconds)[source]¶
Bases:
objectRequired baseline research-gate counters for a single MCP tool.
All fields are required and non-null. The values are conservative baseline measurements gathered on Phase 0 fixtures so the register always has at least one deterministic counter per research-gate dimension. Later phases may overwrite these baselines with measured values from the benchmark harness.
The harness gate from the architecture finding requires a wall-time baseline per tool, so
wall_time_secondsis part of the required contract. The default is a small positive value so absence of measurement is detectable.- Parameters:
transcript_tokens (int)
returned_bytes (int)
tool_calls (int)
evidence_recall (float)
evidence_precision (float)
stale_fallback_events (int)
parse_count (int)
changed_file_count (int)
index_storage_bytes (int)
wall_time_seconds (float)
- class ralph.mcp.explore.audit_register.AuditEntry(tool, family, outcome, rationale, counters, risk='', source='seed')[source]¶
Bases:
objectA single audit entry: tool outcome, rationale, and counters.
AC-01 contract. The
rationalefield is required for every entry. Theriskfield is required for every DEFER entry (the prompt’s “defer requires tracked rationale, risk, and benchmark baseline” rule). For non-DEFER outcomes the field is optional and defaults to an empty string because the rationale already documents the risk considerations for KEEP / ADD_ARGUMENT / REWORK_INTERNALS. Thecountersfield must include a wall-time baseline.- Parameters:
tool (RalphToolName)
family (AuditFamily)
outcome (AuditOutcome)
rationale (str)
counters (AuditCounters)
risk (str)
source (str)
- class ralph.mcp.explore.audit_register.AuditFamily(*values)[source]¶
Bases:
StrEnumClosed vocabulary of tool families used by the audit register.
- class ralph.mcp.explore.audit_register.AuditOutcome(*values)[source]¶
Bases:
StrEnumClosed vocabulary of audit outcomes for a single MCP tool.
- class ralph.mcp.explore.audit_register.Measurement(tool, counters, source='seed')[source]¶
Bases:
objectOne reproducible measurement for a single audit entry.
toolis the canonicalRalphToolNamethe measurement applies to.countersis the measuredAuditCountersrecord (callers MUST source it from a realrun_benchmarkresult or a fixture; the type prevents synthesized values). Thesourcestring is a free-form provenance label (e.g."tests/test_explore_bench_gates.py:Q1") that the audit register preserves alongside the merged entry.- Parameters:
tool (RalphToolName)
counters (AuditCounters)
source (str)
- class ralph.mcp.explore.audit_register.RefreshResult(register, applied, unmeasured, duplicates, source='refresh_audit_register')[source]¶
Bases:
objectOutcome of
refresh_audit_register().registeris the rebuilt tuple ofAuditEntrywith the measured counters overlaid.appliedis the set of tools for which a measurement was supplied.unmeasuredis the set of tools that kept the seed baseline.duplicatesis the set of tool names that received more than one measurement; refresh uses the first and reports the rest so callers can detect and correct upstream bench fixtures.- Parameters:
register (tuple[AuditEntry, ...])
applied (frozenset[RalphToolName])
unmeasured (frozenset[RalphToolName])
duplicates (frozenset[RalphToolName])
source (str)
- ralph.mcp.explore.audit_register.audit_register()[source]¶
Return the audit register (immutable snapshot).
Wrapped as a function so future phases can swap in a measured register without changing call sites. The default view returns the static seed; the bench-driven measured register is built by
refresh_audit_register().- Return type:
tuple[AuditEntry, …]
- ralph.mcp.explore.audit_register.bench_results_to_measurements(results)[source]¶
Convert BenchmarkResult records into Measurement objects.
AC-06 measured provenance. The audit gate’s source field must point to a reproducible real benchmark fixture identifier, not the static seed baseline. Each BenchmarkResult is attributed to its primary RalphToolName via the _FIXTURE_PRIMARY_TOOL mapping; the indexed counters (the gate’s research target) drive the AuditCounters payload; the provenance label is the fixture’s question_id so a downstream consumer can replay the exact fixture.
Multiple fixtures can attribute to the same RalphToolName (Q1 and Q3 both drive grep_files); the resulting Measurement records collide in refresh_audit_register and the duplicate is reported via RefreshResult.duplicates so the bench rerun can fix the attribution.
Fixtures whose question_id is not in _FIXTURE_PRIMARY_TOOL are skipped (the bench harness can grow without breaking the audit pipeline until the mapping is extended).
- Parameters:
results (Sequence[BenchmarkResult])
- Return type:
list[Measurement]
- ralph.mcp.explore.audit_register.refresh_audit_register(measurements=None, *, source='refresh_audit_register')[source]¶
Return a new register with measured counters overlaid on the seed.
AC-06 measured provenance: each
Measurementreplaces the seedAuditCountersfor the matchingRalphToolName. The provenance (rationale, risk, family, outcome) is kept intact from the seed so the audit gate’s outcome-closed- vocabulary invariant is preserved. A measurement for a tool that is not in the seed register is rejected with aValueErrorso a staleMeasurementcannot silently introduce a phantom tool entry.The function is deterministic and pure: callers can replay it with the same measurement sequence and get the same register. Tests assert this and the duplicate-detection contract so a future refactor that drops the dedup check breaks the gate.
- Parameters:
measurements (Sequence[Measurement] | None)
source (str)
- Return type:
ralph.mcp.explore.family_baseline¶
Per-family representative baseline flows for the audit register.
The architecture finding requires that every audited family have one deterministic representative baseline-flow record so the indexed path can be measured against the existing operation model without relying on agent transcripts. Each record names the current-operation script (the tool calls an agent would make today), the baseline counter snapshot (transcript tokens, returned bytes, tool calls, recall), and the catalog-token evidence (the rough schema-token cost of the tools involved). The Phase 0 numbers are conservative so a future re-measurement can only ever shrink them, never inflate them silently.
Required family coverage (AC-01 / AC-04): read, list, search, edit, git, artifact, exec. These are the seven families whose existing tool flow was measured in Phase 0 and whose indexed path (or compact/summary mode) is compared against that flow in the benchmark gate.
This module lives apart from audit_register.py so the audit’s
MCP-timeout scan (a full ast.parse + walk) does not have to
process the long literal strings in the baseline evidence
records; the baseline data is consumed only by the audit and
benchmark code paths, not by the MCP handlers.
- class ralph.mcp.explore.family_baseline.FamilyBaselineFlow(family, name, current_operation_script, counters, catalog_token_evidence)[source]¶
Bases:
objectDeterministic representative baseline flow for a single family.
The record is intentionally lightweight: name + tool script + baseline counters + catalog-token evidence. The benchmark harness may use this to assert the indexed flow beats the baseline on the same question without re-deriving the baseline from a live transcript.
- Parameters:
family (AuditFamily)
name (str)
current_operation_script (tuple[str, ...])
counters (AuditCounters)
catalog_token_evidence (str)
- ralph.mcp.explore.family_baseline.family_baseline_flows()[source]¶
Return the immutable per-family representative baseline flow.
- Return type:
tuple[FamilyBaselineFlow, …]
ralph.mcp.explore.bench¶
Fixed-tokenizer scripted-flow benchmark harness for the indexed exploration substrate.
This module owns the deterministic harness that proves the research-gate counters from the architecture finding:
transcript_tokens— token count of the full scripted transcript (tool descriptions + inputs + outputs + final evidence context) computed by a fixed, pinned tokenizer.returned_bytes— deterministic secondary proxy of transcript cost.tool_calls— number of scripted tool calls per flow.wall_time— wall-clock seconds for the scripted flow.stale_fallback_events— count of stale or fallback responses.evidence_recall— required_evidence / expected_required.evidence_precision— required_evidence / returned_evidence.parse_count— number of files reparsed during the reindex step.changed_file_count— number of files marked dirty and reindexed.index_storage_bytes— disk size of the SQLite index.
The harness runs scripted tool flows over tmp_path workspaces,
NOT a live agent. Wall time uses a constructor-injected Clock
(defaults to a real clock; tests inject FakeClock to avoid the
audit_test_policy wall-clock violation).
Required deterministic questions (per the architecture finding):
Q1 — find where a tool is registered.
Q2 — find likely tests for a handler.
Q3 — estimate rename impact via lexical callers (graph impact is Phase 3, so Q3 uses lexical callers only; the deferral register records that semantic impact is deferred).
- class ralph.mcp.explore.bench.BenchmarkFixture(question_id, description, workspace_files, baseline_script, indexed_script, expected_evidence_ids, max_returned_bytes, max_tool_calls, requires_reindex=False, catalog_tokens=0, final_evidence_tokens=0)[source]¶
Bases:
objectA single benchmark fixture (one question, two flows).
AC-12: the fixture carries its own bounded transcript counters (
catalog_tokensandfinal_evidence_tokens) so the harness can derive the full scripted-transcript total without the caller passing defaults of zero. The bench harness derives catalog tokens from the visible tool descriptions/input schemas the harness itself enumerates; callers do not need to inject them.- Parameters:
question_id (str)
description (str)
workspace_files (Mapping[str, str])
baseline_script (tuple[ScriptedCall, ...])
indexed_script (tuple[ScriptedCall, ...])
expected_evidence_ids (tuple[str, ...])
max_returned_bytes (int)
max_tool_calls (int)
requires_reindex (bool)
catalog_tokens (int)
final_evidence_tokens (int)
- class ralph.mcp.explore.bench.BenchmarkResult(question_id, baseline, indexed, parse_count=0, changed_file_count=0, index_storage_bytes=0, notes=<factory>)[source]¶
Bases:
objectResult of a single benchmark question (one fixture).
- Parameters:
question_id (str)
baseline (BenchmarkCounters)
indexed (BenchmarkCounters)
parse_count (int)
changed_file_count (int)
index_storage_bytes (int)
notes (tuple[str, ...])
- class ralph.mcp.explore.bench.ScriptedCall(tool, params, expected_evidence_ids=())[source]¶
Bases:
objectA single scripted tool call used by a benchmark fixture.
- Parameters:
tool (str)
params (Mapping[str, object])
expected_evidence_ids (tuple[str, ...])
- ralph.mcp.explore.bench.derive_visible_catalog(mcp_config=None)[source]¶
Return registered schema-bearing entries for visible Ralph-owned tools.
AC-12 (acceptance gate): the benchmark harness MUST derive the visible catalog from the registered Ralph-owned registry/specs so the
catalog_tokenscounter reflects the actual MCPtools/listbytes an agent sees rather than a synthetic fixture-provided constant. The defaultmcp_configargument lets unit tests pin the catalog without instantiating a full bridge; production callers should pass the activeMcpConfig.The helper is intentionally lazy: it imports the bridge and
McpConfigon first call so the harness module stays cheap to import and unit tests can stub the bridge without instantiating the full MCP server. The tuple is deterministic across runs (the underlyingtool_specsis deterministic) so the gate tests can pin both the count and the order.- Parameters:
mcp_config (object | None)
- Return type:
tuple[object, …]
- ralph.mcp.explore.bench.run_benchmark(fixture, *, baseline_executor, indexed_executor, clock=None, expected_evidence_ids=None, catalog_tokens=None, final_evidence_tokens=None, visible_tool_catalog=None)[source]¶
Run a fixture’s baseline and indexed flows and produce a result.
baseline_executorandindexed_executorare scripted flows over the existing MCP handlers; they MUST NOT call a live LLM agent. They are pure functions of (call) -> result-dict.AC-07: each executor is invoked exactly
len(script)times (one invocation per scripted call) so the tool-call counters equal the number of executor invocations a counting harness observes. Recall/precision are derived from the fixture’sexpected_evidence_idstruth set and the indexed executor’s actual returnedevidence_idscollected during the SAME pass that records the indexed counters. The previous implementation replayed the indexed script solely to gather evidence ids, which silently doubled executor invocations while leaving the reported counter atlen(script); that double-execution broke the “no extra scripted tool calls” budget and let counting executors observe hidden work.Callers may override the truth set via
expected_evidence_ids; the default is the fixture’s declaredexpected_evidence_ids(a non-empty, unique tuple, by contract).AC-12 (full transcript):
catalog_tokens(bounded serialized visible tool descriptions/input schemas) andfinal_evidence_tokens(the compact evidence context the agent keeps after the flow ends) are added once per script so the transcript cost matches the research gate’s “full scripted transcript” definition. Defaults of0keep the harness back-compat with callers that pass only the script.- Parameters:
fixture (BenchmarkFixture)
baseline_executor (Callable[[ScriptedCall], Mapping[str, object]])
indexed_executor (Callable[[ScriptedCall], Mapping[str, object]])
clock (Clock | None)
expected_evidence_ids (Sequence[str] | None)
catalog_tokens (int | None)
final_evidence_tokens (int | None)
visible_tool_catalog (Sequence[object] | None)
- Return type:
ralph.mcp.explore.deferred_phases¶
Tracked deferral register for Phase 5 of the indexed exploration plan.
Each entry records the phase, its deliverables, a non-empty rationale
(gated on Phase 1 measurement passing / Phase 2 structure existing /
benchmark justification), the risk of staying deferred, and the
baseline_counters snapshot. The latter is filled by Phase 1 benchmark
harness (see ralph.mcp.explore.bench).
This is a pure data module; it has no I/O and is fully black-box testable. Tests assert every deferred phase has a non-empty rationale and a baseline_counters reference.
- ralph.mcp.explore.deferred_phases.DEFERRED_PHASES: Final[tuple[DeferredPhase, ...]] = (DeferredPhase(phase_id='phase_5', title='Optional adapters (NetworkX / Kuzu / Tree-sitter)', deliverables=('NetworkX offline graph metrics behind a feature flag', 'Kuzu adapter gated by measured SQLite bottleneck evidence', 'FTS + graph + git-change hybrid ranking with explicit scores', 'Tree-sitter language parsers after Python/Markdown proves useful'), deferral_rationale="Phase 5 is optional per the prompt. Phases 0-4 are fully shipped (Python + Markdown structure, ``ralph_graph``, impact-aware editing, and Phase-4 non-index remediation), so the only remaining deferrals are the optional adapters that are gated on measured SQLite bottleneck evidence. Kuzu's README says the project is being archived/moved, so it cannot be a phase-1 dependency. NetworkX is offline metrics only. Tree-sitter is multi-language coverage that the shipped Python+Markdown extractors must prove useful first.", risk='Limited to measured SQLite bottlenecks; if Phase 1 measurement reveals graph-walk performance problems, Phase 5 is the path to fix them. If a future audit discovers a regression in the shipped Phases 0-4 implementation, the rationale and deliverables must be re-audited; the audit_register invariant test_audit_register_tracks_defer_outcomes pins the empty-defer contract so a drift is caught immediately.', baseline_counters={<RalphToolName.READ_FILE: 'read_file'>: 120, <RalphToolName.READ_MULTIPLE_FILES: 'read_multiple_files'>: 192, <RalphToolName.STAT_PATH: 'stat_path'>: 48, <RalphToolName.LIST_ALLOWED_ROOTS: 'list_allowed_roots'>: 24, <RalphToolName.GREP_FILES: 'grep_files'>: 288, <RalphToolName.RALPH_REINDEX: 'ralph_reindex'>: 128, <RalphToolName.RALPH_GRAPH: 'ralph_graph'>: 256, <RalphToolName.SEARCH_FILES: 'search_files'>: 160, <RalphToolName.LIST_DIRECTORY: 'list_directory'>: 96, <RalphToolName.LIST_DIRECTORY_RECURSIVE: 'list_directory_recursive'>: 256, <RalphToolName.DIRECTORY_TREE: 'directory_tree'>: 384, <RalphToolName.WRITE_FILE: 'write_file'>: 80, <RalphToolName.EDIT_FILE: 'edit_file'>: 160, <RalphToolName.APPEND_FILE: 'append_file'>: 72, <RalphToolName.CREATE_DIRECTORY: 'create_directory'>: 32, <RalphToolName.MOVE_FILE: 'move_file'>: 64, <RalphToolName.COPY_FILE: 'copy_file'>: 64, <RalphToolName.DELETE_PATH: 'delete_path'>: 48, <RalphToolName.GIT_STATUS: 'git_status'>: 64, <RalphToolName.GIT_DIFF: 'git_diff'>: 128, <RalphToolName.GIT_LOG: 'git_log'>: 80, <RalphToolName.GIT_SHOW: 'git_show'>: 64, <RalphToolName.EXEC: 'exec'>: 192, <RalphToolName.UNSAFE_EXEC: 'unsafe_exec'>: 192, <RalphToolName.RAW_EXEC: 'raw_exec'>: 192, <RalphToolName.SUBMIT_MD_ARTIFACT: 'ralph_submit_md_artifact'>: 128, <RalphToolName.VERIFY_MD_ARTIFACT: 'ralph_verify_md_artifact'>: 96, <RalphToolName.STAGE_MD_ARTIFACT: 'ralph_stage_md_artifact'>: 96, <RalphToolName.EDIT_MD_ARTIFACT: 'ralph_edit_md_artifact'>: 96, <RalphToolName.GET_MD_DRAFT: 'ralph_get_md_draft'>: 64, <RalphToolName.DISCARD_MD_DRAFT: 'ralph_discard_md_draft'>: 64, <RalphToolName.FINALIZE_MD_ARTIFACT: 'ralph_finalize_md_artifact'>: 96, <RalphToolName.REPORT_PROGRESS: 'report_progress'>: 24, <RalphToolName.DECLARE_COMPLETE: 'declare_complete'>: 16, <RalphToolName.COORDINATE: 'coordinate'>: 96, <RalphToolName.READ_ENV: 'read_env'>: 16, <RalphToolName.WEB_SEARCH: 'web_search'>: 320, <RalphToolName.VISIT_URL: 'visit_url'>: 512, <RalphToolName.DOWNLOAD_URL: 'download_url'>: 512, <RalphToolName.READ_IMAGE: 'read_image'>: 128, <RalphToolName.READ_MEDIA: 'read_media'>: 192, <RalphToolName.RALPH_INDEX_STATUS: 'ralph_index_status'>: 96}),)¶
Immutable Phase 5 deferral register, snapshot at module import.
- class ralph.mcp.explore.deferred_phases.DeferredPhase(phase_id, title, deliverables, deferral_rationale, risk, baseline_counters=<factory>)[source]¶
Bases:
objectA single deferred phase tracked under the audit register.
- Parameters:
phase_id (str)
title (str)
deliverables (tuple[str, ...])
deferral_rationale (str)
risk (str)
baseline_counters (dict[str, int | float | None])
ralph.mcp.explore.dirty_paths¶
Persisted dirty-path queue for the indexed exploration substrate.
Write handlers mark affected paths in this queue after a successful workspace mutation. The next reindex consumes the queue.
The queue is persisted in SQLite so crashes and resumed sessions do not lose refresh work. Marking is O(1); the call never blocks on an active reindex (it only inserts a row, the reindex writer observes the queue when it runs).
The queue is owned by ExploreIndex (defined in
handlers.py), which is the optional, lazily-initialized handle on
the session/workspace. When the index is disabled (handle is
None), handlers behave exactly as today — no metadata added, no
dirty marking.
- class ralph.mcp.explore.dirty_paths.ExploreIndexLike(*args, **kwargs)[source]¶
Bases:
ProtocolThe narrow protocol handlers consume to mark dirty paths.
Implemented by
ralph.mcp.explore.handlers.ExploreIndex. Kept as a Protocol so tests can fake it with a one-line stub without inheriting from a heavy base class.- property reindex_in_progress: bool¶
True when a reindex writer is active for the attached handle.
- property store: ExploreStoreLike | None¶
Optional SQLite-backed store handle.
Nonefor NoOp handles.
- class ralph.mcp.explore.dirty_paths.NoOpExploreIndex[source]¶
Bases:
objectDrop-in index handle used when indexing is disabled.
Implements the same protocol but does nothing. Handlers can unconditionally call
index.mark_dirty(...); the no-op variant silently swallows the call, preserving the live behavior contract.
- ralph.mcp.explore.dirty_paths.build_sqlite_index_handle(store)[source]¶
Construct a handle that writes to a SQLite-backed
store.Production code passes a store constructed by
handlers.py(which also owns the path to the.agent/ralph-explore/index directory). Tests use this helper to wire a fake store into the handler path.- Parameters:
store (ExploreStore)
- Return type:
- ralph.mcp.explore.dirty_paths.mark_path(handle, *, path, source_tool, reason='mutated')[source]¶
Helper that always coerces the path before calling the handle.
Centralizes the path-normalization call so handlers stay tidy.
- Parameters:
handle (ExploreIndexLike | None)
path (str)
source_tool (str)
reason (str)
- Return type:
None
- ralph.mcp.explore.dirty_paths.mark_paths(handle, *, paths, source_tool, reason='mutated')[source]¶
Helper for handlers that touch multiple paths (move/copy).
- Parameters:
handle (ExploreIndexLike | None)
paths (list[str])
source_tool (str)
reason (str)
- Return type:
None
- ralph.mcp.explore.dirty_paths.resolve_explore_index(session)[source]¶
Return the explore index handle attached to
sessionif any.Reads
session.explore_indexlazily. ReturnsNonewhen the attribute is missing orNoneso handlers fall back to live behavior (the current contract).- Parameters:
session (object)
- Return type:
ExploreIndexLike | None
ralph.mcp.explore.graph¶
Bounded graph queries over the indexed exploration substrate.
Implements the prompt-exact ralph_graph MCP tool:
neighbors— bounded-depth adjacency walk with relation allowlists and confidence ordering.path— shortest-path search between two endpoints.impact— conservative caller/importer/test impact for a givenchange_kind.hubs— deterministic ranking by in/out-degree weighted by relation priority.tests— deterministic test suggestions for a target symbol.
All queries are bounded by limit (default 25, max 100),
depth / max_paths where applicable, and freshness
policy. Every result records confidence, provenance,
evidence_ids, missing_data, index_generation,
is_stale, truncated, and bounded byte output.
The module is read-only against the ExploreStore. It
never mutates rows; it only reads from the structure tables
introduced in Phase 2.
- class ralph.mcp.explore.graph.GraphEdge(source, target, relation, path, confidence, provenance, reason=None, evidence_id=None)[source]¶
Bases:
objectA single graph edge returned by a query.
- Parameters:
source (str)
target (str)
relation (str)
path (str)
confidence (float)
provenance (str)
reason (str | None)
evidence_id (str | None)
- class ralph.mcp.explore.graph.GraphNode(id, kind, label, path, confidence, provenance, evidence_ids=())[source]¶
Bases:
objectA single graph node returned by a query.
- Parameters:
id (str)
kind (str)
label (str)
path (str)
confidence (float)
provenance (str)
evidence_ids (tuple[str, ...])
- class ralph.mcp.explore.graph.GraphResult(query_type, nodes, edges, paths=(), impacted_files=(), suggested_tests=(), confidence=0.0, provenance='extracted', evidence_ids=(), missing_data=(), index_generation=0, is_stale=False, truncated=False, deadline_exceeded=False, cancelled=False, metadata=<factory>)[source]¶
Bases:
objectA complete graph query response.
- Parameters:
query_type (str)
nodes (tuple[GraphNode, ...])
edges (tuple[GraphEdge, ...])
paths (tuple[tuple[str, ...], ...])
impacted_files (tuple[str, ...])
suggested_tests (tuple[GraphNode, ...])
confidence (float)
provenance (str)
evidence_ids (tuple[str, ...])
missing_data (tuple[str, ...])
index_generation (int)
is_stale (bool)
truncated (bool)
deadline_exceeded (bool)
cancelled (bool)
metadata (Mapping[str, object])
- cancelled: bool¶
True when the caller asked for cooperative cancellation. Same bounded-incomplete contract as
deadline_exceeded.
- deadline_exceeded: bool¶
True when the query returned a bounded incomplete result because the caller’s deadline elapsed. Always accompanied by
missing_datacontaining"deadline_exceeded"and a zeroed/truncated body. Mutable work in flight is discarded; readers see the last committed generation, not partial rows.
ralph.mcp.explore.path_filter¶
Path-filter helpers shared by the indexed search/grep paths.
Ponytail: small, single-purpose helper so the indexed grep/search code path can apply the same path/include/exclude semantics as the live grep branch without dragging in the full workspace traversal machinery. The filter is a pure function of (path) -> bool.
- ralph.mcp.explore.path_filter.compile_path_filter(*, path_prefix, include_globs, exclude_globs)[source]¶
Return a filter function or None when no filter is needed.
The filter accepts a normalized workspace-relative POSIX path and returns True when the path passes every constraint. Constraints apply in this order:
path_prefix(when non-empty) requires the path to equal the prefix or start withprefix + '/'.include_globs(when non-empty) requires the path to match at least one glob (case-sensitivefnmatch).exclude_globs(when non-empty) rejects paths that match any glob.
- Parameters:
path_prefix (str | None)
include_globs (Sequence[str] | None)
exclude_globs (Sequence[str] | None)
- Return type:
Callable[[str], bool] | None
ralph.mcp.explore.structure¶
Deterministic Python AST and Markdown structure extraction.
The structure module owns the syntax-derived rows that
back ralph.mcp.explore.graph. Every edge and span carries a
deterministic id derived from normalized path, span coordinates,
kind, and extractor version so a no-op reindex produces stable
logical rows.
Extraction rules (per the prompt):
containsedges: file contains class / function / heading / body span. Always emitted when a child span exists.defines: emitted only from parser-recognized definition spans (ast.FunctionDef/ast.AsyncFunctionDef/ast.ClassDef/ module-level assignments with a syntactically explicit target, Markdown ATX / Setext headings).imports: emitted only fromast.Import/ast.ImportFrom. Resolution to a local symbol/file is a separate inferred edge with lower confidence.calls_syntax: emitted only when syntax contains a call expression and the callee token/range can be recorded. It does NOT claim semantic dispatch.references_text: emitted from exact token / text matches after identifier normalization. Lower confidence than parser edges.inherits_syntax: emitted only from Python class bases.tests: emitted from deterministic test-naming conventions (path patterns,test_prefix) plus import/references to a target symbol.mentions: emitted only from comments / docs / Markdown text with exact matched spans.
Confidence and provenance are recorded on every edge so callers can
distinguish extracted (parser-verified) from inferred (text /
naming) and unknown (dynamic / reflection / unresolved).
The module is pure (no I/O); the reindex pipeline calls
extract_structure() and persists the returned tuples through
ralph.mcp.explore.store.ExploreStore.replace_structure_rows.
- exception ralph.mcp.explore.structure.PythonExtractionError[source]¶
Bases:
ExceptionTyped extraction failure raised when Python source fails to parse.
PA-001 / AC-02: the reindex pipeline translates this exception in its preflight so the per-path failure path appends the file to
failed_filesand continues the sorted path loop. Prior lexical/structure rows for the path remain queryable, the dirty entry stays queued, and the failure is retried on the next reindex pass.
- class ralph.mcp.explore.structure.StructureExtraction(path, content_hash, spans, symbols, edges)[source]¶
Bases:
objectThe result of extracting structure from a single file.
The reindex pipeline persists the spans/symbols/edges via
ExploreStore.replace_structure_rows()so the values here always travel together with a singlecontent_hash.
- ralph.mcp.explore.structure.derive_edge_id(*, source_id, target_id, relation, path, span_id)[source]¶
Return the deterministic edge id for a (source, target, relation) tuple.
- Parameters:
source_id (str)
target_id (str)
relation (str)
path (str)
span_id (str | None)
- Return type:
str
- ralph.mcp.explore.structure.derive_span_id(*, path, start_line, start_col, end_line, end_col, kind, content_hash)[source]¶
Return the deterministic span id for the given coordinates.
- Parameters:
path (str)
start_line (int)
start_col (int)
end_line (int)
end_col (int)
kind (str)
content_hash (str)
- Return type:
str
- ralph.mcp.explore.structure.derive_symbol_id(*, path, qualified_name, kind, span_id)[source]¶
Return the deterministic symbol id for a definition.
- Parameters:
path (str)
qualified_name (str)
kind (str)
span_id (str)
- Return type:
str
- ralph.mcp.explore.structure.detect_language(path)[source]¶
Return
"python"/"markdown"/Nonebased on extension.- Parameters:
path (str)
- Return type:
str | None
- ralph.mcp.explore.structure.extract_markdown(*, path, content, content_hash, generation)[source]¶
Extract heading spans + heading-as-anchor edges from a Markdown file.
Captures ATX (
# Title) and Setext (Title\n====) headings. The head container span uses the entire file range so other spans nest undercontains.- Parameters:
path (str)
content (str)
content_hash (str)
generation (int)
- Return type:
- ralph.mcp.explore.structure.extract_python(*, path, content, content_hash, generation)[source]¶
Extract spans/symbols/edges from a Python source
content.Raises
PythonExtractionErrorwhen the source fails to parse. Per the prompt’s PA-001 invariant, the reindex pipeline catches the typed exception in its preflight so lexical/structure rows for the path remain queryable. Direct callers ofextract_python(and tests) can decide their own recovery contract.Edge relation coverage (mechanically evidenced):
contains— file/module/class contains child span.defines— emitted only for parser-recognized definitions.imports— emitted only fromast.Import/ast.ImportFrom.calls_syntax— emitted only when syntax contains a call expression with a recordable callee token. Does NOT claim semantic dispatch.references_text— emitted from exact token / text matches after identifier normalization. Lower confidence than parser edges; provenance isinferred.inherits_syntax— emitted only from Python class bases.tests— emitted from deterministic test-naming conventions.mentions— emitted only from comments with exact matched spans. Must not imply code dependency.
- Parameters:
path (str)
content (str)
content_hash (str)
generation (int)
- Return type:
ralph.mcp.explore.handlers¶
MCP handlers for the explore index: ralph_index_status and ralph_reindex.
Both handlers operate on an optional, lazily-initialized
ExploreIndex handle attached to the session. When the index
is disabled or missing, both handlers return a structured “disabled”
response rather than raising — this keeps the live tool surface
unchanged for existing callers.
The handlers own the ExploreIndex handle and the path-resolution
logic so they are testable in isolation. Tests construct a
_SqliteBackedExploreIndex directly over a tmp_path workspace
and pass it as session.explore_index.
- class ralph.mcp.explore.handlers.ExploreIndex(workspace_root, index_root, store, last_refresh_kind='none', cold_index_required=False, generation=0, last_job_status=None, reindex_in_progress=False)[source]¶
Bases:
objectOptional, lazily-initialized handle on the session/workspace.
Stores the index directory + a live
ExploreStore. The handle is created by the MCP server bootstrap (or by tests) and injected assession.explore_index. WhenNone, handlers behave exactly as today — no metadata added, no dirty marking.The handle exposes a single public surface to MCP handlers:
mark_dirty(paths, source_tool, reason)so the dirty-path tracking stays narrow and testable.- Parameters:
workspace_root (Path)
index_root (Path)
store (ExploreStore)
last_refresh_kind (str)
cold_index_required (bool)
generation (int)
last_job_status (str | None)
reindex_in_progress (bool)
- reindex_in_progress: bool = False¶
Set by a reindex writer while it owns the work; the mutation freshness block reports it. Production writers flip this to
Truefor the duration of the job and back toFalseon success/failure. The defaultFalsekeeps the attribute optional for test doubles that subclass or duck-type the handle.
- ralph.mcp.explore.handlers.build_explore_index(workspace_root)[source]¶
Construct a fresh ExploreIndex for
workspace_root.Tests call this directly. Production callers should defer construction to the MCP server bootstrap so the index is created lazily and only when first queried.
AC-05/AC-06: when the persisted
schema_versionorextractor_versionsettings disagree with the runtime constants, the on-disk index is wiped and a safe cold rebuild is triggered on the next call. The handle is still returned so the caller can observe the wiped state viaindex_exists=Trueandcold_index_required=True.- Parameters:
workspace_root (Path)
- Return type:
ralph.mcp.explore.lifecycle¶
Before/after dev-fix session lifecycle refresh hooks.
These hooks are invoked by the pipeline runner around agent invocations for execution-role phases whose session drain is development or fix. They run a bounded changed-file refresh:
BEFORE the agent runs, so the agent starts with a fresh-enough index.
AFTER the agent runs (success or exit), so downstream review / analysis / commit prompt materialization consumes indexed context.
The hooks must:
Skip cleanly (no-op) when the explore index is missing or disabled.
Enforce a strict timeout that is fail-closed for the job (
status='timed_out') and fail-open for the agent (the agent is never blocked indefinitely).Coalesce with any in-flight reindex.
Phase 1 keeps the contract narrow: the hooks touch only reindex
and the existing pipeline_deps.explore_index seam. Phase 2 (Phase
3 in the plan) will add richer hooks for impact-aware editing.
- class ralph.mcp.explore.lifecycle.LifecycleHookResult(invoked, timed_out, reindex_result=None, skipped_reason=None)[source]¶
Bases:
objectResult of a single lifecycle refresh call.
- Parameters:
invoked (bool)
timed_out (bool)
reindex_result (ReindexResult | None)
skipped_reason (str | None)
- ralph.mcp.explore.lifecycle.after_agent_refresh(*, workspace_root, explore_index, timeout_ms=2000, reindex_runner=None)[source]¶
Run a bounded changed-file refresh after an agent invocation.
- Parameters:
workspace_root (Path)
explore_index (object | None)
timeout_ms (int)
reindex_runner (ReindexRunner | None)
- Return type:
- ralph.mcp.explore.lifecycle.before_agent_refresh(*, workspace_root, explore_index, timeout_ms=2000, reindex_runner=None)[source]¶
Run a bounded changed-file refresh before an agent invocation.
Skips cleanly when
explore_indexisNoneor when the underlying store is missing. The hook never blocks the agent indefinitely; a timeout recordstimed_out=Trueand returns a stale-but-usable result.- Parameters:
workspace_root (Path)
explore_index (object | None)
timeout_ms (int)
reindex_runner (ReindexRunner | None)
- Return type:
- ralph.mcp.explore.lifecycle.claim_reindex(store, workspace_root, *, options)[source]¶
Default reindex runner for the lifecycle hooks.
Routes through
ReindexWriter.claim()so concurrent MCPralph_reindexcalls and lifecycle hooks coalesce into a single writer per workspace. Exposed at module scope so the coalescing integration test can drive it on its own thread.- Parameters:
store (ExploreStore)
workspace_root (Path)
options (ReindexOptions)
- Return type:
- ralph.mcp.explore.lifecycle.is_execution_phase_for_refresh(*, phase_role, phase_drain=None)[source]¶
Return True when the phase triggers an indexed refresh.
AC-04: the lifecycle refresh must only run for development / fix sessions, never for planning, review, commit, or analysis. The
rolecheck alone is too permissive: the planning block inralph/policy/defaults/pipeline.tomlis mapped torole = "execution"while its drain isplanning, and allowing that drain to trigger a refresh would index the workspace for the planning agent — an unrelated and uncosted cost. Thephase_drainis therefore the authoritative gate; thephase_roleargument is preserved for backward compatibility (and as a defensive cross-check) but the drain must also be inREFRESHABLE_PHASE_DRAINS.- Parameters:
phase_role (str | None)
phase_drain (str | None)
- Return type:
bool
ralph.mcp.explore.pipeline¶
Idempotent, work-proportional reindex pipeline.
The shipped pipeline indexes lexical chunks plus deterministic Python and Markdown structure, symbols, and provenance-labelled graph edges. Optional Phase 5 adapters remain deferred; the core extraction path has no LLM, vector, or third-party parser dependency.
The pipeline follows the Idempotence And Efficiency Contract from the architecture finding:
Build/compare deterministic manifest sorted by normalized path.
Reuse unchanged file records/chunks/edges by
(path, content_hash).Re-extract only changed/new files (warm small-edit).
Delete stale non-evidence rows for a changed path BEFORE inserting replacement rows; delete FTS rows by
chunk_idfirst.Write bounded
evidence_tombstonesBEFORE deleting stale evidence.Mark missing paths deleted/stale.
Commit a new generation atomically (short transactions).
Cap job history (latest 100 / 14 days) and tombstones (latest 10k / 30 days).
Warm no-op refresh checks the manifest and does NO parsing and NO FTS/edge rewrites. Warm small-edit reparses only changed-file bytes plus bounded local edge cleanup.
mode='full' rebuilds into a temp generation and atomically swaps
metadata only after success.
A single reindex writer per workspace; concurrent requests coalesce
dirty paths rather than starting a second writer. Timeout is
fail-closed for the job (status='timed_out') and fail-open for
agent reads (tools return stale metadata instead of hanging).
- class ralph.mcp.explore.pipeline.ReindexOptions(mode='changed', timeout_ms=5000, path_scope=(), clock=None)[source]¶
Bases:
objectOptions for a reindex call.
- Parameters:
mode (str)
timeout_ms (int)
path_scope (tuple[str, ...])
clock (Clock | None)
- class ralph.mcp.explore.pipeline.ReindexResult(job_id, generation, status, changed_files=(), failed_files=(), parse_count=0, dirty_paths_count=0, elapsed_seconds=0.0, error_summary=None)[source]¶
Bases:
objectResult of a reindex invocation.
- Parameters:
job_id (str)
generation (int)
status (str)
changed_files (tuple[str, ...])
failed_files (tuple[str, ...])
parse_count (int)
dirty_paths_count (int)
elapsed_seconds (float)
error_summary (str | None)
- class ralph.mcp.explore.pipeline.ReindexWriter(store)[source]¶
Bases:
objectSingle reindex writer per workspace.
Concurrent calls coalesce dirty paths rather than starting a second writer (the architecture finding’s “Concurrency and lifecycle contract”).
Ponytail: the lock is a module-level dict keyed by store path so tests do not see thread-shared state leaking between workspaces. Tests inject a custom
lock_factoryto avoid contention.- Parameters:
store (ExploreStore)
- classmethod claim(store, *, workspace_root, options=None, cancel=None)[source]¶
Run reindex, coalescing with any active writer for the same store.
cancelis forwarded to the underlyingreindexso callers can preserve their per-request cancel semantics while still going through the single-writer/coalescing seam. A second concurrent call against the same store short-circuits with a syntheticskipped_no_changesresult so the prior committed generation is preserved.- Parameters:
store (ExploreStore)
workspace_root (Path)
options (ReindexOptions | None)
cancel (Callable[[], bool] | None)
- Return type:
- ralph.mcp.explore.pipeline.hash_workspace_file(workspace_root, relative_path)[source]¶
Return
(content_hash, size_bytes, mtime_ns)for a single workspace file.Reads via the same workspace boundary the MCP tools enforce. The file’s content hash is authoritative; mtime/size are a cheap prefilter only.
Uses
Path.is_relative_to(Python 3.9+) so the boundary check cannot be fooled by sibling-prefix escapes (/tmp/wsvs/tmp/ws_evil) or symlink targets outsideworkspace_root.AC-05: streams the file in bounded chunks so a single large file cannot blow the byte budget or block past a cancellation deadline. The chunk size is the same as the SQLite page size so a workspace file larger than memory can still be hashed.
- Parameters:
workspace_root (Path)
relative_path (str)
- Return type:
tuple[str, int, int]
- ralph.mcp.explore.pipeline.reindex(store, workspace_root, *, options=None, cancel=None)[source]¶
Run a reindex job over
workspace_root.The store is required to already exist on disk. The caller is responsible for initializing
ExploreStore; this keeps the pipeline free of any I/O-oracle side effects at import time.AC-05: a
cancelcallable may be supplied. When the callable returnsTruethe writer preserves the prior committed generation (no partial mutable state is exposed) and the reindex returns astatus='cancelled'result. The callable is polled at phase boundaries (file iteration, FTS commit, and row insert) so cancellation is bounded by the duration of one phase.- Parameters:
store (ExploreStore)
workspace_root (Path)
options (ReindexOptions | None)
cancel (Callable[[], bool] | None)
- Return type:
ralph.mcp.explore.ranking¶
Deterministic ranking for indexed search_files and grep_files.
The ranking pipeline combines path/FTS/role/git-changed signals with
the indexed symbol and graph evidence. Score components are computed
from live indexed rows; when the index lacks the relevant data the
scorer surfaces explicit zero components with a precise
component:no_indexed_data reason so callers can audit the
missing context.
Every ranked response returns a score_reasons list so tests can
assert WHY a path outranks another. Ties sort by
(path, line, evidence_id) for stable output.
- class ralph.mcp.explore.ranking.RankedItem(key, score=0, reasons=<factory>, line=None, evidence_id=None, path='')[source]¶
Bases:
objectA single ranked item with explicit score reasons.
- Parameters:
key (str)
score (int)
reasons (tuple[str, ...])
line (int | None)
evidence_id (str | None)
path (str)
- ralph.mcp.explore.ranking.fts_query_for(pattern, *, whole_word)[source]¶
Convert a literal pattern into an FTS5 MATCH query.
Ponytail: minimal FTS5 escape; only the basic characters that matter for whole-word vs substring matching. The FTS5
unicode61tokenizer used here splits on whitespace and punctuation; escaping with"..."produces a phrase query.- Parameters:
pattern (str)
whole_word (bool)
- Return type:
str
- ralph.mcp.explore.ranking.is_config_role(path)[source]¶
True for build/config/toolchain files (heuristic).
Matches canonical configuration extensions and a small set of well-known build / CI filenames. List-extension files (
requirements.txt,pyproject.toml…) and dotfile configurations (.gitignore,.ruff.toml…) both count so a caller asking forrole=configactually narrows the result instead of returning the full glob set.- Parameters:
path (str)
- Return type:
bool
- ralph.mcp.explore.ranking.is_docs_role(path)[source]¶
True for documentation-only files (heuristic).
Matches Markdown / RST / text files that live under the canonical docs tree or carry an obvious documentation name.
- Parameters:
path (str)
- Return type:
bool
- ralph.mcp.explore.ranking.is_fts_eligible(pattern, *, is_regex, whole_word, case_sensitive=False)[source]¶
Return True when the pattern is safe to run through FTS5.
Index-eligible queries are:
literal search,
whole-word literal search,
simple case-sensitive/case-insensitive token search,
phrase search expressible in FTS5 without changing match semantics.
Non-eligible queries include arbitrary regex constructs, multiline patterns, lookaround, capture-group semantics, backreferences, byte-oriented searches, and substring patterns that FTS tokenization cannot represent exactly.
Case-sensitive search is eligible: the handler narrows candidates via FTS (case-INsensitive) and re-applies a case-sensitive regex post-filter so the result set equals the live grep path. The helper reports the pattern as eligible in both directions and the case-sensitive flag is communicated through the response metadata so callers can audit which branch ran.
case_sensitivedefaults toFalsefor backward compatibility with the legacy call site that omitted the argument entirely.- Parameters:
pattern (str)
is_regex (bool)
whole_word (bool)
case_sensitive (bool)
- Return type:
bool
- ralph.mcp.explore.ranking.is_generated_path(path)[source]¶
Heuristic for the -50 generated/vendor penalty.
- Parameters:
path (str)
- Return type:
bool
- ralph.mcp.explore.ranking.is_generated_role(path)[source]¶
True for generated / vendor / build-output files (heuristic).
Ponytail: kept narrow on purpose so a
role=generatedfilter does not silently nuke results an agent expected to be editable source. Vendor trees (vendor/,node_modules/) and obvious build outputs (dist/,build/…) count, but a regular test fixture is NOT generated just because the word “generated” appears in a comment.- Parameters:
path (str)
- Return type:
bool
- ralph.mcp.explore.ranking.is_source_role(path)[source]¶
True for source files (heuristic).
- Parameters:
path (str)
- Return type:
bool
- ralph.mcp.explore.ranking.is_test_role(path)[source]¶
True for test files (heuristic).
- Parameters:
path (str)
- Return type:
bool
- ralph.mcp.explore.ranking.matches_role(path, role)[source]¶
Return True when
pathmatches the requestedrolepredicate.The set of roles is the canonical
source/test/docs/config/generated/anytaxonomy thatsearch_filesadvertises. Unrecognized role names return False instead of falling back to a free glob, so the handler can surface the typo to the caller rather than silently returning the full glob set.- Parameters:
path (str)
role (str)
- Return type:
bool
- ralph.mcp.explore.ranking.score_grep_match(*, path, line, evidence_id, is_git_changed=False, store=None, chunk_id=None, graph_target=None)[source]¶
Compute a grep_files rank_by score for one match.
The scoring contract:
Baseline: every match carries +1.
is_git_changedaddsGREP_GIT_CHANGEDwhen the file is marked dirty or the livegit statusreports it.Symbol components: when
storeandchunk_idare provided and the chunk is associated with a definition or symbol body, the appropriate score bonus is added. Absent index context surfaces a+0 component:no_indexed_datareason.Graph components: when
graph_targetis provided and the match is in a caller/importer/test neighbor, theGREP_GRAPH_NEIGHBORbonus is added.GREP_GRAPH_COMPONENTis added when the match file is in the same graph component as the target.
- Parameters:
path (str)
line (int)
evidence_id (str)
is_git_changed (bool)
store (ExploreStore | None)
chunk_id (str | None)
graph_target (str | None)
- Return type:
- ralph.mcp.explore.ranking.score_search_file(*, candidate_path, basename, role_requested, is_git_changed, contains_symbol=None)[source]¶
Compute a search_files score for one candidate path.
The scoring contract:
SEARCH_EXACT_BASENAME(+100) for an exact basename match.SEARCH_GIT_CHANGED(+40) for paths the lifecycle hook marked dirty or the livegit statusreported.SEARCH_ROLE_REQUESTED(+30) for role-matched paths when the caller asked forsource/test.SEARCH_GENERATED_PENALTY(-50) for vendor/generated paths.SEARCH_SYMBOL_DEFINITION(+80) andSEARCH_SYMBOL_MENTION(+60) are only emitted when the caller passedcontains_symbolAND the candidate already survived the symbol filter (i.e. the index recognized the symbol as defined in or referenced from the path). Otherwise the components stay at 0 with a precisecomponent:no_indexed_datanote so callers can audit missing index context.
- Parameters:
candidate_path (str)
basename (str)
role_requested (str | None)
is_git_changed (bool)
contains_symbol (str | None)
- Return type:
- ralph.mcp.explore.ranking.sort_ranked(items)[source]¶
Stable sort: score DESC, then path ASC, then line ASC, then evidence_id ASC.
- Parameters:
items (list[RankedItem])
- Return type:
list[RankedItem]
ralph.mcp.explore.store¶
Indexed exploration substrate store hub.
Re-exports ExploreStore and the surrounding store
helpers from their per-concern sub-modules so the hub stays under
the per-file line ceiling. Each sub-module owns a focused slice of
the SQLite + workspace contract:
ralph.mcp.explore._store_types– row dataclasses, Clock + SystemClock, row-converter helpers, chunk/evidence-id helpers, workspace hashing utilities,normalize_index_path.ralph.mcp.explore._store_class– theExploreStoreclass implementation.
The public surface (ExploreStore, DEFAULT_INDEX_ROOT,
DEFAULT_INDEX_DB, dataclasses, Clock/SystemClock,
row_str, hashing helpers, chunk_text, derive_*,
chunk_id, workspace helpers, sha256_text/sha256_bytes)
continues to be importable as from ralph.mcp.explore.store
import X for backward compatibility.
- class ralph.mcp.explore.store.ChunkRow(chunk_id, path, start_line, end_line, text_hash, role, generation)[source]¶
Bases:
objectA row from the
chunkstable.- Parameters:
chunk_id (str)
path (str)
start_line (int)
end_line (int)
text_hash (str)
role (str)
generation (int)
- class ralph.mcp.explore.store.Clock(*args, **kwargs)[source]¶
Bases:
ProtocolProtocol for wall-clock injection. Tests inject a FakeClock.
- class ralph.mcp.explore.store.ContentCacheChunk(start_line, end_line, text_hash, text, role='body')[source]¶
Bases:
objectSingle chunk row stored inside a content-cache payload.
- Parameters:
start_line (int)
end_line (int)
text_hash (str)
text (str)
role (str)
- class ralph.mcp.explore.store.ContentCachePayload(content_hash, extractor_version, chunks)[source]¶
Bases:
objectPath-independent extraction payload keyed by content hash.
Multiple workspace files may share the same payload when they are exact-content copies or moves; the store deduplicates by
content_hashso the disk footprint stays bounded.- Parameters:
content_hash (str)
extractor_version (str)
chunks (tuple[ContentCacheChunk, ...])
- class ralph.mcp.explore.store.ContentCacheRow(content_hash, language, extractor_version, extracted_at, extraction_status, error_summary)[source]¶
Bases:
objectA row from the
content_cachemetadata table.The cache deduplicates extraction payloads by
content_hashso a moved or copied file can reuse an already-extracted record. Path-specific rows (chunks/FTS/evidence/spans/symbols/edges) are rebuilt by the reindex pipeline because their identity depends on the normalized path. The payload itself lives in thecontent_cache_payloadBLOB table; this row carries only the bookkeeping so metadata reads stay cheap.- Parameters:
content_hash (str)
language (str | None)
extractor_version (str)
extracted_at (float)
extraction_status (str)
error_summary (str | None)
- class ralph.mcp.explore.store.EdgeRow(edge_id, source_id, target_id, relation, path, span_id, provenance, confidence, reason, generation)[source]¶
Bases:
objectA row from the
edgestable (AC-06 Phase 2 structure).- Parameters:
edge_id (str)
source_id (str)
target_id (str)
relation (str)
path (str)
span_id (str | None)
provenance (str)
confidence (float)
reason (str | None)
generation (int)
- class ralph.mcp.explore.store.EvidenceRow(evidence_id, path, start_line, end_line, content_hash, generation, source_tool, evidence_kind, created_at, is_stale, chunk_id=None, span_id=None)[source]¶
Bases:
objectA row from the
evidencetable.AC-01:
chunk_idandspan_idare durable links to the persistedchunksandspansrows so the evidence row resolves to the exact indexed identity (line and column bounds, text hash, chunk role, structured symbol) rather than to raw coordinates that can drift between reindex passes. Both fields may be None when the evidence predates the linked extraction.- Parameters:
evidence_id (str)
path (str)
start_line (int)
end_line (int)
content_hash (str)
generation (int)
source_tool (str)
evidence_kind (str)
created_at (float)
is_stale (bool)
chunk_id (str | None)
span_id (str | None)
- class ralph.mcp.explore.store.ExploreStore(index_dir, *, busy_timeout_ms=5000)[source]¶
Bases:
_ContentCacheMethodsOwns the SQLite connection and DDL for the index.
Construct with an explicit index directory. WAL mode + busy timeout are configured at construction time so every subsequent call inherits the bounded-subprocess contract.
- Parameters:
index_dir (Path)
busy_timeout_ms (int)
- consume_dirty_paths()[source]¶
Atomically return + clear all currently dirty paths.
- Return type:
list[str]
- count_deleted_files()[source]¶
Return the count of file rows marked
is_deleted=1.Bounded: a single
COUNT(*)aggregate.iter_filesfilters out deleted rows, so this method is the only bounded way for callers to observe the deleted-row stale signal without materializing the entire table.- Return type:
int
- count_files()[source]¶
Return the live file row count.
Bounded: a single
COUNT(*)aggregate, no row materialization. Callers that need per-row data useiter_files(). The method exists so the index status and git_status compact paths can compute freshness with O(1) work instead of pulling the entirefilestable into memory.- Return type:
int
- delete_chunks_for_path(path)[source]¶
Delete all chunks and FTS rows for
path.- Parameters:
path (str)
- Return type:
None
- delete_file_rows(path)[source]¶
Remove file/chunk/evidence rows for
pathin current generation.- Parameters:
path (str)
- Return type:
None
- find_symbols(*, name=None, qualified_name=None, path=None)[source]¶
Return symbols filtered by
name/qualified_name/path.The query is conjunctive (every filter must match). Empty filters are ignored so callers can pass just one selector without supplying the others. Multiple symbols may match the same
qualified_name(e.g. nested functions in different scopes), so this returns a list rather than a single row; callers must disambiguate by path or generation when needed.- Parameters:
name (str | None)
qualified_name (str | None)
path (str | None)
- Return type:
list[SymbolRow]
- fts_search(query, *, limit=100, path_prefix=None, include_globs=None, exclude_globs=None)[source]¶
Run an FTS5 MATCH query and return rows.
Returns
chunks_ftsrows (path, chunk_id, text). Callers are expected to translate chunk_id to evidence handles.AC-02 indexed-grep filter parity:
path_prefixrestricts matches to paths that equal the prefix or start withprefix + '/'.include_globs/exclude_globsapply the same glob semantics as the legacy live grep so the indexed branch cannot leak out-of-scope matches.- Parameters:
query (str)
limit (int)
path_prefix (str | None)
include_globs (Sequence[str] | None)
exclude_globs (Sequence[str] | None)
- Return type:
list[Row]
- get_span(span_id)[source]¶
Return the unique
SpanRowforspan_idorNone.- Parameters:
span_id (str)
- Return type:
SpanRow | None
- has_deleted_files()[source]¶
Bounded existence check for any deleted file row.
Equivalent to
count_deleted_files() > 0but usesEXISTSso SQLite short-circuits on the first match. Callers that only need a boolean freshness signal (e.g., compactgit_status) should prefer this method over a count query.- Return type:
bool
- mark_dirty(path, *, reason, source_tool, now=None)[source]¶
Persist
pathin the dirty queue. Idempotent.- Parameters:
path (str)
reason (str)
source_tool (str)
now (float | None)
- Return type:
None
- reopen()[source]¶
Close the live connection and reopen it against the same file.
Used by the staged
mode='full'reindex after the staging database is swapped in at the file level: the connection must be re-opened to observe the new file content. Pragmas are reapplied because the prior connection is gone; the DDL is left to_initializeand is idempotent (the staging file already contains the DDL).- Return type:
None
- replace_structure_rows(*, path, spans, symbols, edges)[source]¶
Atomically replace the structure rows for
path.Deletes any prior spans/symbols/edges that point at this path’s previous generation, then inserts the new ones. Used by the reindex pipeline after extraction; AC-06 requires that changed-file reindex leaves no stale graph rows behind.
- class ralph.mcp.explore.store.FileRow(path, content_hash, size_bytes, mtime_ns, language, indexed_generation, indexed_at, is_deleted)[source]¶
Bases:
objectA row from the
filestable.- Parameters:
path (str)
content_hash (str)
size_bytes (int)
mtime_ns (int)
language (str | None)
indexed_generation (int)
indexed_at (float)
is_deleted (bool)
- class ralph.mcp.explore.store.SpanRow(span_id, path, start_line, start_col, end_line, end_col, kind, symbol_id, content_hash, generation)[source]¶
Bases:
objectA row from the
spanstable (AC-06 Phase 2 structure).- Parameters:
span_id (str)
path (str)
start_line (int)
start_col (int)
end_line (int)
end_col (int)
kind (str)
symbol_id (str | None)
content_hash (str)
generation (int)
- class ralph.mcp.explore.store.SymbolRow(symbol_id, name, qualified_name, kind, path, span_id, language, extracted_from, confidence, generation)[source]¶
Bases:
objectA row from the
symbolstable (AC-06 Phase 2 structure).- Parameters:
symbol_id (str)
name (str)
qualified_name (str)
kind (str)
path (str)
span_id (str)
language (str | None)
extracted_from (str)
confidence (float)
generation (int)
- class ralph.mcp.explore.store.SystemClock[source]¶
Bases:
objectDefault wall-clock implementation backed by
time.monotonic.
- ralph.mcp.explore.store.assert_within_workspace(workspace_root, full_path)[source]¶
Raise
ValueErrorwhenfull_pathis outsideworkspace_root.Resolves symlinks and uses
Path.is_relative_toto defend against sibling-prefix and symlink-escape attacks on the indexed exploration store. Used by the reindex pipeline and the workspace file handlers to share one boundary check.- Parameters:
workspace_root (Path)
full_path (Path)
- Return type:
None
- ralph.mcp.explore.store.chunk_text(text, *, lines_per_chunk=50)[source]¶
Split
textinto(start_line, end_line, chunk_text)chunks.Lines are 1-based and inclusive. The trailing chunk may be smaller than
lines_per_chunk. Empty input returns an empty list so callers can iterate without a guard.- Parameters:
text (str)
lines_per_chunk (int)
- Return type:
list[tuple[int, int, str]]
- ralph.mcp.explore.store.collect_workspace_files(workspace_root)[source]¶
Return
[(relative_path, size_bytes, mtime_ns), ...]for indexable files.- Parameters:
workspace_root (Path)
- Return type:
list[tuple[str, int, int]]
- ralph.mcp.explore.store.derive_chunk_id(*, path, start_line, end_line, text_hash, extractor_version)[source]¶
Return a stable chunk id derived from the deterministic inputs.
- Parameters:
path (str)
start_line (int)
end_line (int)
text_hash (str)
extractor_version (str)
- Return type:
str
- ralph.mcp.explore.store.derive_evidence_id(*, path, content_hash, start_line, end_line, kind, extractor_version)[source]¶
Return a stable evidence id derived from the deterministic inputs.
- Parameters:
path (str)
content_hash (str)
start_line (int)
end_line (int)
kind (str)
extractor_version (str)
- Return type:
str
- ralph.mcp.explore.store.deserialize_content_cache_payload(blob)[source]¶
Decode
blobback into aContentCachePayload.- Parameters:
blob (bytes) – bytes previously produced by
serialize_content_cache_payload().- Returns:
The decoded payload.
- Raises:
ValueError – when the blob is malformed or carries an unknown
format_version. Callers must fail closed so an unknown schema cannot accidentally corrupt the live index.- Return type:
- ralph.mcp.explore.store.hash_workspace_file(workspace_root, relative_path)[source]¶
Return
(content_hash, size_bytes, mtime_ns)for a single workspace file.Reads via the same workspace boundary the MCP tools enforce. The file’s content hash is authoritative; mtime/size are a cheap prefilter only.
Uses
Path.is_relative_to(Python 3.9+) so the boundary check cannot be fooled by sibling-prefix escapes (/tmp/wsvs/tmp/ws_evil) or symlink targets outsideworkspace_root.AC-05: streams the file in bounded chunks so a single large file cannot blow the byte budget or block past a cancellation deadline. The chunk size is the same as the SQLite page size so a workspace file larger than memory can still be hashed.
- Parameters:
workspace_root (Path)
relative_path (str)
- Return type:
tuple[str, int, int]
- ralph.mcp.explore.store.iter_indexable_files(workspace_root)[source]¶
Yield indexable file paths under
workspace_root.Skips common VCS / cache / build directories. Always returns paths relative to
workspace_rootso the caller can normalize them withnormalize_index_path().- Parameters:
workspace_root (Path)
- Return type:
Iterator[Path]
- ralph.mcp.explore.store.normalize_index_path(path)[source]¶
Normalize a workspace-relative POSIX path for index storage.
Rejects absolute paths and any normalized path that still contains a
..segment afterPurePosixPathnormalization. The rejection happens before the path is ever persisted, so the index cannot accept a workspace-boundary escape.Raises
ValueErrorfor absolute or parent-escape inputs.- Parameters:
path (str)
- Return type:
str
- ralph.mcp.explore.store.row_str(row, key)¶
Read a string column from
rowwith a precise return type.- Parameters:
row (Row)
key (str)
- Return type:
str
- ralph.mcp.explore.store.serialize_content_cache_payload(payload)[source]¶
Encode
payloadinto a deterministic JSON BLOB.- Parameters:
payload (ContentCachePayload) – path-independent extraction payload built by the reindex pipeline.
- Returns:
UTF-8 encoded JSON bytes. The encoder sets
separators=(",", ":")andsort_keys=Trueso a stable round-trip is reproducible on any platform.- Raises:
TypeError – when
payloadcontains a non-JSON-serializable value.- Return type:
bytes
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:
objectState carrier for one agent-phase git-protection sequence.
GitHelpersis the lightweight bundlestart_agent_phase()andend_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 whichgitbinary is the genuine system one) without recomputing it on every call.- real_git¶
Path to the genuine
gitexecutable on the host. Set by callers when the Ralph wrapper redirectsgitto a sandbox binary;Nonewhen the host has no wrapper in front ofgit. 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/ralphdirectory that holds the agent-phase marker, HEAD OID snapshot, and the Ralph-managed hooks directory. Set bystart_agent_phase()and read byend_agent_phase()during teardown.Nonebeforestart_agent_phase()populates it.- Type:
pathlib.Path | None
- wrapper_repo_root¶
Path to the repository root the protections were enabled for.
Nonebeforestart_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:
Construct (or reuse) a
GitHelpers. Optional — the phase helpers create one for you.Call
start_agent_phase(repo_root, helpers)(); it setswrapper_repo_rootandwrapper_dirand writes the marker / HEAD OID / hooks-path snapshot.Run the agent phase (commit/push attempts are blocked by the marker file the hook scripts check).
Call
end_agent_phase(repo_root, helpers)(); it restorescore.hooksPath, deletes the marker / snapshot / track files, and leaveswrapper_dirpopulated 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 | Nonebecause none of them carry meaning outside of a pairedstart_agent_phase/end_agent_phasecycle.
- exception ralph.git.GitOperationError(operation, message)[source]¶
Bases:
ExceptionRaised 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:
objectResult 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
HEADagainst the OIDstart_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.Pathor a string; the value is resolved against the GitPythonRepoconstructor.- Returns:
Truewhen a stored snapshot exists and the currentHEADOID differs from it (unauthorized commit detected);Falsewhen no snapshot exists, the snapshot is empty, the currentHEADcannot be read, orHEADstill matches the snapshot.- Return type:
bool
- Side effects:
Closes the GitPython
Repoit 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
gitinvocation fails for a reason other than a missingHEAD(detached/unborn HEAD is reported asFalse, 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 previouscore.hooksPathvalue, deletes the Ralph-managed marker / HEAD-OID / track files under<repo>/.git/ralph/, and closes the GitPythonRepoopened for the duration of the call. After this function returns, the repository is in the same git state it was in before the matchingstart_agent_phase()call.- Parameters:
repo_root (Path | str) – Path to the repository root whose protections should be rolled back. Must match the
repo_rootpassed to the matchingstart_agent_phase()call so teardown targets the same repository. Accepts apathlib.Pathor a string.helpers (GitHelpers | None) – Optional
GitHelperscarrier populated by the matchingstart_agent_phase()call. WhenNone, a fresh carrier is constructed andwrapper_repo_rootis set toPath(repo_root)so teardown can locate the.git/ralphdirectory written during setup.
- Returns:
None. The function mutates the repository’s local
core.hooksPathand deletes the marker / snapshot files written during setup.- Return type:
None
- Side effects:
Restores the previous
core.hooksPathfrom 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
Repoit opened for the duration of the call.
- Raises:
git.exc.GitCommandError – If any underlying
gitinvocation fails (filesystem permission, missing snapshot file, or a held lock). Each subprocess is bounded byGIT_SUBPROCESS_TIMEOUT_SECONDSso 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 whetherHEADadvanced 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.
- Raises:
GitOperationError – If HEAD cannot be read.
- Return type:
str
The wrapping is deliberate and matches
find_repo_root()andmerge_base()in this module. Raw GitPython exceptions used to escape into the auto-integrate skip table, where they were recorded as an opaqueunexpected failurethat named neither the operation nor the repository.
- 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
committo 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/hooksdirectory and the.git/ralphmarker 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
Nonethe repository is discovered viaralph.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/hooksdirectory, 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/hooksand<repo>/.git/ralphif they do not already exist.Touches the
no_agent_commitandgit-wrapper-dir.txtmarker files under<repo>/.git/ralphso subsequent agent phases can detect Ralph-managed installations.Backs up any existing non-Ralph hook script as
<hook_name>.ralph.origbefore 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 markerHOOK_MARKERis embedded in each script soreinstall_hooks_if_tampered()can later detect drift.
- Trust Boundary:
The function executes only filesystem writes inside the target repository’s
.gittree; it does not invoke any external commands or read user-supplied data. Callers must ensurerepo_rootresolves 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/hooksdirectory and the.git/ralphmarker 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
Nonethe repository is discovered viaralph.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/hooksdirectory, 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/hooksand<repo>/.git/ralphif they do not already exist.Touches the
no_agent_commitandgit-wrapper-dir.txtmarker files under<repo>/.git/ralphso subsequent agent phases can detect Ralph-managed installations.Backs up any existing non-Ralph hook script as
<hook_name>.ralph.origbefore 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 markerHOOK_MARKERis embedded in each script soreinstall_hooks_if_tampered()can later detect drift.
- Trust Boundary:
The function executes only filesystem writes inside the target repository’s
.gittree; it does not invoke any external commands or read user-supplied data. Callers must ensurerepo_rootresolves 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.phaseis provided the process label becomesphase:<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:
- 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
HEADOID under<repo>/.git/ralph/, snapshots the previouscore.hooksPathvalue, and repointscore.hooksPathat 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 skipend_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.Pathor a string; the value is resolved against the GitPythonRepoconstructor.helpers (GitHelpers | None) – Optional pre-built
GitHelperscarrier. WhenNone, a fresh carrier is constructed and populated withwrapper_repo_rootandwrapper_dir. The same carrier (or one with identical population) must be passed to the matchingend_agent_phase()call so teardown targets the right repository.
- Returns:
None. The function mutates the repository’s
.git/ralphdirectory, the localcore.hooksPathconfig, and the suppliedGitHelperscarrier.- 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
Repoit opened for the duration of the call (callers must not reuse the original handle).
- Raises:
git.exc.GitCommandError – If any underlying
gitinvocation fails (filesystem permission, missing.git, or a held lock). Each subprocess is bounded byGIT_SUBPROCESS_TIMEOUT_SECONDSso 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 whetherHEADadvanced during a protected phase.
- ralph.git.uninstall_hooks(*, logger=None, repo_root=None)[source]¶
Remove Ralph-managed hooks from
repo_rootand restore backups.Walks
RALPH_HOOK_NAMESand, for any hook that still carries theHOOK_MARKERwritten byinstall_hooks_in_repo(), restores the original script from the<hook_name>.ralph.origbackup (when one exists) and removes the backup. When no backup exists the hook file is deleted outright, leaving the slot empty so subsequentpre-commit/pre-pushinvocations 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.Loggerinstance used for the “Uninstalled N Ralph hook(s)” / “No Ralph-managed hooks were found to uninstall” summary lines. Defaults to the process logger whenNone, 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
Nonethe repository is discovered viaralph.git.operations.find_repo_root()from the current working directory.
- Returns:
Truewhen at least one Ralph-managed hook was removed or restored;Falsewhen 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.origbackup, 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.infoline summarizing how many hooks were removed.
- Trust Boundary:
The function only writes inside the target repository’s
.gittree and never invokes external commands. Callers must ensurerepo_rootresolves 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/hooksdirectory and the.git/ralphmarker 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
Nonethe repository is discovered viaralph.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/hooksdirectory, 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/hooksand<repo>/.git/ralphif they do not already exist.Touches the
no_agent_commitandgit-wrapper-dir.txtmarker files under<repo>/.git/ralphso subsequent agent phases can detect Ralph-managed installations.Backs up any existing non-Ralph hook script as
<hook_name>.ralph.origbefore 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 markerHOOK_MARKERis embedded in each script soreinstall_hooks_if_tampered()can later detect drift.
- Trust Boundary:
The function executes only filesystem writes inside the target repository’s
.gittree; it does not invoke any external commands or read user-supplied data. Callers must ensurerepo_rootresolves 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/hooksdirectory and the.git/ralphmarker 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
Nonethe repository is discovered viaralph.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/hooksdirectory, 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/hooksand<repo>/.git/ralphif they do not already exist.Touches the
no_agent_commitandgit-wrapper-dir.txtmarker files under<repo>/.git/ralphso subsequent agent phases can detect Ralph-managed installations.Backs up any existing non-Ralph hook script as
<hook_name>.ralph.origbefore 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 markerHOOK_MARKERis embedded in each script soreinstall_hooks_if_tampered()can later detect drift.
- Trust Boundary:
The function executes only filesystem writes inside the target repository’s
.gittree; it does not invoke any external commands or read user-supplied data. Callers must ensurerepo_rootresolves 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_rootand restore backups.Walks
RALPH_HOOK_NAMESand, for any hook that still carries theHOOK_MARKERwritten byinstall_hooks_in_repo(), restores the original script from the<hook_name>.ralph.origbackup (when one exists) and removes the backup. When no backup exists the hook file is deleted outright, leaving the slot empty so subsequentpre-commit/pre-pushinvocations 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.Loggerinstance used for the “Uninstalled N Ralph hook(s)” / “No Ralph-managed hooks were found to uninstall” summary lines. Defaults to the process logger whenNone, 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
Nonethe repository is discovered viaralph.git.operations.find_repo_root()from the current working directory.
- Returns:
Truewhen at least one Ralph-managed hook was removed or restored;Falsewhen 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.origbackup, 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.infoline summarizing how many hooks were removed.
- Trust Boundary:
The function only writes inside the target repository’s
.gittree and never invokes external commands. Callers must ensurerepo_rootresolves 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:
ExceptionRaised 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.
- Raises:
GitOperationError – If HEAD cannot be read.
- Return type:
str
The wrapping is deliberate and matches
find_repo_root()andmerge_base()in this module. Raw GitPython exceptions used to escape into the auto-integrate skip table, where they were recorded as an opaqueunexpected failurethat named neither the operation nor the repository.
- 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_shais 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
committo 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 --porcelainin 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.merge¶
Non-interactive merge, fast-forward, and worktree primitives for Ralph.
These primitives back ralph.pipeline.auto_integrate. They wrap
ralph.git.subprocess_runner.run_git (which already enforces
non-interactive git env: GIT_TERMINAL_PROMPT=0, GIT_EDITOR=:",
GIT_PAGER=cat) so a missing credential or hung pager can never
block the integration step.
The module never calls git push or repo.remote().push; the
auto-integrate contract is local-only (AC-10).
Public functions (every primitive has a docstring):
branch_exists— cheapgit show-ref --verifycheck.branch_sha— observed HEAD SHA of a branch (the value used as the compare-and-swap<oldvalue>argument later).is_ancestor— wrapsgit merge-base --is-ancestor.merge_state— three-valued merge check: in progress / none / unknown (the git query itself failed). The third verdict is what keepsabort_mergefrom silently doing nothing on a repository it cannot read.merge_in_progress— boolean projection ofmerge_state.merge_target_into_current— runsgit merge --no-editagainst the current branch; aborts cleanly on conflict and reportsMergeResult(outcome='conflict', ...);keep_conflicts=Trueleaves a conflicted merge in progress for resolution.unmerged_paths— lists paths still carrying conflict markers.stage_paths—git add -- <paths>; Ralph stages a resolved conflict itself so resolution never depends on the resolving agent’s git access.paths_with_conflict_markers— textual scan proving a resolution is real;git addon a marker-bearing file silently clears its unmerged state, sounmerged_pathsalone is not proof.commit_merge_in_progress— deterministically commits a fully resolved in-progress merge (git commit --no-edit).abort_merge— guardedgit merge --abort; returns whether the abort actually ran and succeeded.reset_hard— guardedgit reset --hard; used by crash recovery to restore the feature branch to its pre-integration SHA.fast_forward_via_worktree—git -C <wt> merge --ff-only <feature_sha>.merge --ff-onlyis itself the atomic guard: it refuses and returns non-zero whenfeature_shais not a fast-forward of the worktree’s checked-out branch, so a concurrently-advanced target is never force-moved (AC-08).compare_and_swap_branch—git update-ref refs/heads/<target> <new> <old>. Git performs an atomic CAS: it updates the ref ONLY if it still equals<old>, returning rc==0; if the target moved concurrently the rc is non-zero and the ref is left untouched. This REPLACES the previous unconditionalgit branch -fand closes the AC-08 TOCTOU race.worktree_lookup— parsesgit worktree list --porcelainfor the worktree whose checked-out branch isrefs/heads/<branch>, reportingfound/not_checked_out/query_failedso a failed query is never mistaken for “nobody holds it”.worktree_for_branch— Optional-returning wrapper overworktree_lookupfor callers that need no such distinction.
- ralph.git.merge.MERGE_STATE_NONE = 'none'¶
merge_state()verdicts.MERGE_STATE_UNKNOWNis the fail-closed answer: git could not be asked, so “there is no merge to abort” must NOT be inferred.
- class ralph.git.merge.MergeResult(outcome)[source]¶
Bases:
objectOutcome of a merge attempt.
outcomeis one of'success'(merge completed cleanly; may include a merge commit when both sides diverged, or may be a fast-forward),'noop'(target already an ancestor of HEAD or working tree unchanged), or'conflict'(merge conflict; the in-progress merge has already been aborted so the working tree is clean).- Parameters:
outcome (str)
- ralph.git.merge.WORKTREE_FOUND = 'found'¶
worktree_lookup()verdicts.WORKTREE_QUERY_FAILEDis the fail-closed answer, kept distinct fromWORKTREE_NOT_CHECKED_OUTso a caller never treats “I could not look” as “nobody holds it”.
- ralph.git.merge.abort_merge(repo_root)[source]¶
Abort an in-progress merge; report whether the abort actually ran.
Returns True only when
git merge --abortwas invoked and exited zero. A repository with no merge in progress returns False without running anything — there was nothing to abort.The precheck is deliberately fail-closed: a
MERGE_STATE_UNKNOWNverdict still attempts the abort, because “git could not be asked” is not evidence that the working tree is clean. A stray operator merge is still protected, since the only state that skips the abort is the one where git positively reported noMERGE_HEAD.- Parameters:
repo_root (Path | str)
- Return type:
bool
- ralph.git.merge.branch_exists(repo_root, name)[source]¶
Return True when
refs/heads/<name>resolves inrepo_root.- Parameters:
repo_root (Path | str)
name (str)
- Return type:
bool
- ralph.git.merge.branch_sha(repo_root, name)[source]¶
Return the SHA of
refs/heads/<name>orNonewhen absent.The returned SHA is the value to pass as the
<oldvalue>argument tocompare_and_swap_branch()so a concurrent landing cannot be silently force-overwritten (AC-08).A failed
rev-parseis also reported asNonehere. Callers that must tell an absent branch from an unreadable one – the fast-forward path, where the difference decides whether to retry – useobserve_branch_sha()instead.- Parameters:
repo_root (Path | str)
name (str)
- Return type:
str | None
- ralph.git.merge.compare_and_swap_branch(repo_root, target, expected_old_sha, new_sha)[source]¶
Move
refs/heads/<target>fromexpected_old_shatonew_shaatomically.Uses
git update-refwith the<newvalue> <oldvalue>form so git performs an atomic compare-and-swap: it updates the ref ONLY if it still equalsexpected_old_sha, returning rc==0; if the target advanced concurrently the rc is non-zero and the ref is left untouched. This REPLACES the previous unconditionalgit branch -fand closes the AC-08 TOCTOU race.- Parameters:
repo_root (Path | str)
target (str)
expected_old_sha (str)
new_sha (str)
- Return type:
bool
- ralph.git.merge.conflict_stage_entries(repo_root, paths)[source]¶
Return unmerged index entries keyed by path and conflict stage.
The NUL-delimited
ls-files -uformat preserves paths with spaces, newlines, or unicode. A failed query deliberately degrades to an empty mapping so callers can use their existing resolver fallback.- Parameters:
repo_root (Path | str)
paths (Sequence[str])
- Return type:
dict[str, dict[int, tuple[str, str]]]
- ralph.git.merge.fast_forward_via_worktree(worktree_root, feature_sha)[source]¶
Fast-forward the branch checked out in
worktree_root.Returns True on success; False when
feature_shais NOT a fast-forward of the worktree’s current branch.git merge --ff-onlyis itself the atomic guard — non-zero return when the requested SHA is not a descendant of HEAD — so a target that advanced concurrently between observation and the call is never force-moved.- Parameters:
worktree_root (Path | str)
feature_sha (str)
- Return type:
bool
- ralph.git.merge.is_ancestor(repo_root, ancestor, descendant)[source]¶
Return True when
ancestoris reachable fromdescendant.Thin wrapper over
git merge-base --is-ancestor. Used to gate the fast-forward phase (AC-08 guard):is_ancestor(target, feature_sha)must be True before we attempt to move the target tofeature_sha; if False, the target has diverged and we must skip with a recorded reason.- Parameters:
repo_root (Path | str)
ancestor (str)
descendant (str)
- Return type:
bool
- ralph.git.merge.merge_in_progress(repo_root)[source]¶
Return True when a
MERGE_HEADexists in the git directory.Used by the crash-recovery preamble to detect an owned merge we must abort before restoring the feature branch. This is the positive-verdict projection of
merge_state(); callers that must distinguish “no merge” from “could not tell” callmerge_state()directly.- Parameters:
repo_root (Path | str)
- Return type:
bool
- ralph.git.merge.merge_state(repo_root)[source]¶
Classify the merge state of
repo_rootwithout guessing.Returns
MERGE_STATE_IN_PROGRESSwhenMERGE_HEADexists,MERGE_STATE_NONEwhen the git directory was read and holds noMERGE_HEAD, andMERGE_STATE_UNKNOWNwhengit rev-parse --git-diritself failed.The third verdict is the point of this function. Collapsing it into
Falsemadeabort_merge()a silent no-op on a repository it could not read, which could strand aMERGE_HEADand block every later integration.- Parameters:
repo_root (Path | str)
- Return type:
str
- ralph.git.merge.merge_target_into_current(repo_root, target, *, keep_conflicts=False)[source]¶
Run
git merge --no-edit <target>into the current branch.On non-zero return code, run
git merge --abort(guarded by amerge_state()precheck) and return a conflict result. The abort’s own verdict is checked: when it did not run, failed, or left an unreadable merge state, a warning names the repository rather than letting a possibly-strandedMERGE_HEADhide behind an ordinary conflict result. On success returnMergeResult(outcome='success'). The merge is never force-resolved; conflict outcome is the only escape hatch other than success.With
keep_conflicts=Truea conflicted merge is left in progress (MERGE_HEADretained, conflict markers in the working tree) so a conflict-resolution step can repair and complete it; the caller owns the eventualcommit_merge_in_progress()orabort_merge(). A merge that failed without starting (noMERGE_HEAD) still returns'conflict'with nothing to keep.The target branch name is passed AFTER the
--option terminator so a configured value that starts with-(e.g.--allow-unrelated-histories) is treated as a positional revision argument and never parsed as a git option. This closes the untrusted-config subprocess-argument-boundary exposure that the prompt’s feedback item flagged.- Parameters:
repo_root (Path | str)
target (str)
keep_conflicts (bool)
- Return type:
- ralph.git.merge.paths_with_conflict_markers(repo_root, paths)[source]¶
Return the subset of
pathswhose content still holds markers.git addon a file that still contains<<<<<<<markers silently clears its unmerged state, so an emptyunmerged_paths()result is NOT proof that a conflict was really resolved. This scan closes that hole.A path is reported when EITHER an opening
<<<<<<< `` line or a closing ``>>>>>>> `` line survives: half a conflict hunk is still an unresolved conflict, and demanding both fences let a resolver that deleted only one of them past the last remaining gate. A lone ``=======separator is deliberately still allowed, because it is ordinary reStructuredText/Markdown heading punctuation rather than a conflict-specific token. A path that cannot be read as text (binary or deleted) is skipped rather than reported.- Parameters:
repo_root (Path | str)
paths (Sequence[str])
- Return type:
list[str]
- ralph.git.merge.reset_hard(repo_root, sha)[source]¶
Run
git reset --hard <sha>; crash-recovery only.Used by
ralph.pipeline.auto_integrate.recover_incomplete_integration()to restore the feature branch to itspre_feature_shawhen a run is interrupted mid-rebase. Do NOT call this from any other code path — a hard reset on a feature branch is destructive to any uncommitted work.- Parameters:
repo_root (Path | str)
sha (str)
- Return type:
None
- ralph.git.merge.stage_paths(repo_root, paths)[source]¶
Stage the given paths with
git add -- <paths>.Returns True when every path staged cleanly. The paths are passed AFTER the
--terminator so a filename beginning with-is never parsed as a git option. An emptypathssequence is a no-op that returns True.Ralph stages the resolution itself rather than requiring the resolving agent to run
git add: an agent running under Ralph’s own MCP exec policy is denied every git invocation, so a resolver-side staging step could never succeed.- Parameters:
repo_root (Path | str)
paths (Sequence[str])
- Return type:
bool
- ralph.git.merge.worktree_for_branch(repo_root, branch)[source]¶
Return the worktree path that has
refs/heads/<branch>checked out.Optional-returning wrapper over
worktree_lookup(), kept for callers that do not need to distinguish a failed query from a branch that is checked out nowhere. Callers that DO need that distinction — the auto-integrate fast-forward and refresh paths — callworktree_lookup()directly.- Parameters:
repo_root (Path | str)
branch (str)
- Return type:
Path | None
- ralph.git.merge.worktree_lookup(repo_root, branch)[source]¶
Locate the worktree holding
refs/heads/<branch>, fail-closed.Returns
(verdict, path)whereverdictis one ofWORKTREE_FOUND(pathis the holding worktree),WORKTREE_NOT_CHECKED_OUT(git answered and no worktree holds the branch) orWORKTREE_QUERY_FAILED(git worktree listitself failed).pathisNonefor the latter two.Keeping the third verdict distinct is what stops the fast-forward from compare-and-swapping the shared mainline ref while a live checkout may hold it: in a linked-worktree topology the mainline genuinely IS checked out in a sibling worktree, and a CAS there advances the ref while leaving that checkout’s index and working tree behind.
Parses
git worktree list --porcelain(the stable, scriptable format). When multiple worktrees have the same branch checked out (only possible if one of them is the primary repo and another is a linked worktree, which is itself illegal by git’s own rules), the FIRST match wins. Thebranch <ref>field is the authoritative “what branch is checked out here” indicator; theHEADfield is just a detached SHA / refs/heads/<name> pointer and is intentionally NOT used to decide which branch is checked out.- Parameters:
repo_root (Path | str)
branch (str)
- Return type:
tuple[str, Path | 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:
ProtocolExecutor that runs external processes.
- class ralph.git.rebase.rebase.ProcessResult(returncode, stdout, stderr)[source]¶
Bases:
objectRepresents the result of running a git subprocess.
- Parameters:
returncode (int)
stdout (str)
stderr (str)
- class ralph.git.rebase.rebase.RebaseConflicts(files)[source]¶
Bases:
objectRebase stopped because conflicts remain.
- Parameters:
files (list[str])
- class ralph.git.rebase.rebase.RebaseFailed(kind)[source]¶
Bases:
objectRebase failed with a specific error kind.
- Parameters:
kind (RebaseErrorKind)
- class ralph.git.rebase.rebase.RebaseNoOp(reason)[source]¶
Bases:
objectRebase was not applicable (already up-to-date or invalid state).
- Parameters:
reason (str)
- exception ralph.git.rebase.rebase.RebaseOperationError[source]¶
Bases:
ExceptionRaised when a rebase operation fails.
- class ralph.git.rebase.rebase.SubprocessExecutor[source]¶
Bases:
objectDefault executor powered by run_git, hardened per-invocation.
- ralph.git.rebase.rebase.abort_rebase(*, repo_root=None, executor=None)[source]¶
Abort an in-progress rebase.
- Parameters:
repo_root (Path | str | None)
executor (ProcessExecutor | None)
- 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:
repo_root (Path | str | None)
executor (ProcessExecutor | None)
- 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.
Detection is NUL-delimited (D12 / spec section C detection rule): paths with embedded newlines, spaces, tabs, and unicode survive unchanged, and the same path is authoritative over stderr text for every conflict type – several produce no markers at all (modify/delete, binary, symlink, mode-only, gitlink). The parser used is
ralph.git.hardening.unmerged_paths_z(), the shared index-authoritative surface the rebase engine, conflict-resolution pipeline, and recovery code path all read from. Renames are surfaced as the destination path (the live path the resolver will see); the source lives on the returnedPorcelainEntryfor callers that need both names.- Parameters:
repo_root (Path | str | None)
executor (ProcessExecutor | None)
- 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.
Every flag is set explicitly so no user/system git config can hang, abort, or silently mutate a rebase the agent is watching:
--no-autostash(D4) — a dirty tree never starts integration in the first place; an autostash that--abortlater cannot re-apply is not our problem to inherit.--no-autosquash(D5) — user config can turn it on, andsquash!commits would otherwise open an editor.--no-update-refs(B9) —rebase.updateRefs=truewould force-move other branches pointing into the replay range and half-skip ones checked out in sibling worktrees.--empty=drop(B4) — commits that became empty on replay (change already upstream) are dropped without stopping; the flag also answers the older-git prompt that the empty-result would otherwise trigger.--no-keep-emptyis NOT here on purpose — we want a stop on the first genuinely-empty commit, because it is the one case where the agent’s intent matters.The
--terminator (B1/B8) — a branch whose name begins with-would otherwise be parsed as an option, and a target like--allow-unrelated-historieswould be parsed as a flag rather than a revision.The active branch name is appended AFTER
--so the replay range is unambiguous and independent of anybranch.<name>.merge/fork-point heuristic (B8).The backend is pinned to
mergeviaralph.git.hardening.PINNED_CONFIG_ARGS(G5); the apply backend has documented unrecoverable-interrupt states and a different state layout.rerere.enabled=false(D3) — a recorded wrong resolution would be silently committed with zero conflict signal.commit.gpgsign=false/tag.gpgsign=false(D6) — a locked key or pinentry prompt must never hang or fail a replayed commit.
- Parameters:
upstream_branch (str)
repo_root (Path | str | None)
executor (ProcessExecutor | None)
- Return type:
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:
objectPersisted 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
OSErrorif 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
checkpointto 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:
RebaseContinuationErrorRaised when conflicts remain while attempting to continue.
- exception ralph.git.rebase.rebase_continuation.NoRebaseInProgressError[source]¶
Bases:
RebaseContinuationErrorRaised when no rebase is active but continuation was requested.
- exception ralph.git.rebase.rebase_continuation.RebaseContinuationError[source]¶
Bases:
ExceptionBase exception for rebase continuation helpers.
- exception ralph.git.rebase.rebase_continuation.RebaseVerificationError[source]¶
Bases:
ExceptionRaised 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_kinds¶
Classification helpers for Git rebase outcomes.
- class ralph.git.rebase.rebase_kinds.RebaseErrorKind(kind, metadata=<factory>)[source]¶
Bases:
objectPayload for a classified rebase failure.
- Parameters:
kind (RebaseKind)
metadata (Mapping[str, object])
- class ralph.git.rebase.rebase_kinds.RebaseKind(*values)[source]¶
Bases:
EnumEnum describing every supported rebase failure mode.
ralph.git.rebase.rebase_preconditions¶
Precondition validation before performing a git rebase.
- exception ralph.git.rebase.rebase_preconditions.RebasePreconditionError[source]¶
Bases:
ExceptionRaised 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.
Only a closed set of authoritative markers blocks a rebase: the
rebase-merge/rebase-applydirectories, the in-progress marker files in_IN_PROGRESS_MARKER_FILES, the bisect state files and the git lock files. Any other git-dir entry – including bookkeeping git leaves behind after an operation ends, such asAUTO_MERGE– is observed at DEBUG level but never blocks.- 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:
ExceptionRaised 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:
objectPersisted 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:
EnumEvents that drive transitions in the
RebaseStateMachine.
- class ralph.git.rebase.rebase_state_machine.RebaseLock[source]¶
Bases:
objectContext manager that acquires and releases the rebase lock.
- class ralph.git.rebase.rebase_state_machine.RebasePhase(*values)[source]¶
Bases:
StrEnumLifecycle phase of an in-progress rebase operation.
- class ralph.git.rebase.rebase_state_machine.RebaseStateMachine(checkpoint, *, persist=True, max_recovery_attempts=3)[source]¶
Bases:
objectState machine that coordinates rebase lifecycle via
RebaseCheckpoint.- Parameters:
checkpoint (RebaseCheckpoint)
persist (bool)
max_recovery_attempts (int)
- class ralph.git.rebase.rebase_state_machine.RecoveryAction(*values)[source]¶
Bases:
EnumDecision returned by
decideto guide error recovery in a rebase.
- ralph.git.rebase.rebase_state_machine.acquire_rebase_lock()[source]¶
Acquire the rebase lock file, raising
OSErrorif 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
checkpointto 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:
objectOptions 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-levelGIT_OUTPUT_LIMIT_BYTES(10 MiB) — matching the existingSPILL_OUTPUT_LIMIT_BYTESprecedent atralph/mcp/tools/_exec_output_spill.py:33and well above any realistic single-file diff. Outputs exceeding the cap are truncated with a marker (theManagedProcessOutputLimitExceededErrorsemantics 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.phaseis provided the process label becomesphase:<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:
ralph.git.remote_push¶
Fail-open, non-force push of one target branch to one configured remote.
- ralph.git.remote_push.PUSH_TIMEOUT_SECONDS = 30.0¶
Wall-clock ceiling for each remote push attempt.
- class ralph.git.remote_push.PushResult(status, remote, branch, detail='')[source]¶
Bases:
objectStructured, bounded result of an opt-in remote publication attempt.
- Parameters:
status (PushStatus)
remote (str)
branch (str)
detail (str)
- property success: bool¶
Whether the remote now has the requested target branch.
- property summary: str¶
Human-facing compatibility text derived from facts, never parsed.
- class ralph.git.remote_push.PushStatus(*values)¶
Bases:
StrEnum
ralph.git.wrapper¶
Git wrapper helpers for blocking commits during agent phases.
- class ralph.git.wrapper.GitHelpers[source]¶
Bases:
objectState carrier for one agent-phase git-protection sequence.
GitHelpersis the lightweight bundlestart_agent_phase()andend_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 whichgitbinary is the genuine system one) without recomputing it on every call.- real_git¶
Path to the genuine
gitexecutable on the host. Set by callers when the Ralph wrapper redirectsgitto a sandbox binary;Nonewhen the host has no wrapper in front ofgit. 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/ralphdirectory that holds the agent-phase marker, HEAD OID snapshot, and the Ralph-managed hooks directory. Set bystart_agent_phase()and read byend_agent_phase()during teardown.Nonebeforestart_agent_phase()populates it.- Type:
pathlib.Path | None
- wrapper_repo_root¶
Path to the repository root the protections were enabled for.
Nonebeforestart_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:
Construct (or reuse) a
GitHelpers. Optional — the phase helpers create one for you.Call
start_agent_phase(repo_root, helpers)(); it setswrapper_repo_rootandwrapper_dirand writes the marker / HEAD OID / hooks-path snapshot.Run the agent phase (commit/push attempts are blocked by the marker file the hook scripts check).
Call
end_agent_phase(repo_root, helpers)(); it restorescore.hooksPath, deletes the marker / snapshot / track files, and leaveswrapper_dirpopulated 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 | Nonebecause none of them carry meaning outside of a pairedstart_agent_phase/end_agent_phasecycle.
- 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
HEADagainst the OIDstart_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.Pathor a string; the value is resolved against the GitPythonRepoconstructor.- Returns:
Truewhen a stored snapshot exists and the currentHEADOID differs from it (unauthorized commit detected);Falsewhen no snapshot exists, the snapshot is empty, the currentHEADcannot be read, orHEADstill matches the snapshot.- Return type:
bool
- Side effects:
Closes the GitPython
Repoit 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
gitinvocation fails for a reason other than a missingHEAD(detached/unborn HEAD is reported asFalse, 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 previouscore.hooksPathvalue, deletes the Ralph-managed marker / HEAD-OID / track files under<repo>/.git/ralph/, and closes the GitPythonRepoopened for the duration of the call. After this function returns, the repository is in the same git state it was in before the matchingstart_agent_phase()call.- Parameters:
repo_root (Path | str) – Path to the repository root whose protections should be rolled back. Must match the
repo_rootpassed to the matchingstart_agent_phase()call so teardown targets the same repository. Accepts apathlib.Pathor a string.helpers (GitHelpers | None) – Optional
GitHelperscarrier populated by the matchingstart_agent_phase()call. WhenNone, a fresh carrier is constructed andwrapper_repo_rootis set toPath(repo_root)so teardown can locate the.git/ralphdirectory written during setup.
- Returns:
None. The function mutates the repository’s local
core.hooksPathand deletes the marker / snapshot files written during setup.- Return type:
None
- Side effects:
Restores the previous
core.hooksPathfrom 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
Repoit opened for the duration of the call.
- Raises:
git.exc.GitCommandError – If any underlying
gitinvocation fails (filesystem permission, missing snapshot file, or a held lock). Each subprocess is bounded byGIT_SUBPROCESS_TIMEOUT_SECONDSso 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 whetherHEADadvanced 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
HEADOID under<repo>/.git/ralph/, snapshots the previouscore.hooksPathvalue, and repointscore.hooksPathat 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 skipend_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.Pathor a string; the value is resolved against the GitPythonRepoconstructor.helpers (GitHelpers | None) – Optional pre-built
GitHelperscarrier. WhenNone, a fresh carrier is constructed and populated withwrapper_repo_rootandwrapper_dir. The same carrier (or one with identical population) must be passed to the matchingend_agent_phase()call so teardown targets the right repository.
- Returns:
None. The function mutates the repository’s
.git/ralphdirectory, the localcore.hooksPathconfig, and the suppliedGitHelperscarrier.- 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
Repoit opened for the duration of the call (callers must not reuse the original handle).
- Raises:
git.exc.GitCommandError – If any underlying
gitinvocation fails (filesystem permission, missing.git, or a held lock). Each subprocess is bounded byGIT_SUBPROCESS_TIMEOUT_SECONDSso 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 whetherHEADadvanced 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:
objectIn-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
- 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
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:
ProtocolFile 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
- explore_index: object | None¶
Optional
ralph.mcp.explore.handlers.ExploreIndexhandle attached by the production session bridge. The protocol stays permissive (object | None) so legacy workspaces that never opted into indexed exploration remain compatible without an explicit cast.
- 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
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 aralph-workflow.tomlconfig 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 withroot,allowed_roots,local_config_path, andpropagated_config_paths. Usescope.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:
objectSingle 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
WorkspaceScopeinstance rather than callingpathlib.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 deduplicatesallowed_rootsso 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.
rootis 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.tomlfile. Defaults to<root>/.agent/ralph-workflow.tomlwhen 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
rootand collecting any.agent/ralph-workflow.toml,agents.toml,pipeline.toml,artifacts.toml, ormcp.tomlit finds. Order is parent-first so the most-specific entry wins on conflict.- Type:
tuple[pathlib.Path, …]
- Lifecycle:
Construct (or receive from
resolve_workspace_scope()) aWorkspaceScope.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).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 torepo_root. Theworker_namespaceis always added so the worker can write its own artifacts, logs, and temporary outputs even whenallowed_directoriesis narrow. AValueErroris raised when any entry escapesrepo_rootvia..or an absolute path.This method bypasses the standard __init__ to avoid unconditionally adding
repo_rootto 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:
- 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:
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,
agent_retry_* scratch per retry, crashed-run tmp/codex-home-*
directories, and crashed MCP session JSON files — 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/,tmp/agent_retry_*.md,tmp/codex-home-*(using the directory’s own mtime so mirrored symlinks cannot keep orphans fresh), andralph-mcp-session-*.json. When.agent/state.dbis present (RFC-013 P3) the sweep also callsRunStateDB.prune_older_thanso 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 aFailureClassifierand anAgentBudgetRegistry.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-levelcycle_caplimit 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:
objectRegistry mapping (phase, agent_name) -> BudgetState.
Immutable-value-returning:
debitreturns a new registry instance. The previousresetmethod 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 heavyweightClassifiedFailureobjects (original_exception + traceback frames) for the lifetime of the registry. Onlyconsumedis needed to drive the exhausted / remaining decisions.- Parameters:
phase (str)
agent (str)
failure (ClassifiedFailure)
- Return type:
- 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]]
- class ralph.recovery.budget.BudgetState(max_retries, consumed=0)[source]¶
Bases:
objectImmutable budget state for a single (phase, agent) pair.
max_retriesandconsumedare the only counters needed to drive every budget decision (exhausted / remaining). A previousfailures: 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 heavyweightClassifiedFailureobjects (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:
objectPer-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 heavyweightClassifiedFailureobjects (original_exception + traceback frames) for the lifetime of the budget. Onlyconsumedis needed to drive the exhausted / remaining decisions.- Parameters:
failure (ClassifiedFailure)
- Return type:
- ralph.recovery.budget.seed_budget_registry(bundle)[source]¶
Seed the budget registry from policy bundle configuration.
- Parameters:
bundle (PolicyBundle)
- Return type:
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:
objectA 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:
StrEnumCategories of pipeline failures for attribution and routing.
- class ralph.recovery.classifier.FailureClassifier[source]¶
Bases:
objectClassify 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.
- class ralph.recovery.classifier.FailureContext(phase, agent=None, retry_in_session=False, classified_failure=None)[source]¶
Bases:
objectContext 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:
objectA snapshot of a connectivity state transition.
- Parameters:
state (ConnectivityState)
since (datetime)
reason (str)
- 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:
objectProactively 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]
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 onlast_retry_delay_msand re-enters the same phase. Theis_waiting_stateflag is the structured contract the run loop keys off;last_errortext 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_indexsearch 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:
objectSingle 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
agentsthat 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]
Public access to the unavailability store (Protocol-typed).
Callers MUST consume the store through this property, not through the private
_unavailability_trackerattribute. The Protocol is the seam for a future persistent (sqlite, redis, file) implementation; the in-memoryAgentUnavailabilityTrackeris the default whenRecoveryControllerOptions.unavailability_storeis 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)wherecooldown_ms_remainingis 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_trackeror 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 matchesagentsinput 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:
objectOptions 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:
objectTracks 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)
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:
objectStructured 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:
objectSimple 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:
objectEmitted 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:
objectDeterministic connectivity monitor for tests.
Allows injecting state transitions without real network probes.
- Parameters:
initial_state (ConnectivityState)
ralph.recovery.agent_budget_registry¶
Registry mapping (phase, agent_name) to budget state.
- class ralph.recovery.agent_budget_registry.AgentBudgetRegistry(budgets=None)[source]¶
Bases:
objectRegistry mapping (phase, agent_name) -> BudgetState.
Immutable-value-returning:
debitreturns a new registry instance. The previousresetmethod 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 heavyweightClassifiedFailureobjects (original_exception + traceback frames) for the lifetime of the registry. Onlyconsumedis needed to drive the exhausted / remaining decisions.- Parameters:
phase (str)
agent (str)
failure (ClassifiedFailure)
- Return type:
- 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]]
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:
objectImmutable budget state for a single (phase, agent) pair.
max_retriesandconsumedare the only counters needed to drive every budget decision (exhausted / remaining). A previousfailures: 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 heavyweightClassifiedFailureobjects (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:
objectA 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:
objectPer-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 heavyweightClassifiedFailureobjects (original_exception + traceback frames) for the lifetime of the budget. Onlyconsumedis needed to drive the exhausted / remaining decisions.- Parameters:
failure (ClassifiedFailure)
- Return type:
ralph.recovery.failure_category¶
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.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:
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 aRuntimeEnvironmentwith 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 fromralph.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:
objectStructured Python runtime version metadata.
- Parameters:
major (int)
minor (int)
micro (int)
releaselevel (str)
serial (int)
implementation (str)
executable (Path)
version (str)
- class ralph.runtime.RuntimeEnvironment(python, executable, prefix, base_prefix, exec_prefix, base_exec_prefix, in_virtualenv, virtualenv_path, env)[source]¶
Bases:
objectSnapshot 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])
- exception ralph.runtime.SuiteTimeoutError(timeout_seconds)[source]¶
Bases:
RuntimeErrorRaised 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_processreportedTIMEOUT_EXIT_CODE. Surfaced asself.timeout_secondsfor programmatic inspection.- Return type:
None
The error message embeds the policy-violation banner from
_POLICY_FIX_MESSAGEso 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 currentos.environis used as the base.test_timeout_seconds (float) – Value for
RALPH_PYTEST_TEST_TIMEOUT_SECONDS(defaultDEFAULT_TEST_TIMEOUT_SECONDS= 1.0).suite_timeout_seconds (float) – Value for
RALPH_PYTEST_SUITE_TIMEOUT_SECONDS(defaultDEFAULT_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.environor 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:
- 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
commandunder 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.
Noneinherits the parent process environment.suite_timeout_seconds (float) – Wall-clock cap (seconds) passed to
run_process. Default isDEFAULT_SUITE_TIMEOUT_SECONDS(60 s). Note this is the per-invocation cap; the combined test budget is enforced upstream inralph.verify.capture_output (bool) – When
True(default), capture stdout/stderr into the returnedProcessResult; whenFalse, the subprocess writes directly to the parent’s streams.
- Returns:
The
ProcessResultfromrun_process.- Raises:
SuiteTimeoutError – When the subprocess exits with
TIMEOUT_EXIT_CODE, indicating the suite exceededsuite_timeout_seconds.- Return type:
- Side effects:
Spawns a subprocess through the shared
_VERIFY_TIMEOUT_PMProcessManager. The subprocess inherits the parent environment (with the timeout env vars added whenenvisNone); both stdout/stderr are routed percapture_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_SECONDSandRALPH_PYTEST_SUITE_TIMEOUT_SECONDS.default (float) – Value returned when
nameis unset.
- Returns:
The parsed float from the environment, or
defaultif the variable is missing. RaisesValueErrorif 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:
objectSnapshot 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])
- 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:
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:
StrEnumTyped 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:
objectFreshness-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:
objectUnified verdict from child-liveness evidence classification.
- Parameters:
alive_by (AliveBy | None)
deferral_allowed (bool)
all_children_terminal (bool)
- 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:
objectImmutable 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:
objectIn-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
- 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:
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:
objectProduction 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:
objectTest-only probe that returns a fixed activity answer.
When
active_labelsis provided the probe simulates a specific set of active process labels:any_agent_active(prefix)returns True only when at least one label inactive_labelsstarts withprefix. This lets tests distinguish between related and unrelated agent workers.When
active_labelsis None the probe falls back to the flatactiveflag (existing behaviour, unchanged).When
snapshotis provided, child_snapshot() returns it for any prefix.- Parameters:
active (bool)
active_labels (frozenset[str] | None)
snapshot (ChildActivitySnapshot | None)
- class ralph.process.liveness.LivenessProbe(*args, **kwargs)[source]¶
Bases:
ProtocolProtocol 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
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:
objectTracked 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.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.
- ralph.process.mcp_supervisor.AGENT_PROCESS_LABEL_PREFIX = 'invoke:'¶
Label prefix every agent subprocess is spawned under (see
ralph.agents.invoke._process_readerand._pty_runner, which both uselabel=f"invoke:{...}"). Terminating this prefix releases an agent that is blocked on MCP calls the server can no longer answer.
- class ralph.process.mcp_supervisor.McpSupervisor(bridge, *, check_interval=datetime.timedelta(seconds=2), on_restart=None, on_error=None, terminate_agents=<function _terminate_agent_processes>)[source]¶
Bases:
objectBackground-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)everycheck_intervalseconds. Restarts are recorded via the optionalon_restartcallback. If the restart budget is exhausted, the storedMcpServerErroris re-raised when the context manager exits — taking priority over any agent-level error.- Parameters:
bridge (RestartAwareMcpBridge)
check_interval (timedelta)
on_restart (Callable[[int], None] | None)
on_error (Callable[[McpServerError], None] | None)
terminate_agents (Callable[[str], None])
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
OpenCodeExecutionStrategyingests into a per-invocationChildLivenessRegistry. The factory returnsOpenCodeRegistryDiscoveryStrategyfor the OPENCODE transport when a registry is provided so a per-childRegistryBackedSubagentOutputCapturecan 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
NullDiscoveryStrategyfor 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.
═══ OWNERSHIP GUARD — a PID alone is NEVER authority to kill ═══
A PID is a reusable integer, not a capability. Acting on one that this process did not spawn is how a reaper turns into a machine-wide kill:
teardown_subtree(1)enumeratedlaunchd’s recursive descendants — i.e. every process on the host — and SIGTERM’d them all. Fake process managers in the test suite hand outitertools.count(1)PIDs, somake verifyshut the developer’s Mac down.os.killpg(dead_pid, SIGKILL)on an exited host signals whatever unrelated process group has since inherited that number. Every successfulrun_processcall reached this path, becausecommunicate()reaps the child before teardown runs.
So every destructive action below is gated on proof of ownership: the target
must be a live descendant of THIS process (_is_own_descendant), and a
process group is only ever signalled when it was verified — while its leader
was still alive — to be a session this process created
(verified_child_session_pgid). Unverifiable targets are skipped, never
guessed at. Leaking a stray child is recoverable; SIGKILLing a stranger’s
process group is not.
- class ralph.process.teardown.DefaultProcessTeardown(kill_escalation_ms=5000)[source]¶
Bases:
objectReap a process subtree using psutil.
Sends SIGTERM to the host and all descendants, waits up to
KILL_ESCALATION_CEILING_MSfor 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, *, pgid=None)[source]¶
Kill the host process and all of its descendants.
- Parameters:
host_pid (int) – PID of the subtree root. Ignored unless it is a live descendant of this process — see the module docstring.
pgid (int | None) – Process group verified via
verified_child_session_pgidwhile the host was alive. Supplying it lets the group still be reaped after the host exits; without it, an already-dead host is a no-op, because its PID number may since have been reused.
- Return type:
None
- class ralph.process.teardown.ProcessTeardown(*args, **kwargs)[source]¶
Bases:
ProtocolProtocol for reaping a process subtree.
- ralph.process.teardown.register_child_session(pid)[source]¶
Record
pid’s process group as one this process owns.Call once, right after spawning a
start_new_session=Truechild, while it is still alive. The recorded group is what letsteardown_subtreereap descendants that outlive the leader: after the leader exits, nothing about the bare PID can prove ownership any more.Returns the verified PGID, or None when ownership could not be proven (a fabricated PID from a fake process manager, or a child sharing our group).
- Parameters:
pid (int)
- Return type:
int | None
- ralph.process.teardown.teardown_subtree(host_pid, *, kill_escalation_ms=5000, pgid=None)[source]¶
Convenience function that reaps a subtree with the default implementation.
- Parameters:
host_pid (int)
kill_escalation_ms (float)
pgid (int | None)
- Return type:
None
- ralph.process.teardown.verified_child_session_pgid(pid)[source]¶
Return
pidas a process-group ID iff we own that session.Call this while the child is still ALIVE — the answer is what makes a later group signal safe once the child is gone. Returns the PGID only when the process is a live descendant of this process AND is its own group leader (which holds exactly for children spawned with
start_new_session=True). Returns None otherwise; None means “do not signal any group”.- Parameters:
pid (int)
- Return type:
int | 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 requiredidand optionalname/providerfields;frozen=Trueso callers can hash and compare entries safely.fetch_catalog()— returns the fulllist[ModelEntry]fromhttps://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 singleModelEntryby its fully-qualified"provider/model"identifier; returnsNonewhen the id is absent.list_providers()— sorted unique list of every provider present in the current catalog snapshot.search_models()— case-insensitive substring search overname,provider, andid; returnslist[ModelEntry].validate_local_model_support()— run a local OpenCode preflight probe (opencode models --refresh <provider>) and returnNonewhen the local binary supportsmodel_idor 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:
ProtocolCallable 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 localopencodebinary (resolved fromenv_pathorPATH) confirmed the model is supported after amodels --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 targetsmodel_id.
- Parameters:
model_id (str) – Fully-qualified
"provider/model"identifier. When"/"is absent the function returnsNoneimmediately (the caller passed a bare provider name).command (str) – Executable name or path to invoke. Defaults to
"opencode", resolved viashutil.which().env_path (str | None) – Optional override for the
PATHused to resolvecommand. WhenNonethe current process’sPATHis used._run_process (ProcessRunner) – Subprocess seam (
ProcessRunnerprotocol). Defaults toralph.executor.process.run_process(); injectable for tests.
- Returns:
Nonewhenmodel_idis 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 underralph.executor.process.run_process()with the_LOCAL_COMMAND_TIMEOUT_SECScap. Resolution walks theenv_path(orPATH) usingshutil.which()andos.accessso 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:
StrEnumLifecycle status of a Ralph Workflow instance.
- class ralph.supervising.WorkflowInstanceView(instance_id, run_id, lifecycle_status, current_stage, recent_activity)[source]¶
Bases:
objectImmutable 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 andsnapshot.run_idis copied to the view’srun_idfield. This form is used internally byWorkflowInstanceTrackerto preserve the orchestrator-assigned identity while exposing the runtimerun_idseparately.When called without an identity override (the default), the view’s
instance_idis taken directly fromsnapshot.run_id. This form is only valid whensnapshot.run_idis not None. Ifsnapshot.run_idis None and no override is provided, aValueErroris 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_idis None and no_instance_id_overrideis provided. The supervising contract requires a stable identity.- Return type:
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 intoCheckpointBuilder.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:
objectBuilder for assembling enriched Python checkpoint payloads.
- Parameters:
_state (PipelineState | None)
_run_context (RunContext | None)
_execution_history (ExecutionHistory)
_working_dir (str)
_policy (PipelinePolicy | None)
- execution_history(execution_history)[source]¶
Attach bounded execution history.
- Parameters:
execution_history (ExecutionHistory)
- Return type:
- pipeline_policy(policy)[source]¶
Attach the pipeline policy for policy-driven progress derivation.
- Parameters:
policy (PipelinePolicy)
- Return type:
- run_context(run_context)[source]¶
Attach run lineage metadata.
- Parameters:
run_context (RunContext)
- Return type:
ralph.checkpoint.execution_history¶
Bounded checkpoint execution history models.
- class ralph.checkpoint.execution_history.ExecutionHistory(steps=(), file_snapshots=<factory>)[source]¶
Bases:
objectBounded 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:
step (ExecutionStep)
limit (int)
- Return type:
- clone_bounded(limit)[source]¶
Clone the history while keeping only the most recent steps.
- Parameters:
limit (int)
- Return type:
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:
objectTrack 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)
- record_developer_iteration()[source]¶
Return a copy with one more completed developer iteration.
- Return type:
- classmethod resumed_from(previous)[source]¶
Create a new run context for a resumed session.
- Parameters:
previous (RunContext)
- Return type:
ralph.checkpoint.size_monitor¶
Checkpoint size monitoring helpers.
- class ralph.checkpoint.size_monitor.CheckpointSizeMonitor(thresholds=<factory>)[source]¶
Bases:
objectCheck 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:
- check_size(size_bytes)[source]¶
Return the alert level for a serialized checkpoint size.
- Parameters:
size_bytes (int)
- Return type:
- classmethod with_thresholds(thresholds)[source]¶
Create a monitor with custom thresholds.
- Parameters:
thresholds (SizeThresholds)
- Return type:
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:
objectCheckpoint payload combining pipeline state with extension metadata.
- Parameters:
state (PipelineState)
run_context (RunContext)
execution_history (ExecutionHistory)
working_dir (str)
- property phase: str¶
Expose the current phase directly for checkpoint summaries.
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:
objectSingle 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:
ralph.checkpoint.size_alert¶
Alert levels for checkpoint size checks.
ralph.checkpoint.size_check_result¶
Structured results for checkpoint size checks.
ralph.checkpoint.size_thresholds¶
Threshold values for checkpoint size checks.
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:
objectOutcome 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:
- classmethod partial(completed, remaining)[source]¶
Create a partial outcome.
- Parameters:
completed (str)
remaining (str)
- Return type:
- classmethod skipped(reason)[source]¶
Create a skipped outcome.
- Parameters:
reason (str)
- Return type:
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:
objectDiagnostics 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:
- class ralph.diagnostics.AgentStatus(name, display_name, available, json_parser, command)[source]¶
Bases:
objectStatus 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:
objectComplete diagnostic report combining system and agent information.
- Parameters:
system (SystemInfo)
agents (AgentDiagnostics)
fs_health (FsHealth | None)
- system¶
System information.
- agents¶
Agent availability diagnostics.
- fs_health¶
Filesystem-environment health for the workspace volume (RFC-013 P4).
Nonewhenworkspace_rootwas not provided torun_diagnostics.- Type:
- class ralph.diagnostics.FsHealth(volume_root, spotlight_indexing_enabled=None, fsevents_journal_bytes=None, warnings=<factory>)[source]¶
Bases:
objectFilesystem-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.Nonewhen Spotlight status cannot be determined (e.g. non-macOS host,mdutilmissing, or subprocess error).- Type:
bool | None
- fsevents_journal_bytes¶
Total size of files under
<volume>/.fseventsd.Nonewhen 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=None, process_manager=None)[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 | None) – Subprocess runner. Injectable for tests; when supplied,
process_manageris ignored.process_manager (ProcessManager | None) – Child-process manager used by the default runner.
- Returns:
FsHealthpopulated with the volume root, Spotlight status,.fseventsdjournal size, and any operator warnings. On non-darwin hosts onlyvolume_rootis set.- Return type:
- class ralph.diagnostics.SystemInfo(os, arch, working_directory, shell, git_version, git_repo, git_branch, uncommitted_changes)[source]¶
Bases:
objectSystem 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, *, git_probe=<function run_git_probe>)[source]¶
Gather system information.
- Parameters:
env (Mapping[str, str] | None) – Environment mapping used to resolve the login shell. Defaults to
os.environ.git_probe (GitProbe) – Seam used for every git query. Defaults to the real subprocess runner; inject a stub to gather without forking git.
- Returns:
A populated
SystemInfo.- Return type:
- ralph.diagnostics.run_diagnostics(registry, *, env=None, is_available_fn=<function _is_agent_available>, workspace_root=None, git_probe=<function run_git_probe>)[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
FsHealthsnapshot for the volume containing the workspace (Spotlight status,.fseventsdjournal size, operator warnings).git_probe (GitProbe) – Seam forwarded to
SystemInfo.gather()for every git query. Defaults to the real subprocess runner.
- Returns:
DiagnosticReport containing all diagnostic information.
- Return type:
- ralph.diagnostics.run_git_probe(args)[source]¶
Run a git command for diagnostics and return its trimmed stdout.
This is the production
GitProbe. It never raises: a non-zero exit, a timeout, or a missing git binary all reportNone.- Parameters:
args (list[str]) – Git arguments, without the leading
git.- Returns:
Trimmed stdout on success, otherwise
None.- Return type:
str | None
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:
objectFilesystem-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.Nonewhen Spotlight status cannot be determined (e.g. non-macOS host,mdutilmissing, or subprocess error).- Type:
bool | None
- fsevents_journal_bytes¶
Total size of files under
<volume>/.fseventsd.Nonewhen 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=None, process_manager=None)[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 | None) – Subprocess runner. Injectable for tests; when supplied,
process_manageris ignored.process_manager (ProcessManager | None) – Child-process manager used by the default runner.
- Returns:
FsHealthpopulated with the volume root, Spotlight status,.fseventsdjournal size, and any operator warnings. On non-darwin hosts onlyvolume_rootis set.- Return type:
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):
Long paths middle-truncate to absorb excess length on long paths.
Long phase labels tail-truncate to absorb excess length on labels.
Iteration label form degrades canonical (
Cycle 1/3/iter 2/5) -> compact (C1/3/i2/5) -> minimal (1/3/2/5) below the canonical-fit threshold. Custom outer labels (such asRemediationorRound) overrideCycle.The phase marker is dropped below the marker-fit threshold.
Per-iteration glyphs are dropped below the glyph-fit threshold.
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_widthargument tomake_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 overFORCE_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 incontent_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:
objectMultiplexed terminal display for parallel pipeline workers.
Maintains per-worker
RingBufferinstances through anActivityRouterand renders them as a live Rich table while agents are running.All display logic lives on this class; the previously separate
PlainLogRendererinralph.display.plain_rendererhas 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, body_text=None, source_timestamp=None)[source]¶
Emit a kind-tagged, level-badged content line.
body_text(wt-028-display S-4 / DA-002): the body content to wrap on continuations. WhenNone(the default) the function falls back tosanitized(the full rendered text) – the pre-fix behavior that broke the 40-column wrap by consuming the budget on the chrome prefix. When a caller passes the raw body separately, the wrap uses it as a standalone string and the first line keeps the chrome prefix (timestamp + level + cat + badge + rendered chrome) on its own row, so a continuation at the floor carries readable multiword body chunks instead of one-character fragments.source_timestamp(DA-003 / wt-028-display): the source-event ISO-8601 timestamp the parser pipeline extracted from the agent output. When supplied, it replaces the display clock fallback so the rendered record carries the source time instead of the momentemit_activity_linehappened to run.None(the default) keeps the pre-fix behaviour – the display clock stamps the 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)
body_text (str | None)
source_timestamp (str | None)
- Return type:
None
- emit_agents_table(agents)[source]¶
Render the agent table for –list-agents.
Port of the retired ralph.cli.options.display_agents_table helper.
- 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 the retired artifact_renderer.render_analysis_decision helper.
- 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_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 the retired ralph.display.tables.show_checkpoint_summary helper.
optionsis aCheckpointSummaryOptions-like object withphase(str) andbudget_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 the retired artifact_renderer.render_commit_message helper.
- 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 viaself._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 byemit_phase_transition()(section rule + transition banner) andemit_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--quietmode 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_bannersandtests/integration/test_transcript_end_to_end.py::test_quiet_mode_suppresses_run_start_and_phase_closepin this contract.- Parameters:
snapshot (PipelineSnapshot) – The pipeline snapshot to render.
options (CompletionSummaryOptions | None) – Optional
CompletionSummaryOptionsinstance. WhenNone(the default), a freshCompletionSummaryOptions()is constructed.
- Return type:
None
- emit_config_table(config)[source]¶
Render the effective config panel for –check-config.
Port of the retired ralph.display.tables.show_config helper.
DA-004 (wt-028-display S-6 / AC-05): on a height-constrained console (
height <= 12) the borderedPanelaround the full config JSON degrades to an unboxed headed summary that lists the top-level config keys (without the giant nested JSON body). The bordered form would consume the entire 12-row working area; the heading-only form keeps the section rule + a condensed key list so the operator still sees the effective configuration structure without scrolling past a 90+ row bordered panel.- Parameters:
config (UnifiedConfig)
- Return type:
None
- emit_development_artifact(workspace_root)[source]¶
Render development results using the authoritative Markdown handoff.
Port of the retired artifact_renderer.render_development_artifact helper.
- Parameters:
workspace_root (Path)
- Return type:
None
- emit_diagnose_inventory_table(rows)[source]¶
Render the diagnose inventory table.
rowsis a list of tuples; each tuple is one row whose items become the cells of that row in column order. The first column is theServer(theme.cat.meta), the second is theOrigin, the third is theTransportand the fourth is theExposure. 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.
detailsis 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 the retired ralph.display.first_run_panel.render_first_run_panel helper.
P0 (wt-028-display S-6 / AC-05 / DA-005): on a height-constrained console (
self._ctx.height <= 12perDisplayContext.is_height_constrained()) the bordered Panel degrades to unboxed headed text so the welcome takes fewer rows in the working area. The information is the same; the visual chrome is dropped because the panel border + padding would crowd a 12-row split pane. The threshold is<=(not strict<) so the canonical 12-row floor activates the constrained presentation – the 12-row split pane is the documented accessibility path and must trigger the degradation on the boundary itself.- Parameters:
content (list[RenderableType])
- Return type:
None
- emit_fix_artifact(workspace_root)[source]¶
Render fix result artifacts as a titled block.
Port of the retired artifact_renderer.render_fix_artifact helper.
- Parameters:
workspace_root (Path)
- Return type:
None
- emit_info_panel(*, title, content)[source]¶
Render a theme.phase.planning bordered info Panel.
Used by
diagnoseto surface the “Next steps” panel and any free-form info block. Replaces the inlinePanel(...)call in diagnose.py.P0 (wt-028-display S-6 / AC-05): on a height-constrained console the bordered Panel degrades to a heading + body pair (no border, no padding) so the info block fits inside a 12-row working area without crowding the scrollback. The title still carries the theme.phase.planning color so the reader can still locate the section.
- 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_quietis 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 the retired ralph.display.tables.show_metrics helper.
- 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 the retired artifact_renderer.render_missing_plan_hint helper.
- Return type:
None
- emit_parsed_event(unit_id, kind, content, metadata, timestamp=None)[source]¶
Buffer consecutive terminal results so transport floods collapse.
- Parameters:
unit_id (str)
kind (ActivityEventKind)
content (str | None)
metadata (dict[str, object])
timestamp (str | None)
- 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 the retired ralph.display.phase_banner.show_phase_close_banner helper. 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) andemit_phase_close_from_exit()(one-line recap from aPhaseExitModel). The two recap methods stay unchanged; this banner method is the rich, model-based close banner. Do not collapse the three methods.- Parameters:
exit_model (PhaseExitModel)
pipeline_policy (PipelinePolicy | None)
- 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 the retired ralph.display.phase_banner.show_phase_start helper.
- 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 the retired ralph.display.phase_banner.show_phase_start_from_entry helper. 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:
entry (PhaseEntryModel)
pipeline_policy (PipelinePolicy | None)
- 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 the retired ralph.display.phase_banner.show_phase_transition helper. 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 or canonical Markdown summary.
Port of the retired artifact_renderer.render_plan_artifact helper.
- Parameters:
workspace_root (Path)
- Return type:
None
- emit_providers_table(providers)[source]¶
Render the providers table for –list-providers.
Port of the retired ralph.cli.options.display_providers_table helper.
- 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
diagnoseandsmoketables whose row shape does not match the dedicatedemit_diagnose_*/emit_metrics_*helpers. The renderable is printed throughself._consoleso 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 the retired artifact_renderer.render_review_artifact helper.
- 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.
DA-005 (S-6 / AC-05): on a height-constrained console (
height <= 12) the visual chrome (section rule blank line + glyph banner) compresses before any information is dropped; the orientation rows stay visible, condensed into a single unboxed headed block, so the same information reaches the operator on a 12-row split pane or a magnified screen.- 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_texthelper inralph.cli.commands.init(one of the 13+ directconsole.printcall 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_quietis 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
tagandmessageare 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.printcalls inralph.cli.commands.init.- Parameters:
message (str)
- Return type:
None
- emit_welcome_banner(*, version)[source]¶
Print the Ralph Workflow welcome banner.
Port of the retired ralph.banner.show_banner helper.
P0 (wt-028-display S-6 / AC-05): on a height-constrained console the bordered
Panel.fitaround the ASCII-art banner degrades to a plain heading line so the banner takes one row instead of seven. The ASCII art itself is skipped on the constrained surface (it would dominate the 12-row working area); the title + version line carries the same identity information.- 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 run_started_monotonic: float | None¶
Return the run-start monotonic anchor (
time.monotonicunits) orNone.P0 (wt-028-display AC-01): exposed so the Status Bar can recompute elapsed at render time without a model re-push. The anchor is the same
self._run_start_timevaluerun_elapsed_secondsis built on, so callers that want a snapshot use the latter and callers that want the live bar to tick use this anchor plusself._monotonic()(or an injected clock) at render time.
- set_watchdog_attention(value)[source]¶
Set the watchdog-sourced attention state.
wt-047-stall-label: the subscriber’s sink mirrors every watchdog event’s authoritative assessment as
"stalled"/None. It is called from the subscriber thread (indirectly from the watchdog emit path), so the dedicated lock keeps Status Bar reads race-free.Noneclears the stall.# ponytail: one run-level slot is last-writer-wins during fan-out; # track per-unit stalls only if concurrent stalls become an operator hazard.
- Parameters:
value (str | None)
- Return type:
None
- classmethod strip_markup(line)[source]¶
Strip valid Rich markup and terminal control sequences.
Malformed markup remains literal so agent output is not lost.
- Parameters:
line (str)
- Return type:
str
- update_status_bar(model)[source]¶
Push a new
StatusBarModelto 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
- property watchdog_attention: str | None¶
Return the watchdog-sourced attention state, or
None.wt-047-stall-label: this is the Status Bar host’s read of the watchdog’s per-event stall assessment stream. The subscriber mirrors each assessment as
"stalled"/Noneand callsset_watchdog_attention(). The host substitutes the value only when pushedattentionis None; pushedwaiting/retrying/terminatedalways win. ReturnsNonewhen no stall transition has been published since the last run cleanup.
- class ralph.display.PhaseIterationContext(outer_dev=None, outer_dev_cap=None, inner_analysis=None, inner_analysis_cap=None)[source]¶
Bases:
objectCanonical 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 Cycle 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
- 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)[source]¶
Bases:
objectOrientation 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)
- class ralph.display.StatusBar(display, *, clock=None)[source]¶
Bases:
objectLifecycle owner for the persistent bottom Status Bar.
The StatusBar is composed by
ralph.display.parallel_display.ParallelDisplayand reachable viapd.status_bar. The public push-side surface isralph.display.parallel_display.ParallelDisplay.update_status_bar()(callers invokedisplay.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. Thestart()andstop()methods are wired through ParallelDisplay’s ownstart()/stop()lifecycle. Reads happen vialast_model.- Parameters:
display (ParallelDisplay)
clock (Callable[[], float] | None)
- _display¶
Same-package reference to the owning ParallelDisplay instance. Reads
display._ctx(live DisplayContext that the runner keeps fresh via SIGWINCH / poll refreshers) anddisplay._is_quiet.
- _home¶
Home directory resolved once at construction; passed to
render_status_barso render stays pure.
- _live¶
Lazily-constructed
rich.live.Liveinstance (orNone).
- _lock¶
Threading lock guarding
_modelassignment.
- 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
StatusBarModelsupplied viaupdate().
- 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._renderableso each refresh tick re-reads the latest model — the initialrenderableargument is only the first-frame content.Correctness:
_liveis committed toself._liveONLY afterLive.start()succeeds. IfLive.start()raises (e.g. on a console whoseLive.start()path is broken, or a parent that suppresses the underlying terminal), the exception is swallowed butself._livestaysNone. This keepsis_activehonest (is_activeis defined asself._live is not None) so a laterstart()retry still succeeds andstop()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
modelfor 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 invokestatus_bar.update(model)directly; the consolidated contract isdisplay.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_SECONDcadence (4.0 Hz / 250 ms by default), so update calls feed a freshStatusBarModeland the next refresh tick renders it. On Rich “dumb terminal” consoles whereLive.start()succeeds but Rich refuses to draw frames, the fallback renderer erases the previous fallback row and emits one bounded replacement row sois_activestays observable.Safe to call before
start(); in that case the model is stored and the subsequentstart()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, integration_alert=None, outer_label=None, elapsed_seconds=None, agent_name=None, run_started_monotonic=None, attention=None)[source]¶
Bases:
objectImmutable 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)
integration_alert (str | None)
outer_label (str | None)
elapsed_seconds (float | None)
agent_name (str | None)
run_started_monotonic (float | None)
attention (str | 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 cycle (1-indexed), or
Nonewhen the active phase does not track outer progress.- Type:
int | None
- outer_dev_cap¶
Outer cycle cap, or
Nonewhen unknown.- Type:
int | None
- inner_analysis¶
Current inner analysis iteration (1-indexed), or
Nonewhen the active phase does not track analysis cycles.- Type:
int | None
- inner_analysis_cap¶
Inner analysis iteration cap, or
Nonewhen unknown.- Type:
int | None
- integration_alert¶
Operator-facing alert rendered as a leading bar segment while an auto-integrate conflict is unresolved (
Noneotherwise). Present so a run that needs conflict resolution can never scroll its warning out of sight.- Type:
str | None
- elapsed_seconds¶
Elapsed run duration supplied by the display clock, never persisted in pipeline state.
- Type:
float | None
- agent_name¶
Current agent identity supplied by the phase-entry model.
- Type:
str | None
- outer_label¶
Optional phase-appropriate label for the outer cycle (
None-> the neutralCyclelabel viaralph.display.phase_status.format_dev_cycle()). Set toRemediation/Roundetc. by callers whose phase-level semantics want a different noun than the default neutral label, so the bar never claims a phase is something it isn’t (AC-02). WhenNonethe bar rendersCycle N/cap; when set, it renders<outer_label> N/cap.- Type:
str | None
- attention¶
None(healthy),"waiting","stalled","retrying", or"terminated". P0 (wt-028-display AC-03). The"stalled"value is sourced EXCLUSIVELY from the idle watchdog via the host’swatchdog_attentionslot (wt-047-stall-label): the model carries the pushed operator-state value, and the Status Bar host substitutes the watchdog-sourced value on each Live tick ONLY when the pushedattentionis None. Pushed operator states (waiting/retrying/terminated) always win.- Type:
str | 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_displayhelper fromralph.pipeline.legacy_console_display. Rich is a verified required dependency (declared inpyproject.tomlline 22:rich>=13.0) so the construction cannot fail.- Parameters:
workspace_root (Path)
display_context (DisplayContext)
pipeline_policy (PipelinePolicy | None)
is_quiet (bool)
- Return type:
- 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
displayis None but adisplay_contextis provided, the line is written to the context’s console for legacy compatibility.- Parameters:
display (ParallelDisplay | None)
unit_id (str | None)
line (str)
display_context (DisplayContext | None)
- Return type:
None
- ralph.display.format_analysis_cycle(n, cap=None)[source]¶
Return the phase-neutral label for an inner iteration (1-indexed).
- Parameters:
n (int)
cap (int | None)
- Return type:
str
- ralph.display.format_dev_cycle(n, cap=None)[source]¶
Return canonical phase-neutral label for the outer cycle (1-indexed).
The outer cycle is run-level context (the lifecycle counter owned by
developer_iteration/development_final_commit) that is meaningful acrossplanning,development,analysis,commit, and remediation phases alike. The label uses the phase-neutral wordCycleinstead of the phase-specificDevso the same string carries the right meaning in every phase.When cap is provided (and positive), shows
Cycle N/capto make the remaining budget immediately visible. Without a cap, showsCycle #N.Phase-specific labeling overrides (e.g.
Remediationfor policy remediation,Roundfor conflict resolution) are applied in the Status Bar layer viaralph.display.status_bar.StatusBarModel.outer_label, never by introducing a second formatter – format_dev_cycle is the single-source renderer.- 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_contexthelper. The display’s own context is preferred when present (triesdisplay_contextfirst, then_ctxfor back-compat with fakes that store it privately); otherwise the caller-provided context is used.- Parameters:
display (object | None)
display_context (DisplayContext | None)
- Return type:
- 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_stylehelper.Callers that previously imported
phase_stylefromralph.display.phase_bannershould import this accessor instead so they can route through ParallelDisplay’s consolidated surface.- Parameters:
phase (str)
pipeline_policy (PipelinePolicy | None)
- Return type:
str
- ralph.display.render_status_bar(model, ctx, *, home=None, now_monotonic=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.homeis a parameter so callers can supply the resolved home directory once (theStatusBarlifecycle resolves it at construction; tests pass an explicit value).The single default-mode layout renders attention, phase, liveness, elapsed time, applicable iteration context, agent identity, and finally the working directory in priority order. When
ctx.widthis too narrow to fit the canonical forms (Cycle 1/3/iter 2/5) the labels degrade through compact (C1/3/i2/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 (below14 cols) so the bar still fitsctx.width.The phase and path labels are tail/middle truncated to fit the remaining budget.
len(text.plain) <= ctx.widthalways holds (a finalText.truncateclamp 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_rootstarts with it, the rendered path is home-relative.now_monotonic (float | None)
- Returns:
A single-line
rich.text.Textcarrying the bar contents. The rendered text never contains\nso the bar cannot wrap into the working area, andlen(text.plain) <= ctx.widthso the bar fits any terminal width (including widths below 14 cols where iteration segments drop entirely to honor thelen(text.plain) <= ctx.widthinvariant).- 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
DisplayContextpassed asdisplayis unwrapped to itsdisplay_contextslot and a freshParallelDisplayis constructed, so callers that only have a context still get a real display.- Parameters:
display (ParallelDisplay | DisplayContext | None)
display_context (DisplayContext | None)
- Return type:
- 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_displayhelper fromralph.pipeline.legacy_console_display. Pass-through for non-None inputs; constructs aParallelDisplayfrom the supplied context whendisplayisNone. Whenis_quiet=True, the constructed display short-circuits all banner and log-line emissions (see ParallelDisplay quiet-mode contract).- Parameters:
display (ParallelDisplay | None)
display_context (DisplayContext | None)
is_quiet (bool)
- Return type:
- 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 valid Rich markup and terminal control sequences.
- 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:
StrEnumCanonical event kinds emitted across providers.
- class ralph.display.activity_model.ActivityProvider(*values)[source]¶
Bases:
StrEnumCanonical provider identity for agent activity events.
Each value is the canonical identity used on the activity-event bus (the
AgentActivityEvent.providerfield). The enum mirrorsAgentTransportfor 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 toGENERICby the CLI-substring detection indetect_provider_from_command.
- class ralph.display.activity_model.ActivityVisibilityHint(*values)[source]¶
Bases:
StrEnumVisibility 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:
objectTyped canonical activity event for future parser normalization work.
- Parameters:
provider (ActivityProvider)
kind (ActivityEventKind)
content (str | None)
metadata (dict[str, object])
visibility (ActivityVisibilityHint)
source (str)
sequence (int | None)
timestamp (str | None)
- class ralph.display.activity_model.EventOptions(content=None, metadata=None, visibility=ActivityVisibilityHint.VISIBLE, source='')[source]¶
Bases:
objectOptions for constructing an AgentActivityEvent.
- Parameters:
content (str | None)
metadata (dict[str, object] | None)
visibility (ActivityVisibilityHint)
source (str)
- ralph.display.activity_model.make_event(*, provider, kind, options=None)[source]¶
Construct an AgentActivityEvent with an auto-incremented sequence and UTC timestamp.
- Parameters:
provider (ActivityProvider)
kind (ActivityEventKind)
options (EventOptions | None)
- Return type:
- 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.
The activity_router pipeline feeds raw agent activity strings here, so
contentis sanitized throughstrip_terminal_control()BEFORE the cell-width truncation (so an escape sequence can never be split in half) and BEFORE richescape(so rich markup like[red]...[/red]is still neutralised to\[red]...\[/red]).After the wt-028-display consolidation this function constructs a canonical
AgentActivityEventat the ingestion boundary viaralph.display.agent_event_renderer.make_event_for_emit()and callsralph.display.agent_event_renderer.render_event()directly so every event kind routes through the single registry. The pre-render sanitization call is kept here so theralph.testing.audit_terminal_escape_containmentliteral-string invariant that pins this function as a containment sink stays satisfied: a regression that drops the strip is detected as a verify-gate failure. The literal also stays here so the body keeps a defence-in-depth sanitization pass for callers that buildcontentoutside the registry’s normalizer (e.g. raw router lines).- Parameters:
kind (ActivityEventKind)
content (str | None)
timestamp (str | None)
- Return type:
str
ralph.display.activity_event_kind¶
Canonical activity event kinds.
ralph.display.activity_provider¶
Canonical provider identities for activity events.
- class ralph.display.activity_provider.ActivityProvider(*values)[source]¶
Bases:
StrEnumCanonical provider identity for agent activity events.
Each value is the canonical identity used on the activity-event bus (the
AgentActivityEvent.providerfield). The enum mirrorsAgentTransportfor 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 toGENERICby the CLI-substring detection indetect_provider_from_command.
- ralph.display.activity_provider.provider_for_transport(transport)[source]¶
Return the canonical
ActivityProviderfor anAgentTransportvalue.Falls back to
ActivityProvider.GENERICwhentransportisNoneor unknown so callers can blindly forward optional transport values without worrying about the ActivityProvider enum.- Parameters:
transport (str | None)
- Return type:
ralph.display.activity_visibility_hint¶
Visibility hints for activity event presentation.
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:
objectWire 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
RingBufferandAgentParserentries fromself._buffersandself._parsersso 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
ActivityProviderfrom the agent command executable name.- Parameters:
command (list[str])
- Return type:
ralph.display.agent_event_renderer¶
Single registry-driven renderer for agent-output events.
After the wt-028-display consolidation, all agent-output rendering flows through this module. Three independent renderers existed before the consolidation:
ralph/pipeline/activity_stream.py_render_agent_activity_line(and its per-type helpers_render_text_line,_render_tool_use_line,_render_tool_result_line,_render_error_line,_render_metadata_event_line,_tool_input_summary,_metadata_summary,_kv_summary,_styled_prefix) – the pipeline runner’s path.ralph/display/activity_model.pyrender_event_line– the ring buffer / activity-router path (subprocess executor side).ralph/display/parallel_display.py_emit_activity_eventinline body (friendly_tool_name,format_tool_input,condense_content) – the parallel / live-display path.
These three had drifted apart over time. Each grew its own truncation limit, its own icon table, and its own tool-input formatter, so the same logical event could render visibly different text depending on which path produced it. The single renderer registry below owns every agent-output event presentation decision once:
The
EventRendererProtocoldefines the per-kind rendering contract; each kind has exactly one renderer.render_event(event, ctx, *, unit_id=None)is the single public entry point. All three paths above now delegate here.normalize_event_from_agent_output_lineis the single boundary that converts a parser-shapedAgentOutputLineinto anAgentActivityEvent(reusingactivity_model.make_eventandactivity_router.map_parser_type_to_kind) so agent-specific quirks (claude / codex / opencode / …) are normalized BEFORE rendering. The same logical line should produce the same rendered text regardless of which backend produced it.All visible output passes through
line_sanitizer.strip_terminal_controlso a stray escape sequence from an agent can never reach the Live region or the redirected transcript.All styles reference
ralph.display.theme.STATUS_STYLESso a semantic state (success / error / pending / …) carries the same rich-style + unicode-glyph + ascii-label triple everywhere. No literal rich styles appear in this module.
Adding a new agent event kind is a single-file change: add a renderer
function and register it in EVENT_RENDERERS. Existing call sites do
not need to be touched.
- ralph.display.agent_event_renderer.DEFAULT_STYLE = 'default'¶
Sentinel theme key used for plain-text body segments (no semantic role).
- class ralph.display.agent_event_renderer.EventRenderer(*args, **kwargs)[source]¶
Bases:
ProtocolRender a single
AgentActivityEventinto a richText.Implementations MUST be pure (no I/O, no env reads, no Console construction) and MUST reference
ralph.display.theme.STATUS_STYLESfor state-driven styling rather than literal rich styles. The same event rendered by the same renderer MUST return text whose plain representation is identical regardless of which agent backend produced the source line.escape_bodycontrols whether the body segment is Rich-escape()’d before being appended to the returnedText. The default (True) is the rich-Text path’s contract: the body is printed through a Console withmarkup=Trueso literal[red]markers must be escaped. The plain-text path (render_event_kind_text()) passesFalseso the body surfaces verbatim throughmarkup=Falseconsumer contexts (literal[result]content reaches the user unchanged).
- ralph.display.agent_event_renderer.make_event_for_emit(kind, content, *, timestamp=None, metadata=None, source=None)[source]¶
Construct a canonical
AgentActivityEventfrom loose render args.Production ingestion sites that still receive loose render arguments (e.g. the
_emit_activity_eventcallback inralph.display.parallel_displayand therender_event_lineadapter inralph.display.activity_model) call this to build the typed event BEFORE callingrender_event()so the registry owns every rendering decision.Uses
UNKNOWNas theActivityProviderbecause the ingestion sites that hold loose args are provider-agnostic – agent-specific quirks have already been removed upstream bynormalize_event_from_agent_output_line()so the registry renders the same line regardless of the originating provider (AC-07).- Parameters:
kind (ActivityEventKind)
content (str | None)
timestamp (str | None)
metadata (dict[str, object] | None)
source (str | None)
- Return type:
- ralph.display.agent_event_renderer.normalize_event_from_agent_output_line(line, *, provider, unit_id='', source_kind=None)[source]¶
Convert a parser-shaped
AgentOutputLineto the canonical event.Single boundary used by every code path that ingests parser lines; agent-specific quirks (claude/codex/opencode) are removed BEFORE rendering so the same logical line produces a byte-identical rendered string regardless of the backend that emitted it.
- Parameters:
line (AgentOutputLine) – The raw
AgentOutputLineproduced by a parser.provider (ActivityProvider) – The canonical
ActivityProviderfor the source.unit_id (str) – Stable unit identifier for audit breadcrumbs.
source_kind (ActivityEventKind | None) – Optional caller override; defaults to the canonical
map_parser_type_to_kindmapping.
- Returns:
An
AgentActivityEventready forrender_event().- Return type:
- ralph.display.agent_event_renderer.render_event(event, ctx=None, *, unit_id=None, active_identities=None, escape_body=True)[source]¶
Render
eventvia the registry into a richText.This is the single public surface for agent-event rendering. All three former renderers (activity_stream._render_agent_activity_line, activity_model.render_event_line, parallel_display._emit_activity_event) delegate to this function.
- Parameters:
event (AgentActivityEvent) – The canonical agent event to render.
ctx (DisplayContext | None) – Display context providing theme / glyphs / width. The canonical renderers do not currently consume
ctx(they readSTATUS_STYLESdirectly) but the parameter is part of the stable contract so future renderers can pick it up without breaking call sites.unit_id (str | None) – Optional unit identifier; threads into the rendered line so the per-unit identity surfaces in both the rich-Text and the plain-text paths.
escape_body (bool) – When
True(default) the body segment is Rich-escape()’d before being appended. The plain-text path (render_event_kind_text()) passesFalseso literal content reaches the consumer unchanged.active_identities (Iterable[str] | None)
- Returns:
A
rich.text.Textinstance whose plain string carries a non-color redundancy (icon + ASCII label) for every kind.- Return type:
Text
- ralph.display.agent_event_renderer.render_event_kind_text(kind, content, *, timestamp=None, metadata=None, agent_name=None, max_chars=200)[source]¶
Render a stable plain-text line for a single kind + content.
Used by non-rich code paths (the ring-buffer / activity-router path whose consumers don’t carry a Console, and the
ralph.pipeline.activity_stream._render_agent_activity_line()pipeline-runner shim, plus tests that want to assert on a stable plain-text line). After the wt-028-display consolidation, this function is a thin adapter over the canonicalrender_event()registry: it builds the sameAgentActivityEventthe registry expects, calls the registry withescape_body=False(so literal[result]content reaches the plain-text consumer unchanged –escape()is only needed when the Text will be printed through a Console withmarkup=True), then extractstext.plainand applies cell-aware truncation. The icon + ASCII label + state carrier all flow fromralph.display.theme.STATUS_STYLESvia the registry, so the plain-text path cannot drift from the rich-Text path. Theagent_nameprefix threads through the registry’sunit_idparameter so legacy tests assertingbash/command=pytest -q/workdir=/tmp/projectsubstrings continue to pass through the registry’s single source of formatting.A
TOOL_RESULTevent carryingis_error=Truemetadata renders with theerrorcarrier (e.g.✗ FAIL) so an error never accidentally reads as success (AC-10, AC-05).- Parameters:
kind (ActivityEventKind)
content (str)
timestamp (str | None)
metadata (dict[str, object] | None)
agent_name (str | None)
max_chars (int)
- Return type:
str
ralph.display.artifact_reader¶
Helpers for reading plan and analysis-decision artifacts.
These readers are intentionally tolerant: missing files, malformed artifacts,
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:
objectA stable projection of an
*_analysis_decisionartifact.- 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:
objectA stable, presentation-friendly projection of a plan Markdown artifact.
- Parameters:
summary (str | None)
scope_items (tuple[str, ...])
total_steps (int)
risks_mitigations (tuple[str, ...])
- ralph.display.artifact_reader.plan_artifact_path(workspace_root)[source]¶
Return the canonical Markdown plan artifact path.
- Parameters:
workspace_root (Path)
- Return type:
Path
- ralph.display.artifact_reader.read_latest_analysis_decision(workspace_root, drain)[source]¶
Read the canonical Markdown decision artifact for
drain.- Parameters:
workspace_root (Path)
drain (str)
- Return type:
AnalysisDecisionSummary | None
- ralph.display.artifact_reader.read_plan_artifact(workspace_root, *, _text_loader=<function _load_text>)[source]¶
Read
.agent/artifacts/plan.mdand project a PlanSummary.Returns
Noneif the file is missing or malformed beyond recovery. Warnings from lenient vocabulary normalization do not hide an otherwise valid plan from the display.- Parameters:
workspace_root (Path)
_text_loader (Callable[[Path], str | None])
- Return type:
PlanSummary | None
ralph.display.auto_integrate_message¶
User-facing phrase for an auto-integration outcome.
Single mapping between the auto-integrate producer state (see
ralph.pipeline.auto_integrate) and the human-readable phrase
shown to the operator. Both the live activity line emitted by
ralph.pipeline.runner and the end-of-run completion summary route
through this function so the two surfaces cannot drift.
- ralph.display.auto_integrate_message.format_auto_integrate_message(action, target, reason, *, fast_forwarded=False, refresh=None, push=None, remote_sync=None, remote=None)[source]¶
Render the auto-integrate outcome into a single human-readable phrase.
The four arguments are exactly the four fields the producer records on
ralph.pipeline.rebase_state.RebaseState. Thefast_forwardedparameter is the only signal that distinguishes a successful land from a refused one, because the producer keepslast_actionasrebased/mergedin BOTH cases and records the fast-forward result on the boolean instead. A successful land rendersrebased onto target (<target>), fast-forwarded <target>(or itsmergedvariant); a refused land renders the same prefix followed by, fast-forward skipped: <reason>.reasonis treated as a fast-forward SKIP reason ONLY whenfast_forwardedis False. Whenfast_forwardedis True any non-Nonereasonis a staleRebaseNoOprebase reason (the producer retains it on a successful land) and must not be rendered as a skip – doing so would emit a phantomfast-forward skippedon a healthy run.refreshis theRebaseState.last_refreshoutcome of the origin refresh performed immediately before the fast-forward observed the target SHA. It is appended as[target refresh: <outcome>]when present, and omitted entirely whenNone(no refresh was recorded, e.g. a skip that short-circuited before the landing phase).pushis theRebaseState.last_pushsummary produced by the opt-in push to the configured remote after a successful local landing. It is appended as[push: <summary>]when present, and omitted entirely whenNone. The push is fail-open, so local success is unchanged regardless of what the push reported.The unknown-verb fallback returns a bare
f"{action}"rather thanf"auto-integrate: {action}"; the singleauto-integrate:prefix is added at the emit site, and emitting a second one here previously doubled the prefix in the live line.- Parameters:
action (str | None)
target (str | None)
reason (str | None)
fast_forwarded (bool)
refresh (str | None)
push (str | None)
remote_sync (str | None)
remote (str | None)
- Return type:
str
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:
Long paths middle-truncate to absorb excess length on long paths.
Long phase labels tail-truncate to absorb excess length on labels.
Iteration label form degrades canonical (
Cycle 1/3/iter 2/5) -> compact (C1/3/i2/5) -> minimal (1/3/2/5) below the canonical-fit threshold. Custom outer labels (such asRemediationorRound) overrideCycle.The phase marker is dropped below the marker-fit threshold.
Per-iteration glyphs are dropped below the glyph-fit threshold.
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, body_measure_cap=100, terminal_background_is_light=None, height=None, env=<factory>, _resolved_env=<factory>, _force_width=None, _force_glyphs=None, _force_height=None, _explicit_env=False)[source]¶
Bases:
objectImmutable 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)
body_measure_cap (int)
terminal_background_is_light (bool | None)
height (int | None)
env (Mapping[str, str])
_resolved_env (_ResolvedEnv)
_force_width (int | None)
_force_glyphs (bool | None)
_force_height (int | None)
_explicit_env (bool)
- 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
- body_measure()[source]¶
Return the cap for prose / log body text on this console.
P1 (wt-028-display S-5 / AC-04): one measure rule for prose and log body text. On a very wide terminal (e.g. 250 cols) the cap keeps the line at a comfortable measure (the
body_measure_capconstant) so the operator’s eye does not have to track a 250-char line. On a narrow console (e.g. 80 cols) the cap is a no-op because the full width is already at or below the cap. Rules, tables, and aligned columns useself.widthdirectly; they are not prose.The minimum of 40 columns is preserved even on the narrowest consoles so a single token wider than the floor still wraps through the line wrap machinery instead of crashing on a zero-or-negative effective measure.
- Return 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
- is_height_constrained(threshold=12)[source]¶
Return
Truewhen the height is known and at-or-belowthreshold.P0 (wt-028-display S-6 / AC-05 / DA-005): on a short terminal (
height <= threshold) framed presentation degrades to unboxed headed text. The threshold defaults to 12 rows (the canonical split-pane size where the working area shrinks enough that a bordered panel would crowd the scrollback). WhenheightisNone(the legacy opt-out) the constraint check returnsFalseso a caller without a known height keeps the full boxed presentation – the height is required to make the degradation decision at all.The check is at-or-below (
<=) rather than strict less-than (<) so the canonical 12-row floor activates the constrained presentation – a 12-row split pane is the documented accessibility path (large-text / magnified / braille displays) and the framed panels must degrade there, not one row later.- Parameters:
threshold (int) – The row count at or below which the presentation is considered “height-constrained”. Defaults to 12 (the canonical short-terminal threshold).
- Returns:
Truewhenself.heightis set and is at-or-belowthreshold;Falsewhenself.heightisNoneor is> threshold.- Return type:
bool
- refreshed()[source]¶
Return a new DisplayContext with refreshed terminal width AND height.
Re-resolves width using the same precedence rules as make_display_context(), preserving any active overrides (COLUMNS, force_width) stored at construction time. P0 (wt-028-display S-6 / AC-03): height is RE-RESOLVED from the live Console on every cycle so a SIGWINCH or poll refresher observes a vertical resize, while the
force_heightoverride the operator pinned at construction time continues to win. The console identity, theme, color_enabled, glyphs_enabled, and adaptive limits are unchanged. Mode is always'default'.- Returns:
New DisplayContext with updated width and height.
- Return type:
- 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, force_height=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).
force_height (int | None) – Override terminal height detection.
Nonefalls back toconsole.size.height(the canonical Rich Console contract);Nonedisables height-aware rendering for callers that don’t consume the height field.
- Returns:
Fully initialised DisplayContext.
- Return type:
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:
objectOptional 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:
snapshot (PipelineSnapshot)
display_context (DisplayContext)
options (CompletionSummaryOptions | None)
- 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
Textobject summarising pipeline completion for the terminal.- Parameters:
snapshot (PipelineSnapshot)
options (CompletionSummaryOptions | None)
- 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:
snapshot (PipelineSnapshot)
display_context (DisplayContext)
options (CompletionSummaryOptions | None)
- 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:
role (str)
pipeline_policy (PipelinePolicy | None)
- 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:
objectOptions 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
CondenseOptionsto configure limits, overflow ref, and summarization. Omit to use defaults.Returns
(visible, condensed_flag)whenoptions.summaryis False. Returns(visible, condensed_flag, summary_line, ai_summary_line)whenoptions.summaryis True.Rules: - If
cell_len(text) <= soft_limit: return(text, False[, None, None])- Ifcell_len(text) <= hard_limit: head-only truncation with suffix - Ifcell_len(text) > hard_limit: head + tail with middle elided- Parameters:
text (str)
options (CondenseOptions | None)
- Return type:
tuple[str, bool] | tuple[str, bool, str | None, str | None]
ralph.display.edit_preview¶
Build syntax-highlighted previews of agent file-edit tool calls.
When ParallelDisplay emits a TOOL_USE event for one of the
content-edit tools (write_file, append_file, edit_file,
ralph_edit_md_artifact, ralph_stage_md_artifact,
ralph_submit_md_artifact), the operator already sees the existing
one-line registry header line carrying the friendly tool name and the
formatted argument repr. That line stays byte-identical (the consolidation
contract). What this module contributes is purely additive: a
syntax-highlighted preview block printed after the header so the
operator can read the actual edit content in the live console instead of
having to open the file or scroll back through the overflow log.
Design:
The builder is pure: it takes
(tool_name, input_dict, width)and returns arichrenderable (orNonefor non-content tools / empty payloads). No Console, no I/O, no env reads. The display owner routes the renderable through its own Console, quiet-gates the print, and suppresses exceptions so a malformed renderable cannot break the live display path (R-3).Language inference is delegated to
ralph.display.language_inference.lexer_for_path(); inference failures degrade to"text"(plain text) rather than raising. Artifact tools that carry nopathkey (ralph_edit_md_artifact,ralph_stage_md_artifact,ralph_submit_md_artifact) default to"markdown"because their content is always a markdown artifact document.All agent-derived content is sanitized through
ralph.display.line_sanitizer.strip_terminal_control()before it enters any renderable, so a hostile escape sequence can never paint the operator’s terminal (R-1). Theaudit_terminal_escape_containmentaudit pins this contract and the dedicatedtests/display/test_terminal_escape_containment.pysuite contains the black-box assertions.Markdown content gets word-wrap (so headings and paragraphs don’t blow past the panel width); non-markdown content does NOT wrap (wrapping python code would garble indentation).
Write-style tools (
write_file/append_file/ralph_stage_md_artifact/ralph_submit_md_artifact): the content is wrapped in arich.syntax.Syntaxwithline_numbers=True; markdown usesrender_markdown_preview()instead.Edit-style tools (
edit_file/ralph_edit_md_artifact): every entry ineditsis rendered as a- old / + newdiff with both hunks syntax-highlighted underline_numbers=Trueusing the same lexer. A positive integerstart_lineon an edit starts its new-content line numbers at that known file position; absent or invalid values remain snippet-relative at 1. Only the literal-and+markers use thetheme.status.error/theme.status.successcarriers, so the diff remains readable when color is disabled.A bounded preview cap (
_MAX_PREVIEW_LINES = 40) trims long content and appends a muted… (N more lines)elision line so the operator knows the file is bigger than what is shown.Zero hex color literals: the new module references named
STATUS_STYLESstyle keys ("success","error","theme.text.muted") instead of literal hex values, so the widertests/display/test_no_hex_colors_outside_theme.pyanti-drift guard (which walks everyralph/display/*.py) does not flag this module (R-2).
Background-aware highlighting¶
Highlight colours are supplied by the fixed-RGB, contrast-tested Pygments
themes in ralph.display.theme.syntax_theme_for_background(). The
background is transparent, so previews retain the operator’s terminal surface.
The previous implementation pinned pygments’ default theme, which
is a light-background theme: it renders plain identifiers and
punctuation at pure black and keywords at dark navy. Combined with a
transparent background that made most of a preview invisible on the
(overwhelmingly common) dark terminal. The background is resolved once
by the display owner via
ralph.display.theme.detect_terminal_background_is_light() – which
asks the terminal for its actual background colour rather than assuming
one – and threaded in as terminal_bg_is_light; this module stays
pure and reads no env.
The diff - / + markers are resolved the same way, through
ralph.display.theme.pick_status_styles(), so the marker colours
also clear WCAG contrast on a light terminal.
- ralph.display.edit_preview.build_edit_preview(tool_name, input_dict, *, width, terminal_bg_is_light, overflow_ref=None, glyphs_enabled=True, diff_fills=None)[source]¶
Return a rich renderable previewing the edit described by
input_dict.Returns
Nonefor tools without content (e.g.exec) or empty payloads so the caller can skip the print cleanly.The renderable is a
rich.syntax.Syntax(write-style tools) or arich.console.Group(edit-style tools with multiple blocks). The caller (ParallelDisplay) prints the renderable through its own Console so the same width / quiet / sanitization policies apply. No I/O happens here – including no env reads and no terminal probing: the caller resolves the background once and passes the answer in.- Parameters:
tool_name (str) – Raw tool name from the event (MCP / alias prefixes are stripped internally).
input_dict (dict[str, object] | PreviewPayload) – The tool-call input payload.
width (int) – Effective console width.
terminal_bg_is_light (bool | None) –
Truewhen the terminal background is light,Falsewhen dark,Nonewhen undetermined.Noneselects the fixed-RGB palette proven safe on both black and white backgrounds; the value also selects the diff-marker styles.overflow_ref (str | None)
glyphs_enabled (bool)
diff_fills (tuple[str, str] | None)
- Returns:
A rich renderable, or
Nonewhen there is nothing to preview.- Return type:
RenderableType | None
- ralph.display.edit_preview.preview_header(tool_name, path, *, glyphs_enabled=True)[source]¶
Return the structural file-content header shown before a preview body.
- Parameters:
tool_name (str)
path (str | None)
glyphs_enabled (bool)
- Return type:
Text
- ralph.display.edit_preview.preview_record_text(tool_name, input_dict, *, overflow_ref=None, glyphs_enabled=True)[source]¶
Return a plain preview projection and its full sanitized source.
The display owner writes the latter only when the bounded live preview elides content; keeping file I/O outside this pure module preserves the preview builder’s no-I/O contract.
- Parameters:
tool_name (str)
input_dict (dict[str, object] | _PreviewPayload)
overflow_ref (str | None)
glyphs_enabled (bool)
- Return type:
tuple[str, str | None]
ralph.display.preview_payload¶
Normalize recognized file-activity events into bounded preview payloads.
The display layer consumes these parser-agnostic values rather than branching
on agent parser identity. Unknown tools and malformed envelopes deliberately
return None.
- ralph.display.preview_payload.PreviewHunk¶
alias of
_PreviewHunk
- ralph.display.preview_payload.PreviewPayload¶
alias of
_PreviewPayload
ralph.display.language_inference¶
Bounded, failure-safe lexer inference for file-content previews.
Path names are preferred over content because a short file fragment is often
ambiguous. Content is consulted only when no meaningful path hint exists.
Every public call degrades to "text" rather than exposing Pygments or
parser failures to the display path.
- ralph.display.language_inference.lexer_for_path(path, content='')[source]¶
Return a lexer alias for arbitrary path/content without raising.
- Parameters:
path (str | None) – Optional source filename.
content (str) – Optional text used only when the path is inconclusive.
- Returns:
A Pygments lexer alias or
"text".- Return type:
str
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), emoji preservation, and full CSI / OSC / two-character ESC
escape containment (alternate screen, erase display, cursor positioning,
scroll region, cursor hide/show, private-parameter CSI like ESC[>0c and
ESC[<35;1;2M, OSC titles, two-character ESC forms like ESC M).
The stripper uses the repository-proven full CSI parameter-byte range taken
from ralph.display.vt_normalizer so every valid CSI sequence is
matched (including the private-parameter forms that a narrower digit-only
class leaks). The narrower forms used elsewhere in the display module are
NOT safe at the agent-content boundary and must never be copied here.
- ralph.display.line_sanitizer.sanitize_display_line(raw, max_chars=200)[source]¶
Sanitize a raw agent output line for safe terminal display.
Decodes bytes (errors=’replace’), normalizes CRLF to LF, removes every terminal control construct via
strip_terminal_control()(so the output is safe to print on a real TTY – no alternate-screen swaps, erase-display repaints, cursor moves, or private-parameter CSI leaks), and truncates tomax_charswith a ‘…’ suffix when over the limit. TAB and LF are preserved so callers can rely on line structure and tab alignment.- Parameters:
raw (bytes | str)
max_chars (int)
- Return type:
str
- ralph.display.line_sanitizer.strip_markup_safe(text)[source]¶
Reduce Rich markup to plain text and strip terminal control, never raising.
This is the ONLY place in the package allowed to hand agent-origin text to
rich.text.Text.from_markup(); every display sink routes through it (the audit’s package-widefrom_markupinvariant enforces that).from_markupis a parser, and arbitrary agent output is adversarial input to it. A grep whose pattern lists PDF filter names emits[/pdf /text /imageb], which Rich reads as a closing tag with no open tag and rejects withMarkupError. Rich raises from its ownConsoleErrorhierarchy (MarkupError,StyleSyntaxError) – NOTValueError– so a guard naming a specific exception type has already failed once here, and a new Rich release may add another type. A display sink that raises takes the whole run down with it, so the guard is deliberately total: any parse failure falls back to the literal text, which stays copy-pasteable.Terminal control bytes are stripped on BOTH paths – the fallback must not become an escape-containment hole.
- Parameters:
text (str) – Arbitrary text, possibly carrying Rich markup, malformed bracket sequences, and terminal control bytes.
- Returns:
Plain text with valid markup reduced, malformed markup left literal, and every terminal control construct removed.
- Return type:
str
- ralph.display.line_sanitizer.strip_terminal_control(text)[source]¶
Remove every terminal control construct from
textwhile preserving visible characters.Strips, in order:
CSI sequences (
ESC[followed by parameter bytes in0x30-0x3Fand intermediate bytes in0x20-0x2Fand a final byte in0x40-0x7E) – covers alternate screen (ESC[?1049h/ESC[?1049l), erase display (ESC[2J) and line (ESC[K), cursor positioning (ESC[H,ESC[12;40H), scroll region (ESC[1;50r), cursor hide/show (ESC[?25l/ESC[?25h), SGR colour (ESC[32m/ESC[0m), and the private-parameter formsESC[>0c(device attributes reply) andESC[<35;1;2M(SGR mouse report).OSC sequences (
ESC]… terminator) – titles (ESC]0;some title BELand the ST-terminatedESC]0;t ESC\form).Two-character ESC forms (
ESC Mreverse index, etc.).
Then sweeps any orphaned C0 control bytes (Bell, Unit-Separator, DEL, etc.) while sparing TAB and LF so line structure and tab alignment survive.
- Parameters:
text (str) – Input string. May contain CSI/OSC/C0/escape bytes.
- Returns:
A new string with every terminal control construct removed. Visible characters (including TAB and LF) are preserved unchanged.
""is returned whentextis empty.- Return type:
str
ralph.display.log_sink¶
Terminal boundary for loguru records.
This module is the terminal boundary for log records. Every loguru record that reaches Ralph’s terminal flows through one of these two sink factories, both of which:
Strip every terminal-control construct via
ralph.display.line_sanitizer.strip_terminal_control()(the single canonical stripper – no module may define a second, narrower regex).Write through a single rendering surface so the rich
Livestatus bar is the only painter of Ralph’s terminal.
Two factories are exposed:
make_sanitizing_log_sink(ctx: DisplayContext): returns a loguru sink that prints throughctx.consolewithmarkup=Falseandhighlight=False. Use this from the CLI where aDisplayContextalready owns a Console.
make_stderr_log_sink: a fallback for worker / library callers that have noDisplayContext. Still strips escapes before writing.
DI: this module MUST NOT construct a rich.console.Console.
The single source of truth for Console construction is
ralph.display.theme. Take the Console from the injected
DisplayContext (ctx.console) and pass an explicit
writer= callback when building the stderr variant.
- ralph.display.log_sink.make_sanitizing_log_sink(ctx)[source]¶
Return a loguru sink that sanitizes via the DisplayContext Console.
- Parameters:
ctx (DisplayContext) – DisplayContext that owns the Console. Its
ctx.console.print(..., markup=False, highlight=False)call is what makes the sink write through the rich Live region – the sameConsoleinstance the status bar uses – so the logger and the status bar are no longer two independent painters and Live’s cursor-relative erases no longer wipe independent log lines.- Returns:
Callable accepting loguru’s fully-formatted message string. The trailing newline added by loguru is stripped (rich adds its own), every terminal-control construct is removed via the canonical stripper, and the visible text is printed through the Console with
markup=Falseandhighlight=Falseso bracketed paths and[bold]tokens survive verbatim.- Return type:
Callable[[str], None]
- ralph.display.log_sink.make_stderr_log_sink(*, writer=None)[source]¶
Return a loguru sink that sanitizes before writing to
sys.stderr.Used by library / worker callers that have no
DisplayContext. Still strips escapes before writing; the writing surface is plainsys.stderrwith awriter=override point for tests.- Parameters:
writer (Callable[[str], None] | None) – Optional override for the writing surface. Defaults to
sys.stderr.write. Tests inject aStringIO.writeso they can assert against captured text without touching the real terminal.- Returns:
Callable accepting loguru’s fully-formatted message string. Strips escapes, strips the trailing newline, and writes the sanitized text via
writer(no rich markup interpretation).- Return type:
Callable[[str], None]
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
\rclears the current accumulated line (rewrite). However,\r\r\nor\r\nat 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.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):
Long paths middle-truncate to absorb excess length on long paths.
Long phase labels tail-truncate to absorb excess length on labels.
Iteration label form degrades canonical (
Cycle 1/3/iter 2/5) -> compact (C1/3/i2/5) -> minimal (1/3/2/5) below the canonical-fit threshold. Custom outer labels (such asRemediationorRound) overrideCycle.The phase marker is dropped below the marker-fit threshold.
Per-iteration glyphs are dropped below the glyph-fit threshold.
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.widthinvariant 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:
objectMultiplexed terminal display for parallel pipeline workers.
Maintains per-worker
RingBufferinstances through anActivityRouterand renders them as a live Rich table while agents are running.All display logic lives on this class; the previously separate
PlainLogRendererinralph.display.plain_rendererhas 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, body_text=None, source_timestamp=None)[source]¶
Emit a kind-tagged, level-badged content line.
body_text(wt-028-display S-4 / DA-002): the body content to wrap on continuations. WhenNone(the default) the function falls back tosanitized(the full rendered text) – the pre-fix behavior that broke the 40-column wrap by consuming the budget on the chrome prefix. When a caller passes the raw body separately, the wrap uses it as a standalone string and the first line keeps the chrome prefix (timestamp + level + cat + badge + rendered chrome) on its own row, so a continuation at the floor carries readable multiword body chunks instead of one-character fragments.source_timestamp(DA-003 / wt-028-display): the source-event ISO-8601 timestamp the parser pipeline extracted from the agent output. When supplied, it replaces the display clock fallback so the rendered record carries the source time instead of the momentemit_activity_linehappened to run.None(the default) keeps the pre-fix behaviour – the display clock stamps the 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)
body_text (str | None)
source_timestamp (str | None)
- Return type:
None
- emit_agents_table(agents)[source]¶
Render the agent table for –list-agents.
Port of the retired ralph.cli.options.display_agents_table helper.
- 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 the retired artifact_renderer.render_analysis_decision helper.
- 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_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 the retired ralph.display.tables.show_checkpoint_summary helper.
optionsis aCheckpointSummaryOptions-like object withphase(str) andbudget_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 the retired artifact_renderer.render_commit_message helper.
- 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 viaself._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 byemit_phase_transition()(section rule + transition banner) andemit_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--quietmode 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_bannersandtests/integration/test_transcript_end_to_end.py::test_quiet_mode_suppresses_run_start_and_phase_closepin this contract.- Parameters:
snapshot (PipelineSnapshot) – The pipeline snapshot to render.
options (CompletionSummaryOptions | None) – Optional
CompletionSummaryOptionsinstance. WhenNone(the default), a freshCompletionSummaryOptions()is constructed.
- Return type:
None
- emit_config_table(config)[source]¶
Render the effective config panel for –check-config.
Port of the retired ralph.display.tables.show_config helper.
DA-004 (wt-028-display S-6 / AC-05): on a height-constrained console (
height <= 12) the borderedPanelaround the full config JSON degrades to an unboxed headed summary that lists the top-level config keys (without the giant nested JSON body). The bordered form would consume the entire 12-row working area; the heading-only form keeps the section rule + a condensed key list so the operator still sees the effective configuration structure without scrolling past a 90+ row bordered panel.- Parameters:
config (UnifiedConfig)
- Return type:
None
- emit_development_artifact(workspace_root)[source]¶
Render development results using the authoritative Markdown handoff.
Port of the retired artifact_renderer.render_development_artifact helper.
- Parameters:
workspace_root (Path)
- Return type:
None
- emit_diagnose_inventory_table(rows)[source]¶
Render the diagnose inventory table.
rowsis a list of tuples; each tuple is one row whose items become the cells of that row in column order. The first column is theServer(theme.cat.meta), the second is theOrigin, the third is theTransportand the fourth is theExposure. 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.
detailsis 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 the retired ralph.display.first_run_panel.render_first_run_panel helper.
P0 (wt-028-display S-6 / AC-05 / DA-005): on a height-constrained console (
self._ctx.height <= 12perDisplayContext.is_height_constrained()) the bordered Panel degrades to unboxed headed text so the welcome takes fewer rows in the working area. The information is the same; the visual chrome is dropped because the panel border + padding would crowd a 12-row split pane. The threshold is<=(not strict<) so the canonical 12-row floor activates the constrained presentation – the 12-row split pane is the documented accessibility path and must trigger the degradation on the boundary itself.- Parameters:
content (list[RenderableType])
- Return type:
None
- emit_fix_artifact(workspace_root)[source]¶
Render fix result artifacts as a titled block.
Port of the retired artifact_renderer.render_fix_artifact helper.
- Parameters:
workspace_root (Path)
- Return type:
None
- emit_info_panel(*, title, content)[source]¶
Render a theme.phase.planning bordered info Panel.
Used by
diagnoseto surface the “Next steps” panel and any free-form info block. Replaces the inlinePanel(...)call in diagnose.py.P0 (wt-028-display S-6 / AC-05): on a height-constrained console the bordered Panel degrades to a heading + body pair (no border, no padding) so the info block fits inside a 12-row working area without crowding the scrollback. The title still carries the theme.phase.planning color so the reader can still locate the section.
- 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_quietis 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 the retired ralph.display.tables.show_metrics helper.
- 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 the retired artifact_renderer.render_missing_plan_hint helper.
- Return type:
None
- emit_parsed_event(unit_id, kind, content, metadata, timestamp=None)[source]¶
Buffer consecutive terminal results so transport floods collapse.
- Parameters:
unit_id (str)
kind (ActivityEventKind)
content (str | None)
metadata (dict[str, object])
timestamp (str | None)
- 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 the retired ralph.display.phase_banner.show_phase_close_banner helper. 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) andemit_phase_close_from_exit()(one-line recap from aPhaseExitModel). The two recap methods stay unchanged; this banner method is the rich, model-based close banner. Do not collapse the three methods.- Parameters:
exit_model (PhaseExitModel)
pipeline_policy (PipelinePolicy | None)
- 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 the retired ralph.display.phase_banner.show_phase_start helper.
- 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 the retired ralph.display.phase_banner.show_phase_start_from_entry helper. 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:
entry (PhaseEntryModel)
pipeline_policy (PipelinePolicy | None)
- 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 the retired ralph.display.phase_banner.show_phase_transition helper. 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 or canonical Markdown summary.
Port of the retired artifact_renderer.render_plan_artifact helper.
- Parameters:
workspace_root (Path)
- Return type:
None
- emit_providers_table(providers)[source]¶
Render the providers table for –list-providers.
Port of the retired ralph.cli.options.display_providers_table helper.
- 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
diagnoseandsmoketables whose row shape does not match the dedicatedemit_diagnose_*/emit_metrics_*helpers. The renderable is printed throughself._consoleso 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 the retired artifact_renderer.render_review_artifact helper.
- 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.
DA-005 (S-6 / AC-05): on a height-constrained console (
height <= 12) the visual chrome (section rule blank line + glyph banner) compresses before any information is dropped; the orientation rows stay visible, condensed into a single unboxed headed block, so the same information reaches the operator on a 12-row split pane or a magnified screen.- 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_texthelper inralph.cli.commands.init(one of the 13+ directconsole.printcall 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_quietis 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
tagandmessageare 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.printcalls inralph.cli.commands.init.- Parameters:
message (str)
- Return type:
None
- emit_welcome_banner(*, version)[source]¶
Print the Ralph Workflow welcome banner.
Port of the retired ralph.banner.show_banner helper.
P0 (wt-028-display S-6 / AC-05): on a height-constrained console the bordered
Panel.fitaround the ASCII-art banner degrades to a plain heading line so the banner takes one row instead of seven. The ASCII art itself is skipped on the constrained surface (it would dominate the 12-row working area); the title + version line carries the same identity information.- 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 run_started_monotonic: float | None¶
Return the run-start monotonic anchor (
time.monotonicunits) orNone.P0 (wt-028-display AC-01): exposed so the Status Bar can recompute elapsed at render time without a model re-push. The anchor is the same
self._run_start_timevaluerun_elapsed_secondsis built on, so callers that want a snapshot use the latter and callers that want the live bar to tick use this anchor plusself._monotonic()(or an injected clock) at render time.
- set_watchdog_attention(value)[source]¶
Set the watchdog-sourced attention state.
wt-047-stall-label: the subscriber’s sink mirrors every watchdog event’s authoritative assessment as
"stalled"/None. It is called from the subscriber thread (indirectly from the watchdog emit path), so the dedicated lock keeps Status Bar reads race-free.Noneclears the stall.# ponytail: one run-level slot is last-writer-wins during fan-out; # track per-unit stalls only if concurrent stalls become an operator hazard.
- Parameters:
value (str | None)
- Return type:
None
- property status_bar: object¶
Return the composed
StatusBar(owner of the persistent footer).
- classmethod strip_markup(line)[source]¶
Strip valid Rich markup and terminal control sequences.
Malformed markup remains literal so agent output is not lost.
- Parameters:
line (str)
- Return type:
str
- update_status_bar(model)[source]¶
Push a new
StatusBarModelto 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
- property watchdog_attention: str | None¶
Return the watchdog-sourced attention state, or
None.wt-047-stall-label: this is the Status Bar host’s read of the watchdog’s per-event stall assessment stream. The subscriber mirrors each assessment as
"stalled"/Noneand callsset_watchdog_attention(). The host substitutes the value only when pushedattentionis None; pushedwaiting/retrying/terminatedalways win. ReturnsNonewhen no stall transition has been published since the last run cleanup.
- 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_displayhelper fromralph.pipeline.legacy_console_display. Rich is a verified required dependency (declared inpyproject.tomlline 22:rich>=13.0) so the construction cannot fail.- Parameters:
workspace_root (Path)
display_context (DisplayContext)
pipeline_policy (PipelinePolicy | None)
is_quiet (bool)
- Return type:
- 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
displayis None but adisplay_contextis provided, the line is written to the context’s console for legacy compatibility.- Parameters:
display (ParallelDisplay | None)
unit_id (str | None)
line (str)
display_context (DisplayContext | None)
- 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_contexthelper. The display’s own context is preferred when present (triesdisplay_contextfirst, then_ctxfor back-compat with fakes that store it privately); otherwise the caller-provided context is used.- Parameters:
display (object | None)
display_context (DisplayContext | None)
- Return type:
- 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:
phase (str)
pipeline_policy (PipelinePolicy | None)
- 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
DisplayContextpassed asdisplayis unwrapped to itsdisplay_contextslot and a freshParallelDisplayis constructed, so callers that only have a context still get a real display.- Parameters:
display (ParallelDisplay | DisplayContext | None)
display_context (DisplayContext | None)
- Return type:
- 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_displayhelper fromralph.pipeline.legacy_console_display. Pass-through for non-None inputs; constructs aParallelDisplayfrom the supplied context whendisplayisNone. Whenis_quiet=True, the constructed display short-circuits all banner and log-line emissions (see ParallelDisplay quiet-mode contract).- Parameters:
display (ParallelDisplay | None)
display_context (DisplayContext | None)
is_quiet (bool)
- Return type:
- 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 valid Rich markup and terminal control sequences.
- 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:
objectOptional 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:
objectActivity 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:
objectImmutable 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)
- iteration_label_parts()[source]¶
Return ordered canonical label strings for the iteration context.
- Return type:
list[str]
- 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:
objectImmutable 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:
context (ExitContext | None)
- Return type:
- 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:
objectImmutable 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:
snapshot (PipelineSnapshot)
exit_trigger (str)
elapsed_seconds (float | None)
activity (PhaseActivityCounts | None)
- Return type:
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:
objectCanonical 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 Cycle 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
- ralph.display.phase_status.format_analysis_cycle(n, cap=None)[source]¶
Return the phase-neutral label for an inner iteration (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
iter N/captoi1/3(4 chars) anditer #Ntoi#1(3 chars). Theiprefix keeps the label distinct from the outer-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,#Notherwise. The compact and minimal forms of the analysis cycle share the same shape (theAprefix 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 phase-neutral label for the outer cycle (1-indexed).
The outer cycle is run-level context (the lifecycle counter owned by
developer_iteration/development_final_commit) that is meaningful acrossplanning,development,analysis,commit, and remediation phases alike. The label uses the phase-neutral wordCycleinstead of the phase-specificDevso the same string carries the right meaning in every phase.When cap is provided (and positive), shows
Cycle N/capto make the remaining budget immediately visible. Without a cap, showsCycle #N.Phase-specific labeling overrides (e.g.
Remediationfor policy remediation,Roundfor conflict resolution) are applied in the Status Bar layer viaralph.display.status_bar.StatusBarModel.outer_label, never by introducing a second formatter – format_dev_cycle is the single-source renderer.- Parameters:
n (int)
cap (int | None)
- Return type:
str
- ralph.display.phase_status.format_dev_cycle_compact(n, cap=None)[source]¶
Return compact phase-neutral outer-cycle label for narrow-terminal rendering.
Shortens the canonical
Cycle N/captoC1/3(4 chars) so the persistent Status Bar fits a constrained terminal without dropping the iteration field. Without a cap, returnsC#1(3 chars).The compact form keeps the disambiguating
Cprefix so an operator can still tell outer cycles from inner-analysis cycles at a glance. Used byralph.display.status_barwhen the canonical label exceeds the per-iteration label budget derived fromctx.width.- Parameters:
n (int)
cap (int | None)
- Return type:
str
- ralph.display.phase_status.format_dev_cycle_minimal(n, cap=None)[source]¶
Return minimal phase-neutral outer-cycle label for very narrow terminals.
Returns
N/cap(no prefix) when a cap is provided,#Notherwise. Used byralph.display.status_barwhen even the compact form (C1/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
Noneif absent.The path is resolved through
ralph.pro_support.prompt.resolve_effective_prompt_path()so thePROMPT_PATHenv var is honoured in Pro mode. The engine-owned.agent/PRODUCT_CRITERIA.mdis still checked as a fallback because it is the materialised prompt the agent actually reads.The caller supplies the
envmapping. Readingos.environdirectly inside the display module would violate the DI invariant enforced bytests/display/test_di_invariants.py. Callers that do not have a pre-resolved env should passcontext.make_display_context().envor fall back toos.environat their own call site.- Parameters:
workspace_root (Path)
env (Mapping[str, str] | None)
- Return type:
Path | None
ralph.display.presented_entry¶
Canonical PresentedEntry dataclass and its builder.
Split from ralph.display.agent_event_renderer so the
renderer registry file stays under the 1000-line audit cap. The
PresentedEntry is the structured intermediate that both
consumers (live display and text-first record writer) feed off
of; separating it from the rendering helpers keeps the contract
surface small and reviewable.
P1 (wt-028-display): one event, one entry, one identity, one timestamp – the canonical seam that makes “one presentation, one vocabulary” structurally enforced rather than remembered.
- class ralph.display.presented_entry.PresentedEntry(kind, severity, identity, body, timestamp=None, phase=None, cycle=None, iter=None, metadata=<factory>, indent_level=0, grouping_role='agent_text')[source]¶
Bases:
objectCanonical structured output of the agent-event renderer registry.
S-10 (wt-028-display P1): every event the renderer registry produces yields exactly one
PresentedEntry. Two consumers feed off the same stream:the live display (Rich color + glyphs), via
render_to_text;the text-first rendered record (plain, greppable, stable field order), via
ralph.display.record_writer.RenderedRecordWriter.
Adding a new event kind or a new agent means adding one renderer; both consumers inherit the change automatically. The unknown / malformed-input fallback also produces a valid
PresentedEntry(kind=”unknown”, severity=”info”) so a raw dump can never reach either consumer.- Parameters:
kind (str)
severity (str)
identity (str)
body (str)
timestamp (str | None)
phase (str | None)
cycle (int | None)
iter (str | None)
metadata (dict[str, object])
indent_level (int)
grouping_role (str)
- kind¶
ActivityEventKind for the event (e.g.
TEXT,TOOL_CALL,TOOL_RESULT,THINKING,PROGRESS,SUBAGENT_PROGRESS,UNKNOWN).UNKNOWNis the first-class fallback for unparsed / malformed input.- Type:
str
- severity¶
One of
info/warn/error. The renderer registry picks the severity; consumers do not compute it.- Type:
str
- identity¶
Agent / unit identity for this entry. Used by the renderer to apply the deterministic identity color and by the record writer to fill the
agent=field.- Type:
str
- timestamp¶
Optional ISO-8601 timestamp (UTC).
Nonewhen the event has no timestamp (the record writer renders the placeholder[??:??:??]slot in that case).- Type:
str | None
- phase¶
Optional phase label (e.g.
development,planning). Surfaced in the live log section header and in the record writer’s field-order contract.- Type:
str | None
- cycle¶
Optional outer-cycle count (1-indexed). Surfaced in the record writer as
cycle=N.- Type:
int | None
- iter¶
Optional inner-iteration label (e.g.
2/4). Surfaced in the record writer asiter=2/4.- Type:
str | None
- body¶
Plain-text body. Renderers may split this across multiple live-display lines, but the record writer flattens it back to a single greppable line.
- Type:
str
- metadata¶
Optional metadata bag (tool name, exit code, duration, …). Carried for downstream consumers; the record writer ignores it.
- Type:
dict[str, object]
- property agent: str¶
Alias for
identity(the record writer’s contract).S-10 (wt-028-display P1) keeps
identityas the canonical field name on the structured entry, but the text-first record writer’s stable field order usesagent=so the same line is greppable for either concept. Exposingagentas a property pins the compatibility without forking the dataclass shape.
- ralph.display.presented_entry.build_presented_entry(event, *, unit_id=None, timestamp=None, phase=None, cycle=None, iter_=None)[source]¶
Build the canonical
PresentedEntryforevent.S-10 (wt-028-display P1): every renderer in
EVENT_RENDERERSis expected to expose its rendered output as aPresentedEntry. This helper centralises the extraction of the public-ish attributes (kind / severity / identity / body / metadata) so the live display path can keep itsTextrendering while the text-first record writer consumes the same structured intermediate.S-12 (wt-028-display P1): the canonical entry also carries
indent_levelandgrouping_roleso hierarchy is data rather than embedded glyphs. The mapping is fixed in_KIND_TO_GROUPING; adding a new event kind means picking a role there once.S-14 (wt-028-display P1 / AC-04): severity is derived from outcome metadata for
TOOL_RESULT(nonzero exit code or explicitis_error=True→error; otherwiseinfo).ERRORis the only kind that maps toseverity=errorunconditionally. TheUNKNOWN-as-infofallback (the AC-04 missing-data graceful-degrade contract) is preserved: when aTOOL_RESULTomits its exit code and error flag, the renderer does not invent a failure; it staysinfo.- Parameters:
event (AgentActivityEvent)
unit_id (str | None)
timestamp (str | None)
phase (str | None)
cycle (int | None)
iter_ (str | None)
- Return type:
- ralph.display.presented_entry.outcome_is_failure(metadata)[source]¶
True when the tool-result outcome metadata flags a failure.
Inspects the conventional parser metadata keys (
is_error,exit_code,status,error_code,error,stderr). The first three are explicit signals; a presenterrororstderrpayload is treated as a failure even without an explicit code, because the parser only emits those when something went wrong.- Parameters:
metadata (dict[str, object])
- Return type:
bool
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, *, model=None, max_bytes=52428800, flush_interval_seconds=5.0, now=<built-in function monotonic>)[source]¶
Bases:
objectAppend-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)
model (str | None)
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
- 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_writtencounter is the authoritative liveness signal — an on-diskstat()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.record_writer¶
Text-first rendered-record writer (wt-028-display S-13 / AC-11).
The verbatim .agent/raw/<safe_id>.log capture remains the source
of truth: every event the agent emits lands there untouched, and the
content condenser cites that file when it condenses oversized lines.
The rendered record is the parallel human-readable file an
operator reaches for once the terminal is closed and scrollback has
overflowed: one entry per event, coalesced reasoning, single
identity, indentation-based hierarchy, no color codes, stable field
order, safe to grep and redirect.
Two consumers feed the same stream of canonical
PresentedEntry records:
the live display (Rich color + glyphs + spinner residue),
this writer (plain text,
[hh:mm:ss] phase cycle=N iter=N/M agent=fooline format).
Adding a new event kind means adding one renderer; both consumers inherit it. The writer does not know about Rich, so it cannot accidentally leak a live-display artifact into the run record.
- class ralph.display.record_writer.RenderedRecordWriter(workspace_root, agent, *, model=None, on_error=None)[source]¶
Bases:
objectBounded text-first writer for the
.agent/raw/<id>.rendered.logfile.Lifecycle (matches the
RawOverflowLogpattern: bounded buffer, flush interval, silent-disable on I/O error):append(entry)formats one entry to a boundeddeque(maxlen=_DEFAULT_BUFFER_CAP). It flushes before the deque can evict an accepted line, so a chatty stream cannot exhaust memory or silently lose record entries.flush()writes the buffered lines to disk under.agent/raw/<safe_id>.rendered.logand clears the buffer. Failures are caught and recorded via the optionalon_errorcallback; subsequent writes continue silently so a transient disk error cannot crash the display path.disable()permanently disables the writer; furtherappend()calls are no-ops. This is the recovery path for a filesystem that has gone read-only.
Concurrency:
RenderedRecordWriteris thread-safe; the underlyingdequeoperations are atomic and the file write is wrapped in a lock so a concurrentflush()from another thread cannot interleave a partial line.- Parameters:
workspace_root (Path)
agent (str)
model (str | None)
on_error (Callable[[Exception], object] | None)
- append(entry)[source]¶
Format
entryand buffer the resulting line.DA-003 (wt-028-display): flush eagerly when the buffered count reaches
_BUFFER_FLUSH_THRESHOLDso the underlyingdeque(maxlen=_DEFAULT_BUFFER_CAP)never silently evicts an accepted entry on a chatty long-running run. The eager flush reusesflush()– which holds_lockonly for the buffer swap and not for the file write – so concurrentappend()callers from other threads do not block on disk I/O. The lock is acquired once to add the line and decide whether to flush; the threshold check itself runs under the lock so a burst of concurrentappend()calls cannot collectively push the buffer past_DEFAULT_BUFFER_CAPbetween the check and the flush.- Parameters:
entry (object)
- Return type:
None
- property buffer_capacity: int¶
Return the maximum number of entries the in-memory buffer can hold.
DA-003 (wt-028-display): the in-memory
dequelives behind a private_DEFAULT_BUFFER_CAPconstant; tests and out-of-process consumers need a stable public name to size their stress probes (the regression case intests/display/test_record_writer.pyexercisescap + 1events to prove the eager-flush guard fires before the deque silently evicts an accepted entry). Returning the deque’s ownmaxlenkeeps the public property in lock-step with the actual buffer; the value is the same integer_DEFAULT_BUFFER_CAPwould return.deque.maxlenis typed asint | Nonefor unbounded deques; the writer constructs its deque with a finite cap (see__init__), so theor _DEFAULT_BUFFER_CAPfallback is unreachable in production but the type system needs a concreteintreturn path – the fallback returns the canonical cap so the contract is never silently violated if a future change ever relaxes the deque bound.
- property disabled: bool¶
Return
Trueoncedisable()has been called.
- extend(entries)[source]¶
Buffer every entry in
entriesin order.- Parameters:
entries (Iterable[object])
- Return type:
None
- flush()[source]¶
Write the buffered lines to disk and clear the buffer.
Returns the number of lines written. Failures are routed to the optional
on_errorcallback (and silently swallowed) so the display path is never crashed by a transient disk error.- Return type:
int
- property path: Path¶
Return the absolute path of the rendered record file.
- property pending_lines: int¶
Return the number of buffered lines not yet flushed to disk.
- ralph.display.record_writer.rendered_record_path(workspace_root, agent, model=None)[source]¶
Return the absolute path of the rendered record for
agent/model.- Parameters:
workspace_root (Path)
agent (str)
model (str | None)
- Return type:
Path
- ralph.display.record_writer.safe_id_for(agent, model=None)[source]¶
Return the safe filename component for an
(agent, model)pair.The component is what
RenderedRecordWriterappends to.agent/raw/<component>.rendered.log. Two agents that share a model therefore produce distinct files (claude_minimax-M3vspi_minimax-M3); two runs of the same agent / model share the same file (safe_id_for("claude", "minimax-M3")is stable across the project lifetime).- Parameters:
agent (str)
model (str | None)
- Return type:
str
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:
objectThread-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.
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:
objectImmutable 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=(), auto_integrate_action=None, auto_integrate_reason=None, auto_integrate_target=None, auto_integrate_fast_forwarded=False, auto_integrate_push=None, auto_integrate_remote_sync=None, auto_integrate_remote=None)[source]¶
Bases:
objectImmutable 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, ...])
auto_integrate_action (str | None)
auto_integrate_reason (str | None)
auto_integrate_target (str | None)
auto_integrate_fast_forwarded (bool)
auto_integrate_push (str | None)
auto_integrate_remote_sync (str | None)
auto_integrate_remote (str | None)
- 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.
- auto_integrate_push: str | None¶
Opt-in configured-remote push summary after a successful local landing.
Nonewhen sync is disabled or no prior integration produced a record. The fail-open summary remains visible without changing local success.
- auto_integrate_remote_sync: str | None¶
Latest opt-in remote-sync outcome, kept separate from the free-form push summary so both live and completion surfaces render it uniformly.
- 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:
objectDisplay 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:
objectImmutable 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:
state (PipelineState)
context (SnapshotContext | None)
- Return type:
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-
NoneANDctx.widthcan accommodate it),inner analysis iteration (when non-
NoneANDctx.widthcan accommodate it),elapsed run time and current agent identity when the path budget has room.
Width-driven degradation (in order) so len(text.plain) <= ctx.width
holds at every width:
Path middle-truncation absorbs excess length on long paths.
Phase label tail-truncation absorbs excess length on long labels.
Iteration label form degrades canonical -> compact -> minimal.
Phase marker is dropped below the marker-fit threshold.
Per-iteration glyphs are dropped below the glyph-fit threshold.
Elapsed-time and agent-identity segments drop before iteration context when the path budget cannot preserve both core fields.
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 fitsctx.widtheven 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_baris a pure function: no I/O, no env reads, no Console construction, noPath.home()calls (homeis a parameter so the function can be tested deterministically).status_bar.pydoes not construct arich.Consoleand does not reados.environ/os.getenv; the DI invariants test asserts this.The StatusBar lifecycle class lazily constructs a single
rich.live.Liveregion only when the real-TTY gate passes; it never reads env at module import.
Cadence constants:
_STATUS_BAR_REFRESH_PER_SECOND(default4.0): bounded Live-region repaint cadence. The elapsed clock changes each second while a run is active; unchanged frames are not re-emitted by the non-interactive fallback._STATUS_BAR_TRANSIENT(defaultTrue): frames are erased on stop, preserving clean scrollback, copy/paste, terminal search, and post-run log review. The completion panel carries the durable outcome; the final terminated-state push is visible before teardown.
Default rendering¶
The single default layout renders (in priority order):
[attention] [integration_alert] [phase_marker] {phase_label}
[milestone] {liveness} {elapsed}
[milestone] {outer_dev} Cycle N/cap
[milestone] {inner_analysis} iter N/cap
[milestone] Agent name [milestone] {workspace_root}
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, *, clock=None)[source]¶
Bases:
objectLifecycle owner for the persistent bottom Status Bar.
The StatusBar is composed by
ralph.display.parallel_display.ParallelDisplayand reachable viapd.status_bar. The public push-side surface isralph.display.parallel_display.ParallelDisplay.update_status_bar()(callers invokedisplay.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. Thestart()andstop()methods are wired through ParallelDisplay’s ownstart()/stop()lifecycle. Reads happen vialast_model.- Parameters:
display (ParallelDisplay)
clock (Callable[[], float] | None)
- _display¶
Same-package reference to the owning ParallelDisplay instance. Reads
display._ctx(live DisplayContext that the runner keeps fresh via SIGWINCH / poll refreshers) anddisplay._is_quiet.
- _home¶
Home directory resolved once at construction; passed to
render_status_barso render stays pure.
- _live¶
Lazily-constructed
rich.live.Liveinstance (orNone).
- _lock¶
Threading lock guarding
_modelassignment.
- 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
StatusBarModelsupplied viaupdate().
- 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._renderableso each refresh tick re-reads the latest model — the initialrenderableargument is only the first-frame content.Correctness:
_liveis committed toself._liveONLY afterLive.start()succeeds. IfLive.start()raises (e.g. on a console whoseLive.start()path is broken, or a parent that suppresses the underlying terminal), the exception is swallowed butself._livestaysNone. This keepsis_activehonest (is_activeis defined asself._live is not None) so a laterstart()retry still succeeds andstop()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
modelfor 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 invokestatus_bar.update(model)directly; the consolidated contract isdisplay.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_SECONDcadence (4.0 Hz / 250 ms by default), so update calls feed a freshStatusBarModeland the next refresh tick renders it. On Rich “dumb terminal” consoles whereLive.start()succeeds but Rich refuses to draw frames, the fallback renderer erases the previous fallback row and emits one bounded replacement row sois_activestays observable.Safe to call before
start(); in that case the model is stored and the subsequentstart()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, integration_alert=None, outer_label=None, elapsed_seconds=None, agent_name=None, run_started_monotonic=None, attention=None)[source]¶
Bases:
objectImmutable 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)
integration_alert (str | None)
outer_label (str | None)
elapsed_seconds (float | None)
agent_name (str | None)
run_started_monotonic (float | None)
attention (str | 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 cycle (1-indexed), or
Nonewhen the active phase does not track outer progress.- Type:
int | None
- outer_dev_cap¶
Outer cycle cap, or
Nonewhen unknown.- Type:
int | None
- inner_analysis¶
Current inner analysis iteration (1-indexed), or
Nonewhen the active phase does not track analysis cycles.- Type:
int | None
- inner_analysis_cap¶
Inner analysis iteration cap, or
Nonewhen unknown.- Type:
int | None
- integration_alert¶
Operator-facing alert rendered as a leading bar segment while an auto-integrate conflict is unresolved (
Noneotherwise). Present so a run that needs conflict resolution can never scroll its warning out of sight.- Type:
str | None
- elapsed_seconds¶
Elapsed run duration supplied by the display clock, never persisted in pipeline state.
- Type:
float | None
- agent_name¶
Current agent identity supplied by the phase-entry model.
- Type:
str | None
- outer_label¶
Optional phase-appropriate label for the outer cycle (
None-> the neutralCyclelabel viaralph.display.phase_status.format_dev_cycle()). Set toRemediation/Roundetc. by callers whose phase-level semantics want a different noun than the default neutral label, so the bar never claims a phase is something it isn’t (AC-02). WhenNonethe bar rendersCycle N/cap; when set, it renders<outer_label> N/cap.- Type:
str | None
- attention¶
None(healthy),"waiting","stalled","retrying", or"terminated". P0 (wt-028-display AC-03). The"stalled"value is sourced EXCLUSIVELY from the idle watchdog via the host’swatchdog_attentionslot (wt-047-stall-label): the model carries the pushed operator-state value, and the Status Bar host substitutes the watchdog-sourced value on each Live tick ONLY when the pushedattentionis None. Pushed operator states (waiting/retrying/terminated) always win.- Type:
str | None
- ralph.display.status_bar.render_status_bar(model, ctx, *, home=None, now_monotonic=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.homeis a parameter so callers can supply the resolved home directory once (theStatusBarlifecycle resolves it at construction; tests pass an explicit value).The single default-mode layout renders attention, phase, liveness, elapsed time, applicable iteration context, agent identity, and finally the working directory in priority order. When
ctx.widthis too narrow to fit the canonical forms (Cycle 1/3/iter 2/5) the labels degrade through compact (C1/3/i2/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 (below14 cols) so the bar still fitsctx.width.The phase and path labels are tail/middle truncated to fit the remaining budget.
len(text.plain) <= ctx.widthalways holds (a finalText.truncateclamp 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_rootstarts with it, the rendered path is home-relative.now_monotonic (float | None)
- Returns:
A single-line
rich.text.Textcarrying the bar contents. The rendered text never contains\nso the bar cannot wrap into the working area, andlen(text.plain) <= ctx.widthso the bar fits any terminal width (including widths below 14 cols where iteration segments drop entirely to honor thelen(text.plain) <= ctx.widthinvariant).- 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>, _prompt_path_finder=<function find_prompt_path>, _plan_reader=<function read_plan_artifact>, _plan_marker_reader=None, on_snapshot=None, pipeline_policy=None, watchdog_attention_sink=None)[source]¶
Bases:
objectReceives 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:
queue (Queue[PipelineSnapshot])
workspace_root (Path)
run_id (str)
_prompt_reader (Callable[[Path], tuple[str, ...]])
_prompt_path_finder (Callable[[Path], Path | None])
_plan_reader (Callable[[Path], PlanSummary | None])
_plan_marker_reader (Callable[[Path], int | None] | None)
on_snapshot (Callable[[PipelineSnapshot], None] | None)
pipeline_policy (PipelinePolicy | None)
watchdog_attention_sink (Callable[[str | None], None] | None)
- 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
- set_watchdog_attention_sink(sink)[source]¶
Bind (or clear) the watchdog-attention sink after construction.
wt-047-stall-label (DA-001): the supported
subscriber=argument onralph.display.parallel_display.ParallelDisplaylets a test / advanced caller supply a fully-constructedPipelineSubscriber. Without this binder the supplied subscriber’swatchdog_attention_sinkslot stays unset and per-event stall assessment never reaches the hostwatchdog_attentionsurface – the public constructor path would silently fail to renderSTALLED.The constructor
watchdog_attention_sink=parameter is still the preferred seam for production callers (binding at construction time, not after the first event); this method exists for the injected-subscriber path.Noneclears the sink. The binding is guarded by a dedicated lock so a concurrentrecord_waiting_statuscall sees a stable sink reference.- Parameters:
sink (Callable[[str | None], None] | 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.
Fixed-RGB, background-aware Rich and Pygments theme helpers.
All semantic colors are selected from the resolved terminal background and must preserve contrast and non-color structural carriers.
The palette is Okabe-Ito, so semantic states are not distinguished by hue
alone. STATUS_STYLES carries a (rich_style, unicode_icon, ascii_label)
tuple per semantic state so display code can retain every meaning carrier on
colorblind and no-color consoles. Semantic roles are defined once here;
background-aware role tables preserve contrast and hue identity on light,
dark, and unknown terminal backgrounds.
- ralph.display.theme.IDENTITY_PALETTE_ON_UNKNOWN_BG: Final[tuple[str, ...]] = ('#0070F0', '#0080A0', '#3070E0', '#308080', '#508040', '#6070C0', '#608000', '#7060F0', '#807080', '#9060C0', '#907000', '#907030')¶
Mid-luminance identity colours proven readable on both black and white.
- ralph.display.theme.SYNTAX_BACKGROUND_TRANSPARENT: Final[str] = 'default'¶
Rich’s
Syntax(background_color=...)sentinel meaning “do not paint a background; let the terminal’s own background show through”. Named here so syntax-preview call sites never hard-code the literal.
- ralph.display.theme.SYNTAX_THEME_ON_DARK_BG: Final[SyntaxTheme] = <rich.syntax.PygmentsSyntaxTheme object>¶
Syntax-highlight palettes selected for dark and light terminal backgrounds. They use fixed RGB because stock ANSI slots are operator-configurable and therefore cannot satisfy the enforced contrast and CVD contract.
- ralph.display.theme.background_hex_is_light(bg_hex)[source]¶
Classify an arbitrary background colour as light or dark.
Uses the WCAG relative luminance of the colour and the exact crossover point at which dark foregrounds start out-contrasting light ones, so any background colour – not just black or white – is classified from its measured contrast behaviour.
- Parameters:
bg_hex (str) – Background colour in
#rgbor#rrggbbform.- Returns:
Truewhen dark foregrounds read better on this background,Falsewhen light foregrounds do,Nonewhenbg_hexis not a parseable colour.- Return type:
bool | None
- 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.detect_terminal_background_is_light(env)[source]¶
Resolve the terminal background, asking the terminal when allowed.
Wraps
terminal_background_is_light()with the OSC 11 probe so the answer comes from the emulator’s own reported background colour whenever it will tell us. The probe is skipped entirely when an explicitRALPH_TERMINAL_BGoverride is present (no reason to touch the tty when the operator already answered) and degrades toNoneon every terminal that does not implement the query.- Parameters:
env (Mapping[str, str]) – Mapping of environment variable names to values.
- Returns:
Truewhen the background is light,Falsewhen dark,Nonewhen undetermined.- Return type:
bool | None
- ralph.display.theme.diff_fill_styles(terminal_bg_is_light)[source]¶
Return derived removed/added diff fills, or transparent for unknown backgrounds.
- Parameters:
terminal_bg_is_light (bool | None)
- Return type:
tuple[str, str] | None
- ralph.display.theme.diff_token_foregrounds(terminal_bg_is_light)[source]¶
Return the deleted and inserted token colours for the resolved theme.
- Parameters:
terminal_bg_is_light (bool | None)
- Return type:
tuple[str, str]
- ralph.display.theme.format_status(status_name)[source]¶
Format a status name as a Rich-markup string.
Looks up the status in the default
STATUS_STYLEStable and returns a string of the form[style]icon label[]that Rich can render with the linked style and inline icon. RaisesKeyErrorlisting the known statuses when the name is unknown.- Parameters:
status_name (str) – The status key (e.g.
"running").- Returns:
A Rich-markup-formatted string carrying the icon and label of the status.
- Return type:
str
- ralph.display.theme.identity_color(name, *, active=None, terminal_bg_is_light)[source]¶
Return the hex color for an identity, with collision-nudge.
When
activeis supplied, the helper nudges the chosen slot to the first palette position whose hex does not match the resolved hex of any OTHER active identity AND whose simulated hex under each of the three documented CVD deficiencies (deuteranopia / protanopia / tritanopia) does not match either. AC-15 (wt-028-display P3) pins this contract: two simultaneously-rendered identities can never read as the same color under any of the three simulations.Resolution is two-pass: every name in
activeis resolved with the deterministic base slot first, then each name is nudged so it cannot collide with any other name’s RESOLVED color. This prevents N identities that share K base slots from all landing on the same nudge slot.- Parameters:
name (str)
active (Iterable[str] | None)
terminal_bg_is_light (bool | None)
- Return type:
str
- ralph.display.theme.make_console(*, no_color=None, force_terminal=None, width=None)[source]¶
Construct a Rich
Consolewired with the Ralph theme.Centralizes the console construction so every display surface inherits the same theme,
highlight=False(which keeps plaintext inside tool output uncoloured), and the sameno_color/force_terminal/widthresolver semantics. Tests and runtime code use this helper to keep the colour behaviour consistent.- Parameters:
no_color (bool | None) – When
True, strip colour from the console.force_terminal (bool | None) – When
True, treat the console as a TTY even when it is not (useful for tests).width (int | None) – Override the console width (default: auto-detect).
- Returns:
A
rich.console.Consoleinstance with the Ralph theme.- Return type:
Console
- ralph.display.theme.pick_status_styles(terminal_bg_is_light)[source]¶
Return the status style table for the given background.
Maps the boolean/None flag to the matching palette table. A
Noneselects the unknown-background table, whose colours clear the contrast floor on both black and white terminals.- Parameters:
terminal_bg_is_light (bool | None) –
Truefor light backgrounds,Falsefor dark,Nonefor unknown.- Returns:
A mapping from status name to
(label, glyph, style).- Return type:
dict[str, tuple[str, str, str]]
- ralph.display.theme.syntax_theme_for_background(terminal_bg_is_light)[source]¶
Return the fixed-RGB syntax theme resolved for this background.
- Parameters:
terminal_bg_is_light (bool | None)
- Return type:
SyntaxTheme
- ralph.display.theme.terminal_background_is_light(env, *, measured_bg_hex=None)[source]¶
Detect whether the terminal background is light.
Precedence, strongest signal first:
RALPH_TERMINAL_BGas an explicitlight/darkword – the operator overriding everything.RALPH_TERMINAL_BGas a#RRGGBBcolour – classified bybackground_hex_is_light().measured_bg_hex– the colour the terminal itself reported (seeralph.display._terminal_bg_query). This is a measurement, so it beats the heuristic below.COLORFGBG– a coarse legacy hint set by only some emulators, and usable only for the palette indices whose lightness is unambiguous.
Returns
Nonewhen no signal resolves, so the caller can fall back to its documented default rather than acting on a guess.- Parameters:
env (Mapping[str, str]) – Mapping of environment variable names to values.
measured_bg_hex (str | None) – Background colour measured from the terminal, or
Nonewhen no measurement is available.
- Returns:
Truewhen the background is light,Falsewhen dark,Nonewhen undetermined.- Return type:
bool | None
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_filebecomesralph.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 aProcessResultwith stdout, stderr, and return code.run_process_async(cmd, ...)— async variant for use in asyncio contexts.ProcessResult— holds stdout, stderr, returncode, and a conveniencecheck()method that raisesProcessExecutionErroron non-zero exit.ProcessExecutionError— raised when a process exits with a non-zero code; carries the fullProcessResultfor 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:
RuntimeErrorRaised 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:
- class ralph.executor.process.ProcessResult(command, returncode, stdout, stderr)[source]¶
Bases:
objectCaptured result from a completed process.
- Parameters:
command (tuple[str, ...])
returncode (int)
stdout (str)
stderr (str)
- property succeeded: bool¶
Return
Truewhen the process exited successfully.
- class ralph.executor.process.ProcessRunOptions(cwd=None, env=None, timeout=None, capture_output=True, label=None)[source]¶
Bases:
objectExecution 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"insiderun_process/run_process_asyncwhen 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 atprocess.py:175-194and: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_outputisFalsethe child process inherits the parent’s stdout/stderr so output streams directly to the terminal. The returnedProcessResultwill have emptystdoutandstderrstrings.- Parameters:
command (str)
args (Sequence[str])
options (ProcessRunOptions | None)
_pm (ProcessManager | None)
- Return type:
- 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:
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:
StrEnumPossible outcomes that affect pause behavior.
- class ralph.exit_pause.LaunchContext(is_windows, has_terminal_session_marker, parent_process_name)[source]¶
Bases:
objectContext 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:
StrEnumWhen 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:
- 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
SIGINTsynchronously cancelsroot_taskand swaps in the second-SIGINT handler. The slow body (begin_interruptplus the early-escalation poll) is dispatched off the event loop vialoop.run_in_executorwith a done callback that logs any executor-body exception. This makes the cancel + handler-swap fast even whenbegin_interruptwould block.Second
SIGINTforce-kills tracked child processes viapm.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:
objectBridge 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_taskand forward to child PIDs.The fourth argument is type-broadened to accept an
InterruptController(legacy) OR anInterruptDispatcher(new). Discrimination is byisinstanceinside the body. When a controller is passed, the implementation synthesizes a dispatcher that forwards the controller’skill_process_groupandhard_exitso 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:
loop (asyncio.AbstractEventLoop)
root_task (asyncio.Task[object])
bridge (SignalBridge)
controller (InterruptController | InterruptDispatcher | None)
- 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:
objectCoordinate 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_labelis non-empty ANDshutdown_all_for_labelis set, the controller calls the label-targeted closure INSTEAD of the genericshutdown_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_pgidsparameter is the new canonical name; the legacybridge_pidskeyword is accepted via**kwargsfor 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_pgidsparameter is the new canonical name; it is forwarded tokill_process_groupas PGIDs. The legacybridge_pidskeyword is accepted via**kwargsfor backward compatibility and emits a single loguru warning when used. The per-pgid kill loop has been dropped: the realProcessManager’sshutdown_all(0)already escalates to SIGKILL every active record, so the per-pgid loop was redundant. Callers MUST passbridge_pgidsin 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
InterruptControllerfrom a ProcessManager instance.Wires both
shutdown_allandshutdown_all_for_labelclosures sobegin_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:
- 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
signumkwarg defaults tosignal.SIGINTso the existing call sites keep their prior behavior. Passingsignal.SIGTERMinstalls 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 toforce_exitlike the second SIGINT).- Parameters:
on_force_interrupt (Callable[[], None])
signal_getter (SignalGetter)
signal_setter (SignalSetter)
signum (int)
- 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:
objectSingle seam for SIGINT handling.
Wires an
InterruptControllerto aProcessManager, a connectivity-stop callback, and a hard-exit function.begin_interruptforwards to the controller with the dispatcher’skill_labeldefault ('invoke:') and optionally blocks until the process manager’s active-record list is empty.force_exitis 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_calledflag (viaobject.__setattr__, since the dataclass is frozen), routes through the controller’sforce_interruptfor tracked-process shutdown, and then invokes the exit callable. The dispatcher’s ownhard_exitfield is preferred; if it is None, the controller’sforce_exitis invoked so the controller’s injected exit callable is the one that runs (PA-019 thread-through).The
bridge_pidskeyword is accepted for backward compatibility; it is deprecated and emits a single loguru warning when used. New callers MUST passbridge_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_blockusesbegin_interrupt(block=True)which routes through the dispatcher’s liveness-based_wait_for_list_active_empty(waiting forprocess_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 fullgrace_period_sto die naturally before the dispatcher escalates viaforce_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 bymax_wait_s(defaults toself.hard_kill_budget_s). Mirrors the prior inline helper in_runner_interrupt._sigint_early_escalation_poll. The method’s dedicated tests intests/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
InterruptDispatcherfrom a ProcessManager instance.Threads
hard_exitandkill_process_groupinto the controller factory so the controller’s own force_exit path uses the same injected exit callable. Thehard_exitis also stored on the dispatcher (the dispatcher’s force_exit invokes the dispatcher’s own field, not the controller’s). Theclockandsleepkwargs default totime.monotonicandtime.sleepand 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:
- 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_pipelineandralph.cli.commands.run.runbehind a single helper. The helper:Builds an
InterruptDispatchervia the factory.Calls
begin_interrupt(grace_period_s=..., block=True)so the agent’s process group is SIGTERMed viashutdown_all_for_label('invoke:', grace)and the CLI catch blocks until the process manager’s active list drains (or escalates viaforce_exiton deadline expiration).Returns
exit_code(defaultINTERRUPT_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 owntry/exceptand 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
signumkwarg defaults tosignal.SIGINTso the existing call sites keep their prior behavior. Passingsignal.SIGTERMinstalls 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 toforce_exitlike the second SIGINT).- Parameters:
on_force_interrupt (Callable[[], None])
signal_getter (SignalGetter)
signal_setter (SignalSetter)
signum (int)
- 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_interruptentry point (ralph.pipeline._runner_interrupt._begin_interrupt) and the asyncioinstall_signal_handlersentry point (ralph.interrupt.asyncio_bridge._shutdown_block) route through this helper so the bodies cannot drift. The 7th architectural seam iserror_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, nothreading.Thread.join. The dispatcher uses its liveness-based_wait_for_list_active_empty(viablock=True) to wait for the process manager’s active-record list to drain, escalating viaforce_exitonly when the grace deadline elapses with records still active. This replaces the prior CPU-progress-basedrun_early_escalation_polldaemon thread, which SIGKILLed alive-but-zero-CPU long-running agents (writing checkpoints, releasing locks, draining queues) prematurely. Therun_early_escalation_pollmethod 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_sparameter is now unused and is kept for backward compatibility with the prior call shape. The two call sites (ralph/pipeline/_runner_interrupt.pyandralph/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__sofrom 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.signal_getter¶
Protocol for signal getter callables.
ralph.interrupt.signal_handler¶
Shared signal handler typing helpers.
ralph.interrupt.signal_setter¶
Protocol for signal setter 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:
objectCaptured state for a single tracked file.
- Parameters:
path (Path)
checksum (str)
size (int)
exists (bool)
- class ralph.files.operations.FileStateIssue(kind, path)[source]¶
Bases:
objectA mismatch between captured and current file state.
- Parameters:
kind (FileStateKind)
path (Path)
- class ralph.files.operations.FileStateKind(*values)[source]¶
Bases:
EnumKinds of file-state drift detected during checkpoint validation.
- class ralph.files.operations.FileSystemState(root, files)[source]¶
Bases:
objectSnapshots for tracked Ralph files rooted at a workspace path.
- Parameters:
root (Path)
files (dict[Path, FileSnapshot])
- 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:
- 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:
- ralph.files.operations.validate_file_system_state(state, root=None)[source]¶
Compare current tracked files with a captured checkpoint snapshot.
- Parameters:
state (FileSystemState)
root (Path | str | None)
- Return type:
list[FileStateIssue]
ralph.guidelines¶
Language-specific review guidelines for the Python port.
- class ralph.guidelines.GoGuidelines(frameworks=())[source]¶
Bases:
objectReview guidelines for Go codebases.
- Parameters:
frameworks (tuple[str, ...]) – Optional framework names used to add stack-specific guidance.
- class ralph.guidelines.JavaGuidelines(frameworks=())[source]¶
Bases:
objectJava language-specific review checks.
- Parameters:
frameworks (tuple[str, ...]) – Optional framework names used to add stack-specific guidance.
- class ralph.guidelines.JavaScriptGuidelines(frameworks=(), typescript=False)[source]¶
Bases:
objectJavaScript 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.
- class ralph.guidelines.PHPGuidelines(frameworks=())[source]¶
Bases:
objectReview guidelines for PHP codebases.
- Parameters:
frameworks (tuple[str, ...]) – Optional framework names that activate stack-specific checks.
- class ralph.guidelines.PythonGuidelines(frameworks=())[source]¶
Bases:
objectReview guidelines for Python codebases.
- Parameters:
frameworks (tuple[str, ...]) – Optional framework names used to add stack-specific guidance.
- class ralph.guidelines.RubyGuidelines(frameworks=())[source]¶
Bases:
objectRuby review checks.
- Parameters:
frameworks (tuple[str, ...]) – Optional framework names used to add stack-specific guidance.
- 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:
objectRust 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])
- 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:
objectMerged 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.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.
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.
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:
objectJavaScript 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.
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.
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.
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:
objectRuby review checks.
- Parameters:
frameworks (tuple[str, ...]) – Optional framework names used to add stack-specific guidance.
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:
objectRust 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])
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:
objectMerged 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.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:
objectDetected 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.extensions¶
File extension to language mapping for project detection.
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:
objectDetected 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:
workspace (Workspace)
root (str)
- 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:
workspace (Workspace)
root (str)
- 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_scannable_dir(workspace, path)[source]¶
Return True if
pathis a directory reachable inside the workspace root.Directory listings surface symlinks that point outside the workspace root (uv caches, interpreter shims, tool bin/ links). Probing one raises
ValueErrorfrom the workspace boundary check, so such an entry is reported as non-scannable instead of aborting the whole scan.- Parameters:
workspace (Workspace)
path (str)
- Return type:
bool
- ralph.language_detector.scanner.is_scannable_file(workspace, path)[source]¶
Return True if
pathis a file reachable inside the workspace root.- Parameters:
workspace (Workspace)
path (str)
- Return type:
bool
- ralph.language_detector.scanner.is_test_file_name(file_name, primary_language, path_components)[source]¶
Return True if
file_namematches the test file convention forprimary_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
rootup toMAX_FILES_TO_SCANfiles.- Parameters:
workspace (Workspace)
root (str)
- Return type:
Iterator[str]
- ralph.language_detector.scanner.join_path(parent, child)[source]¶
Join
parentandchildas a normalised POSIX path.- Parameters:
parent (str)
child (str)
- Return type:
str
ralph.language_detector.signatures¶
Signature file heuristics for framework and package manager detection.
Project Policy¶
This group owns the project-policy-readiness preflight: the versioned, machine-checkable schema (markers, required headings, RALPH-FACT / RALPH-COMMAND / RALPH-LANG / RALPH-INAPPLICABLE field markers), the shared readiness-evidence inventory the validator and the cache share, the deterministic validator, the change-aware READY cache, the AGENTS.md / CLAUDE.md idempotent bootstrap, the bundled starter policies, the deterministic preflight orchestrator, and the synchronous, bounded, out-of-graph remediation driver.
ralph.project_policy¶
Project-policy-readiness preflight for Ralph Workflow.
This package owns the deterministic, project-policy-readiness preflight that
runs at the start of every Ralph Workflow invocation (NOT gated on
ralph --init). It is intentionally physically separate from the
ralph/policy/ orchestration-policy package; the two packages have nothing
to do with each other at the code level.
ralph/policy/— the orchestration policy (drain bindings, agentchains, phase graphs, artifacts, MCP).
ralph/project_policy/— the project quality-policy readiness capability(canonical files under
docs/ralph-workflow-policy/, AGENTS.md bootstrap, deterministic validator, change-aware cache, optional remediation driver).
The capability surfaced by this package:
Detects an opt-out via the byte-exact
<!-- ralph-workflow-policy: skip -->marker in AGENTS.md.Builds the shared readiness-evidence inventory the validator and the cache BOTH consume (so a stale ready cannot diverge from a fresh validation).
Idempotently bootstraps AGENTS.md and CLAUDE.md with a managed instruction block.
Runs a deterministic, versioned machine-checkable validator over every canonical policy file (presence, identifier, schema, headings, citations, field markers, placeholders, template banners, per-language coverage).
Caches READY under a content+signature key so a single ready run does not pay for validation on every subsequent preflight; edits and deletions to any input invalidates the cached READY.
When NOT ready, hands findings to a single bounded remediation agent outside the phase graph and ALWAYS re-runs the validator afterward.
Public API:
run_policy_readiness_preflight()— orchestrator (opt-out → cache → bootstrap → validate → cache/return).validate_readiness()— deterministic validator (also exposed for tests).ralph.project_policy.pipeline_driver.run_policy_pipeline()— the two-phase remediation/analysis pipeline the preflight hands off to.
ralph.project_policy.markers¶
Project policy readiness contract markers.
This module is the single source of truth for the deterministic, machine-checkable
schema used by Ralph Workflow’s project-policy-readiness preflight. Every other
module in ralph.project_policy imports these constants; validators, the
shared evidence inventory, the AGENTS.md bootstrap, the cached READY signature,
and the bundled starter policies all reference these tokens instead of repeating
string literals.
Design notes:
All values use
Finaltyping with explicitstrannotations so a typo at any consumer site fails mypy rather than silently passing a different string to the validator.Comments name the rationale so a future maintainer can confirm the marker is still load-bearing before changing it. Changing ANY marker is a hard break of every existing cached READY and every existing canonical policy file; the change must be coordinated with a schema bump and a migration of starter content.
ralph.project_policy.schema_state¶
Classify a project’s policy pack against the schema this Ralph expects.
markers.SCHEMA_VERSION describes the INSTALLED Ralph and is already implied
by ralph.__version__. What is worth knowing — for telemetry, and for any
caller deciding whether to nag about an upgrade — is whether the PROJECT’s own
policy files carry the current schema marker. Only the first non-empty line of
each policy file (the marker line) is inspected; no file content is retained.
- ralph.project_policy.schema_state.POLICY_SCHEMA_STATES: Final[frozenset[str]] = frozenset({'absent', 'current', 'outdated', 'unknown'})¶
Closed vocabulary of policy-schema states.
- ralph.project_policy.schema_state.policy_schema_state(workspace_root)[source]¶
Return the project’s policy-schema state.
current— every present policy file carries the current schema marker.outdated— at least one policy file predates the current marker.absent— the project has no policy pack at all.unknown— the policy pack could not be read.- Parameters:
workspace_root (Path)
- Return type:
str
ralph.project_policy.models¶
Project-policy readiness models — re-export shim.
The canonical class definitions live in the per-attribute private modules:
ralph.project_policy._status—ReadinessStatus.ralph.project_policy._findings—PolicyFinding.ralph.project_policy._evidence—EvidenceEntry.ralph.project_policy._migration—MigrationCandidate.ralph.project_policy._result—ReadinessResult.
The classes are split per-file so each top-level class lives in its own
module (the repository structure policy caps each source file at one
public top-level class). This module preserves the original import path
from ralph.project_policy.models import ReadinessStatus, PolicyFinding,
... for callers and tests that already depend on it.
No I/O. No AI. No network. All models are deterministic data carriers that the validator, the cache, the orchestrator, the remediation driver, and the display layer thread through a single shared contract.
- class ralph.project_policy.models.EvidenceEntry(rel_path, exists, content_sha256=None)[source]¶
Bases:
objectA single
(path, exists, sha256)atom in the readiness-evidence inventory.- Parameters:
rel_path (str)
exists (bool)
content_sha256 (str | None)
- rel_path¶
Workspace-relative path.
- Type:
str
- exists¶
True when the file exists at scan time.
- Type:
bool
- content_sha256¶
Lowercase hex SHA-256 of the file content when
existsis True;Nonewhenexistsis False.- Type:
str | None
- class ralph.project_policy.models.MigrationCandidate(path, recognized_heading, resolved)[source]¶
Bases:
objectA doc flagged by the migration detector.
- Parameters:
path (str)
recognized_heading (str)
resolved (bool)
- path¶
Workspace-relative path of the candidate file.
- Type:
str
- recognized_heading¶
The lowercased heading phrase that caused the file to be classified as a migration candidate.
- Type:
str
- resolved¶
True when the file carries the exact migrated marker (pointing at a canonical file that exists) or no longer contains any recognized heading. Unresolved candidates generate RWP-MIGRATE-UNRECONCILED findings.
- Type:
bool
- class ralph.project_policy.models.PolicyFinding(requirement_id, path, missing_evidence, required_outcome)[source]¶
Bases:
objectA single deterministic validation finding.
- Parameters:
requirement_id (str)
path (str)
missing_evidence (str)
required_outcome (str)
- requirement_id¶
Stable
RWP-*identifier. The validator emits exactly one finding per(requirement_id, path)pair so the remediation agent can address findings deterministically.- Type:
str
- path¶
Workspace-relative path of the affected file or concern (e.g.
docs/ralph-workflow-policy/testing-policy.md).- Type:
str
- missing_evidence¶
Short, human-readable description of what is missing or invalid. Surfaced directly in the BLOCKED report and the remediation prompt.
- Type:
str
- required_outcome¶
What an agent must do to clear the finding.
- Type:
str
- class ralph.project_policy.models.ReadinessResult(status, findings=<factory>, changed_files=<factory>, migrated_sources=<factory>, commands_run=<factory>, report_lines=<factory>)[source]¶
Bases:
objectThe orchestrator’s return value.
- Parameters:
status (ReadinessStatus)
findings (list[PolicyFinding])
changed_files (list[str])
migrated_sources (list[str])
commands_run (list[str])
report_lines (list[str])
ralph.project_policy.evidence¶
Shared readiness-evidence inventory for the project-policy preflight.
This module is the SINGLE owner of every filesystem input the readiness
decision depends on. The validator and the cache BOTH consume the inventory
returned by readiness_evidence() and the signature returned by
evidence_signature(); they cannot diverge because they share the same
input.
The inventory captures (per file): path, existence, and content SHA-256.
content_sha256 is None when the file does not exist so a deletion
invalidates a cached READY just like an edit.
Conditional-domain detection follows the exact signals declared in
ralph.project_policy.markers. The functions return a tuple of
(required, consulted_signal_files) so the orchestrator can report which
inputs triggered each domain.
All file reads happen through the injected Workspace
seam. The module never touches raw pathlib.Path I/O, keeping it
testable with MemoryWorkspace.
- ralph.project_policy.evidence.conditional_domain_requirements(workspace, stack)[source]¶
Return every conditional domain’s requirement and exact evidence.
This is the shared dispatch surface for preflight and validation. New domains cannot be seeded without also being validated from the same result.
- Parameters:
workspace (Workspace)
stack (ProjectStack)
- Return type:
dict[str, tuple[bool, list[str]]]
- ralph.project_policy.evidence.design_system_required(workspace, stack)[source]¶
Determine whether a design-system policy is required.
Required when ANY of these conditions holds:
stack.frameworksintersectsmarkers.UI_FRAMEWORK_SIGNALS, ORstack.secondary_languagesintersectsmarkers.CSS_LANGUAGE_SIGNALS, ORux_required(workspace, stack)returns True (a UX-substantial project also needs design-system coverage; the requirement is symmetric — UX implies both design-system AND ux, while design-system can stand on its own).
The third condition is computed by calling
ux_required()so the signal set is shared with the UX detector — there is no separate, hand-maintained signal list that could drift. The consultation list reports every signal file/framework that triggered the requirement so the orchestrator can produce a transparent report.- Parameters:
workspace (Workspace)
stack (ProjectStack)
- Return type:
tuple[bool, list[str]]
- ralph.project_policy.evidence.evidence_signature(workspace, stack)[source]¶
Return a stable signature covering the full readiness-evidence inventory.
The signature is a SHA-256 of the canonical-JSON serialization of the sorted
EvidenceEntrytuples plus the serializedProjectStack. Any change to any input — file edit, file deletion, framework added to the stack, language removed — produces a different signature, so a cached READY cannot stay valid across such a change.- Parameters:
workspace (Workspace)
stack (ProjectStack)
- Return type:
str
- ralph.project_policy.evidence.memory_required(workspace, stack)[source]¶
Determine whether a memory-usage policy is required.
Required only when:
any of
markers.MEMORY_SIGNAL_PATHSexists, ORany manifest file contains a memory-tooling dep substring from
markers.MEMORY_DEP_SIGNALS.
Absent both signals, no memory-usage policy is required.
- Parameters:
workspace (Workspace)
stack (ProjectStack)
- Return type:
tuple[bool, list[str]]
- ralph.project_policy.evidence.migration_candidates(workspace)[source]¶
Return every migration candidate the detector can recognize.
A candidate is a file that:
Is listed in
markers.MIGRATION_CANDIDATE_PATHSOR exists as a top-level*.mdundermarkers.MIGRATION_DOCS_GLOB_DIR(but NOT already inside the canonical policy directory), ANDContains a recognized heading phrase.
A candidate is RESOLVED when it carries the exact migrated marker pointing at a canonical file that exists, OR when its recognized headings have all been removed.
Unrelated mixed-purpose docs (no recognized heading) are NEVER candidates, so they never generate findings.
- Parameters:
workspace (Workspace)
- Return type:
list[MigrationCandidate]
- ralph.project_policy.evidence.performance_required(workspace, stack)[source]¶
Determine whether a performance policy is required.
Required only when:
any of
markers.PERF_SIGNAL_PATHSexists, ORany manifest file contains a benchmarking dep substring from
markers.PERF_DEP_SIGNALS.
Absent both signals, no performance policy is required (no invented targets, no empty file).
- Parameters:
workspace (Workspace)
stack (ProjectStack)
- Return type:
tuple[bool, list[str]]
- ralph.project_policy.evidence.readiness_evidence(workspace, stack)[source]¶
Return the ordered, deduplicated readiness-evidence inventory.
Every entry the validator reads (every policy file, every signal file, every migration candidate, the AGENTS.md and CLAUDE.md files, and the opt-out source path) is captured here as an
EvidenceEntry.The cache hashes the SHA-256 signature of this inventory so any edit OR deletion of any evidence file invalidates a cached READY.
- Parameters:
workspace (Workspace)
stack (ProjectStack)
- Return type:
list[EvidenceEntry]
- ralph.project_policy.evidence.required_languages(stack)[source]¶
Compute the set of code languages that must be covered by typecheck/lint.
Returns the primary language plus every secondary language that is not a non-code language (JSON, YAML, HTML, CSS family, …). Empty stack yields an empty set; the validator handles the empty case as “no languages required”.
- Parameters:
stack (ProjectStack)
- Return type:
set[str]
- ralph.project_policy.evidence.ux_required(workspace, stack)[source]¶
Determine whether a UX policy is required (stricter than design-system).
Required only when:
stack.frameworksintersectsmarkers.UX_APP_FRAMEWORKS, ORany manifest file contains a router-dep substring from
markers.UX_ROUTER_DEP_SIGNALS.
UX always implies design-system — a project using react-router needs both a design-system policy AND a UX policy.
- Parameters:
workspace (Workspace)
stack (ProjectStack)
- Return type:
tuple[bool, list[str]]
ralph.project_policy.agents_md¶
Opt-out detection and idempotent AGENTS.md/CLAUDE.md bootstrap.
All operations act on an injected Workspace.
There is no raw pathlib.Path I/O so the bootstrap is fully testable
with MemoryWorkspace.
Behavior summary:
is_opted_out()returns True ONLY when AGENTS.md exists AND its contents contain the byte-exactmarkers.OPT_OUT_MARKERsubstring. Missing AGENTS.md returns False; near-miss prose, extra whitespace, or any case change returns False.bootstrap()is idempotent:AGENTS.md missing -> create a deterministic minimal placeholder that contains the managed block, references
markers.CANONICAL_DIR, and instructs the first remediation agent to inspect the project, create and maintain the canonical files underdocs/ralph-workflow-policy/, migrate existing policies, configure executable gates, require agents to run those gates, and replace placeholder state. The placeholder MUST NOT claim any tool, command, or safeguard exists.AGENTS.md present WITHOUT the managed block -> append exactly one managed block after existing content, preserving all prior bytes byte-for-byte.
AGENTS.md present WITH the managed block -> no-op.
CLAUDE.md missing -> create a minimal file pointing Claude-compatible agents to AGENTS.md.
CLAUDE.md present -> preserve unrelated content; append a one-line AGENTS.md pointer only when no AGENTS.md reference already exists.
- ralph.project_policy.agents_md.bootstrap(workspace)[source]¶
Idempotently bootstrap AGENTS.md and CLAUDE.md.
Returns the list of workspace-relative paths that were created or modified. Repeated calls never duplicate the managed block or append a second pointer to CLAUDE.md.
- Parameters:
workspace (Workspace)
- Return type:
list[str]
- ralph.project_policy.agents_md.condense_placeholder_block(workspace)[source]¶
Replace an untouched bootstrap placeholder block with the concise form.
Called after the project reaches READY. The bootstrap placeholder is temporary scaffolding for the first remediation agent; once the policies exist and validate, only the short pointer block remains. A block whose body was rewritten (no placeholder sentinel), a missing AGENTS.md, or a malformed block is left untouched — user- and agent-authored content is never clobbered.
Returns the changed-file list (
[markers.AGENTS_MD]or[]).- Parameters:
workspace (Workspace)
- Return type:
list[str]
- ralph.project_policy.agents_md.has_significant_unmanaged_content(workspace)[source]¶
Return True iff AGENTS.md is marker-free and holds significant content.
Significant means at least one markdown heading line (starting with
markers.SIGNIFICANT_HEADING_PREFIX) or at leastmarkers.SIGNIFICANT_NONEMPTY_LINE_THRESHOLDnon-empty lines. Any Ralph marker (managed block or opt-out) means the repository has already been bootstrapped or already decided, so the interactive skip-inline-policy offer must not fire.- Parameters:
workspace (Workspace)
- Return type:
bool
- ralph.project_policy.agents_md.is_opted_out(workspace)[source]¶
Return True iff AGENTS.md exists and contains the byte-exact opt-out marker.
- Parameters:
workspace (Workspace)
- Return type:
bool
- ralph.project_policy.agents_md.write_opt_out(workspace)[source]¶
Append the byte-exact opt-out marker to AGENTS.md.
Persists the user’s “keep my existing policy” choice from the run preflight prompt. Prior bytes are preserved; a file already carrying the marker is left untouched. Returns the changed-file list (
[markers.AGENTS_MD]or[]), mirroringbootstrap().- Parameters:
workspace (Workspace)
- Return type:
list[str]
ralph.project_policy.validators¶
Deterministic project-policy validator.
This module is the ONLY authority on whether a project is policy-ready.
There is no AI, no network, no prose NLP — every check is a structural
pattern match against the canonical file content. Each failure emits a
PolicyFinding with a stable
requirement_id (one of the ID_* constants in
ralph.project_policy.markers), the affected path, the missing
evidence, and the required remediation outcome.
The validator is the consumer of the shared readiness-evidence inventory
in ralph.project_policy.evidence. The inventory enumerates every
file the validator needs to read; the cache hashes the same inventory so
the cache cannot drift from the validator.
A project is ready iff validate_readiness returns an empty list.
- ralph.project_policy.validators.validate_readiness(workspace, stack)[source]¶
Run every deterministic readiness check and return the findings list.
The function is pure: it does NOT mutate the workspace and it does NOT consult any AI. Every check reads through the workspace seam and emits zero or more findings. A project is ready iff the returned list is empty.
- Parameters:
workspace (Workspace)
stack (ProjectStack)
- Return type:
list[PolicyFinding]
ralph.project_policy.cache¶
Change-aware READY cache for the project-policy readiness preflight.
The cache is the FAST PATH for the orchestrator: when the same project
state was already validated READY on a prior preflight, the validator is
skipped on the current preflight. The cache hashes the
ralph.project_policy.evidence.evidence_signature() of the project —
the same signature the validator iterates — so any edit OR deletion of
any evidence file invalidates a cached READY automatically.
The cache is stored at markers.CACHE_REL_PATH (relative to the
workspace root) so it travels with the workspace but stays out of source
control. The cache only ever stores a READY status — remediation-required
and blocked states are never cached because they require an agent run.
- ralph.project_policy.cache.read_cached_ready(workspace, stack)[source]¶
Return True only when the cache holds a matching READY signature.
A cached READY whose signature does NOT match the current
ralph.project_policy.evidence.evidence_signature()is treated as stale: any edit OR deletion of an evidence file changes the signature, so a stale cache can never return a false READY.- Parameters:
workspace (Workspace)
stack (ProjectStack)
- Return type:
bool
- ralph.project_policy.cache.write_cache(workspace, stack, status)[source]¶
Persist the current evidence signature and status to the cache file.
- Parameters:
workspace (Workspace)
stack (ProjectStack)
status (ReadinessStatus)
- Return type:
None
ralph.project_policy.preflight¶
Deterministic project-policy-readiness preflight orchestrator.
The orchestrator sequences the five deterministic steps:
Opt-out check (byte-exact marker in AGENTS.md).
Fast-path cache lookup (change-aware READY cache).
AGENTS.md / CLAUDE.md bootstrap (idempotent).
Bundle-starter seeding for every missing core file (and for any required conditional file).
Deterministic validator (returns
[]on READY, list of findings otherwise).
The readiness decision is purely deterministic — no AI is consulted. The
remediation driver (see ralph.project_policy.remediation) is invoked
by the run-loop ONLY when this orchestrator returns REMEDIATION_REQUIRED.
- ralph.project_policy.preflight.REMEDIATION_PROMPT_REL_PATH: str = '.agent/tmp/policy_remediation_prompt.md'¶
Filename of the materialized remediation prompt. The orchestrator does NOT write this file;
ralph.project_policy.remediation.remediate()owns the prompt materialization.
- ralph.project_policy.preflight.run_policy_readiness_preflight(workspace, stack, *, emit=<function _noop_emit>)[source]¶
Run the deterministic preflight and return a
ReadinessResult.- Parameters:
workspace (Workspace) – Injected workspace seam.
stack (ProjectStack) – Detected project stack (from
ralph.language_detector.get_project_stack()).emit (EmitFn) – Display callback. The orchestrator emits short, one-line status messages here. Defaults to a no-op so the orchestrator is usable without a display.
- Returns:
ReadinessStatus.READY— every check passed.ReadinessStatus.SKIPPED— the byte-exact opt-out marker is present; no policy writes occurred.ReadinessStatus.REMEDIATION_REQUIRED— one or more findings; an agent must reconcile them. The findings, changed_files, migrated_sources, commands_run, and report_lines carry the actionable detail.
- Return type:
A
ReadinessResultwhosestatusis one of
ralph.project_policy.remediation¶
The policy-remediation PHASE: write and fix the canonical policy files.
This module owns ONE phase, not the loop. It invokes a remediation agent through
ralph.pipeline.effect_executor.execute_agent_effect() and ALWAYS re-runs
the deterministic validator afterward – an agent’s completion claim alone is
never sufficient evidence that the project is policy-ready. The loop, the
analysis phase, and the routing between them live in
ralph.project_policy.pipeline_driver and
ralph.project_policy.pipeline_graph.
The remediation phase runs OUT-OF-GRAPH: the shipped default pipeline
(ralph/policy/defaults/pipeline.toml) has no phase bound to the
policy_remediation drain, because policy readiness is a startup preflight
concern rather than a step of the development pipeline.
Handoff contract (NON-ARTIFACT):
The findings are handed to the agent through a materialized prompt at
.agent/tmp/policy_remediation_prompt.md written via the workspace seam.
This is a justified non-artifact handoff because:
.agent/artifacts/<type>.jsonwrites are forbidden and AST-audited byralph/testing/audit_artifact_submission_canonical_path.py.The remediation drain is deliberately DENIED
artifact.submitinralph/mcp/session_plan.py: it is judged by the deterministic validator that re-runs after it exits, so it has no verdict of its own to submit. (Its ANALYSIS counterpart is grantedartifact.submit– returning a routing decision is that phase’s entire purpose.)The prompt is the agent’s task definition, not a structured artifact.
- ralph.project_policy.remediation.PROMPT_TEMPLATE_NAME: str = 'policy_remediation.jinja'¶
Packaged Jinja template holding the remediation prompt body. Kept alongside the pipeline prompt templates so remediation follows the same convention.
- exception ralph.project_policy.remediation.RemediationInvocationError[source]¶
Bases:
RuntimeErrorThe remediation agent could not be launched at all.
Distinct from an agent that ran and failed: a launch crash is deterministic infrastructure breakage, so retrying it burns the whole loop budget in milliseconds and floods the display. The driver stops looping on the first occurrence – and then lets the RUN CONTINUE anyway. A broken agent subprocess is not a reason to refuse to do development work; see
ralph.project_policy.pipeline_driver.
- ralph.project_policy.remediation.render_blocked_report(findings)[source]¶
Render the report for a policy that could not be made ready.
Each line carries the stable requirement id, path, missing evidence, and required outcome so the operator can address each item without re-running the validator – and so the NEXT run picks up exactly where this one left off.
- Parameters:
findings (list[PolicyFinding])
- Return type:
list[str]
- ralph.project_policy.remediation.run_remediation_phase(workspace, stack, findings, *, invoke_agent, analysis_feedback=None, emit=<function _noop_emit>)[source]¶
Run ONE remediation phase and return the REVALIDATED findings.
Materializes the prompt (the open findings, plus whatever the analysis phase said came up short last round), invokes the agent chain, and then ALWAYS re-runs the deterministic validator. The agent’s own success/failure report is deliberately NOT the return value: an agent that swears it fixed everything and an agent that gives up are treated identically, because the only evidence that ever counted is what the validator says afterward.
- Parameters:
workspace (Workspace) – Injected workspace seam.
stack (ProjectStack) – Detected project stack.
findings (list[PolicyFinding]) – The findings currently open against the project.
invoke_agent (InvokePolicyAgent) – Callable that runs the remediation agent chain with the materialized prompt. Injected so tests can substitute a fake drain.
analysis_feedback (AnalysisDecision | None) – The previous analysis decision, when looping back.
emit (EmitFn) – Display callback.
- Returns:
The findings still open AFTER the agent ran – empty when the project now passes the deterministic validator.
- Raises:
RemediationInvocationError – When the agent could not be launched at all. Propagated to the driver, which stops looping but still lets the run continue.
- Return type:
list[PolicyFinding]
ralph.project_policy.analysis¶
The policy-remediation ANALYSIS phase: review what remediation wrote.
The deterministic validator (ralph.project_policy.validators) can only
check FORM – headings present, no placeholder tokens, the first token of each
RALPH-COMMAND on the approved-tool allowlist. It cannot tell whether a
RALPH-FACT is TRUE, whether make verify is a target that actually exists,
or whether a gate script calls a tool nobody installed. A project can reach a
structurally perfect policy that is factually a lie.
This phase closes that gap with an agent. It is deliberately weak: its drain class
(analysis) grants artifact.submit and process.exec_bounded but NO
workspace-write MCP tool, so it can probe the declared gates and report a decision,
and it has no sanctioned way to edit the work it is reviewing.
That is a strong hint, not a proof. process.exec_bounded runs compound shell
commands, and a shell can redirect – so “cannot write” is not enforceable at the
tool surface. The property that actually holds is enforced elsewhere: the driver
re-runs the deterministic validator before caching READY
(ralph.project_policy.pipeline_driver._finish()), so an analysis agent cannot
approve its way past a failing validator no matter what it touches.
Decision handoff¶
ralph.pipeline.effect_executor.execute_agent_effect() returns a bare
PipelineEvent enum with no payload, so the decision cannot ride back on the
return value. It is read from the Markdown artifact the agent submits through MCP –
exactly how ralph.phases.analysis reads the in-graph analysis decisions.
The stale-artifact rule is the sharpest edge in the whole pipeline: the decision
file is DELETED before every invocation, so a completed left behind by a
previous iteration can never be re-read as this iteration’s decision. Without
that delete, an analysis agent that crashed on iteration 2 would silently inherit
iteration 1’s approval.
- ralph.project_policy.analysis.ANALYSIS_ARTIFACT_TYPE: str = 'policy_remediation_analysis_decision'¶
The artifact the analysis agent submits, as declared in
ralph/policy/defaults/artifacts.toml.
- ralph.project_policy.analysis.ANALYSIS_PROMPT_REL_PATH: str = '.agent/tmp/policy_remediation_analysis_prompt.md'¶
Where the analysis agent’s task definition is materialized. Symmetric with the remediation prompt; see
ralph.project_policy.remediation.
- class ralph.project_policy.analysis.AnalysisDecision(status, summary='', what_came_up_short=<factory>, how_to_fix=<factory>)[source]¶
Bases:
objectOne
policy_remediation_analysis_decisionartifact, parsed.- Parameters:
status (str)
summary (str)
what_came_up_short (list[str])
how_to_fix (list[str])
- status¶
One of the decision vocabulary (
completed,request_changes,failed). A missing, corrupt, or out-of-vocabulary artifact is normalized tofailedbyralph.project_policy.analysis.read_analysis_decision()– never tocompleted.- Type:
str
- summary¶
The agent’s one-line account of what the review found.
- Type:
str
- what_came_up_short¶
What is wrong with the policy the remediation agent wrote. Empty for a
completeddecision.- Type:
list[str]
- how_to_fix¶
Concrete steps to resolve each item. Empty for a
completeddecision.- Type:
list[str]
- class ralph.project_policy.analysis.InvokePolicyAgent(*args, **kwargs)[source]¶
Bases:
ProtocolCallable contract for invoking one phase’s agent chain.
The production implementation (
ralph.project_policy.cli_integration) builds anInvokeAgentEffectfor the phase’s drain and runs it throughralph.pipeline.effect_executor.execute_agent_effect(). Returns True when the agent reported success.
- ralph.project_policy.analysis.clear_stale_decision(workspace)[source]¶
Delete any decision artifact left behind by a previous iteration.
MUST run before every analysis invocation. Without it, an agent that fails to submit leaves the previous iteration’s artifact in place, and the driver would read that stale verdict as the current one – turning a crashed review into an inherited approval.
- Parameters:
workspace (Workspace)
- Return type:
None
- ralph.project_policy.analysis.read_analysis_decision(workspace)[source]¶
Read the analysis decision back from the markdown artifact.
Fails closed in every degenerate case – artifact absent, unreadable, malformed, wrong type, or carrying a status outside the decision vocabulary – by returning
failed, which routes back to remediation and consumes one iteration of the loop budget. The one status this function will never invent iscompleted.- Parameters:
workspace (Workspace)
- Return type:
- ralph.project_policy.analysis.run_analysis_phase(workspace, *, invoke_agent, emit=<function _noop_emit>)[source]¶
Run one analysis phase and return its decision.
Clears any stale decision, materializes the prompt, invokes the agent chain, and reads the decision back from the artifact. An agent that reports failure, or that reports success without submitting a usable artifact, yields
failed– which routes back to remediation rather than forward to done.- Parameters:
workspace (Workspace)
invoke_agent (InvokePolicyAgent)
emit (EmitFn)
- Return type:
ralph.project_policy.analysis_decision¶
The decision the policy-remediation analysis agent returns.
Carries both the routing decision and the concrete feedback that becomes the NEXT remediation prompt’s input, which is what closes the loop: analysis does not merely say “try again”, it says exactly what came up short and how to fix it.
- class ralph.project_policy.analysis_decision.AnalysisDecision(status, summary='', what_came_up_short=<factory>, how_to_fix=<factory>)[source]¶
Bases:
objectOne
policy_remediation_analysis_decisionartifact, parsed.- Parameters:
status (str)
summary (str)
what_came_up_short (list[str])
how_to_fix (list[str])
- status¶
One of the decision vocabulary (
completed,request_changes,failed). A missing, corrupt, or out-of-vocabulary artifact is normalized tofailedbyralph.project_policy.analysis.read_analysis_decision()– never tocompleted.- Type:
str
- summary¶
The agent’s one-line account of what the review found.
- Type:
str
- what_came_up_short¶
What is wrong with the policy the remediation agent wrote. Empty for a
completeddecision.- Type:
list[str]
- how_to_fix¶
Concrete steps to resolve each item. Empty for a
completeddecision.- Type:
list[str]
ralph.project_policy.pipeline_route¶
One routing edge in the hardcoded policy pipeline.
Mirrors ralph.policy.models.PhaseDecisionRoute — the in-graph pipeline’s
decision route — so the out-of-graph policy pipeline expresses routing with the
same vocabulary the real pipeline uses. It is deliberately a plain frozen
dataclass rather than the Pydantic model: the policy pipeline is hardcoded, so
there is no TOML to validate and no need to pay for validation of a table that
cannot change at runtime.
- class ralph.project_policy.pipeline_route.PolicyRoute(target, reset_loop=False)[source]¶
Bases:
objectWhere a decision sends the policy pipeline, and what it does to the loop.
- Parameters:
target (str)
reset_loop (bool)
- target¶
The phase this decision routes to. May be the terminal phase.
- Type:
str
- reset_loop¶
True when this route is a FORWARD exit from the analysis loop, which zeroes the iteration counter (matching
reset_loop = truein the in-graph pipeline’s decision tables). False when the route is a loopback, which consumes one iteration of the analysis budget.- Type:
bool
ralph.project_policy.pipeline_phase¶
One phase definition in the hardcoded policy pipeline.
Mirrors ralph.policy.models.PhaseDefinition — drain, role, transitions,
decision table — restricted to the fields the two-phase policy pipeline actually
needs. See ralph.project_policy.pipeline_graph for the graph itself.
- class ralph.project_policy.pipeline_phase.PolicyPhaseDef(drain, role, on_success=None, decisions=<factory>)[source]¶
Bases:
objectA phase of the policy pipeline.
- Parameters:
drain (str)
role (str)
on_success (str | None)
decisions (dict[str, PolicyRoute])
- drain¶
The agent drain this phase invokes. Bound to a chain in
ralph/policy/defaults/agents.toml; both policy drains are out-of-graph (bound to no pipeline phase).- Type:
str
- role¶
"execution"or"analysis", matching the in-graphPhaseRolevocabulary. Only an analysis phase carries a decision table and consumes the loop budget.- Type:
str
- on_success¶
The phase reached when this phase completes.
Nonefor a phase that routes solely through its decision table.- Type:
str | None
- decisions¶
Decision string -> route. Populated only for an analysis phase; the keys are exactly the
decision_vocabularyof the phase’s artifact contract inartifacts.toml.- Type:
ralph.project_policy.pipeline_graph¶
The hardcoded policy pipeline: a frozen two-phase graph with an analysis loop.
This module is the policy pipeline’s single source of truth for ROUTING. It is
pure: no I/O, no agents, no workspace. ralph.project_policy.pipeline_driver
executes what this module decides.
The graph:
policy_remediation --on_success--> policy_remediation_analysis
|
completed | -> done (reset_loop)
request_changes | -> remediation (loop += 1)
failed | -> remediation (loop += 1)
Two rules bound it, and both are load-bearing:
The deterministic validator is a hard gate. done requires BOTH an empty
finding list from validators.validate_readiness AND a completed decision
from the analysis agent. While findings remain, the analysis phase is never
consulted at all — reviewing the QUALITY of a policy file that is still
structurally invalid is wasted work, and an analysis agent must never be in a
position where its completed could launder a failing validator.
The analysis budget never blocks the run. When the loop budget is spent, the
driver runs one final remediation and then walks FORWARD to the terminal phase
rather than failing – the same exhausted-analysis bypass that
ralph.pipeline.handoffs.resolve_exhausted_analysis_bypass() applies to the
in-graph analysis phases. Project-policy readiness is best-effort; it is NOT a
precondition for doing development work. A run whose policy could not be made
ready proceeds to planning anyway, loudly, with its findings printed.
With the default cap of 3 the full loop is R A R A R A R: four remediation
invocations, three analysis invocations, and the last remediation is the final
one before the run proceeds.
- ralph.project_policy.pipeline_graph.DECISION_COMPLETED: Final[str] = 'completed'¶
Decision the analysis agent returns to accept the policy as it stands.
- ralph.project_policy.pipeline_graph.DECISION_REQUEST_CHANGES: Final[str] = 'request_changes'¶
Decision the analysis agent returns to send concrete findings back to remediation. The driver treats an unusable/absent/unparseable decision as
DECISION_FAILED, never asDECISION_COMPLETED(fail closed).
- ralph.project_policy.pipeline_graph.DEFAULT_ANALYSIS_CAP: Final[int] = 3¶
Analysis loop budget. Three review rounds is enough to converge a policy file or to prove it will not converge; a fourth is a display flood, not a strategy.
- ralph.project_policy.pipeline_graph.DEFAULT_MAX_REMEDIATION_ATTEMPTS: Final[int] = 4¶
R A R A R A Rhas one more remediation invocation than analysis rounds.
- ralph.project_policy.pipeline_graph.PHASE_ANALYSIS: Final[str] = 'policy_remediation_analysis'¶
reviews what remediation wrote and routes back or forward.
Its
analysisdrain class grants NO workspace-write MCP tool – no write, no edit, no delete. It is not, however, hermetically read-only: the class does grantprocess.exec_boundedso the agent can PROBE the gates the policy declares, and a shell command can redirect. The guarantee that a review cannot launder its own approval therefore rests on the deterministic re-validation inralph.project_policy.pipeline_driver._finish(), NOT on the tool surface.- Type:
The analysis phase
- ralph.project_policy.pipeline_graph.PHASE_REMEDIATION: Final[str] = 'policy_remediation'¶
writes and fixes the canonical policy files.
- Type:
The remediation phase
- ralph.project_policy.pipeline_graph.POLICY_PIPELINE: Final[Mapping[str, PolicyPhaseDef]] = mappingproxy({'policy_remediation': PolicyPhaseDef(drain='policy_remediation', role='execution', on_success='policy_remediation_analysis', decisions={}), 'policy_remediation_analysis': PolicyPhaseDef(drain='policy_remediation_analysis', role='analysis', on_success=None, decisions={'completed': PolicyRoute(target='done', reset_loop=True), 'request_changes': PolicyRoute(target='policy_remediation', reset_loop=False), 'failed': PolicyRoute(target='policy_remediation', reset_loop=False)})})¶
The phase graph, read-only. Exposed for tests and for the driver.
- ralph.project_policy.pipeline_graph.TERMINAL_DONE: Final[str] = 'done'¶
The terminal phase. Reaching it always means “stop the policy pipeline and let the run continue” – never “abort the run”.
- ralph.project_policy.pipeline_graph.analysis_budget_spent(iteration, cap)[source]¶
True when the analysis loop budget is exhausted.
At that point the driver applies the exhausted-analysis bypass: it walks forward to
TERMINAL_DONEinstead of entering the analysis phase again. MirrorsAnalysisLoopCounter.should_skip_reentry(ralph.pipeline.progress).- Parameters:
iteration (int)
cap (int)
- Return type:
bool
- ralph.project_policy.pipeline_graph.phase_definition(phase)[source]¶
Return the definition of
phase.- Raises:
KeyError – When
phaseis not a phase of the policy pipeline. A typo in a phase name is a programming error, not a routing outcome, so it fails loudly rather than defaulting to some phase.- Parameters:
phase (str)
- Return type:
- ralph.project_policy.pipeline_graph.resolve_decision(decision)[source]¶
Map an analysis decision string onto its route.
An unrecognized decision routes back to remediation exactly as
faileddoes. The analysis agent is a model, and a model can emit anything; the one outcome it must never be able to reach by accident or by fabrication isdone. Fail closed.- Parameters:
decision (str)
- Return type:
ralph.project_policy.pipeline_driver¶
Execute the hardcoded policy pipeline.
Drives the two-phase graph defined in ralph.project_policy.pipeline_graph:
remediation writes the policy, the deterministic validator gates it, analysis
reviews it, and the decision routes back to remediation or forward to done.
THE INVARIANT THIS MODULE EXISTS TO PROTECT¶
Policy readiness NEVER blocks the development run. There is no code path in this module that aborts a run. Whatever happens – the analysis budget runs out with findings still open, the agent chain has no configured agent, the agent subprocess cannot even be launched – the driver reports it, declines to cache a false READY, and returns. The caller proceeds to planning.
That is a deliberate inversion of the previous behavior, where a BLOCKED policy
returned exit code 2 and killed the run. Policy is documentation about the
project; a project with imperfect documentation is still a project you can do
work on. Coupling the two meant a stale RALPH-LANG block for a language
nobody uses could stop all development. Never again. The only place a non-zero
exit survives is the --redo-policy-only / --run-policy-agents-only flags,
which have no development run to proceed to, so their exit code is the only
signal they can give.
The loop, with the default cap of 3:
R A R A R A R -> done -> planning
^ ^
entry the analysis budget is spent, so the driver runs one
FINAL remediation (applying the last review's feedback)
and then walks forward. It does not fail.
- ralph.project_policy.pipeline_driver.run_policy_pipeline(workspace, stack, findings, *, invoke_agent, entry_phase='policy_remediation', analysis_cap=3, emit=<function _noop_emit>, on_remediation_attempt=<function _noop_attempt_callback>)[source]¶
Run the policy pipeline to a terminal state. Never blocks the run.
- Parameters:
workspace (Workspace) – Injected workspace seam.
stack (ProjectStack) – Detected project stack.
findings (list[PolicyFinding]) – The findings currently open against the project. May be empty when
entry_phaseis the analysis phase (--run-policy-agentsaudits an already-valid policy).invoke_agent (InvokePolicyAgent) – Callable that runs one phase’s agent chain. Injected so tests can substitute a fake drain.
entry_phase (str) – Where to enter the graph.
policy_remediationfor the normal path;policy_remediation_analysisfor--run-policy-agents, which reviews the EXISTING policy in place and only rewrites it if analysis routesrequest_changes.analysis_cap (int) – The analysis loop budget.
emit (EmitFn) – Display callback.
on_remediation_attempt (AttemptCallback) – Callback invoked BEFORE each remediation attempt with the 1-indexed attempt number so the persistent status bar can surface the live
Remediation N/Maxlabel instead of a hardcodedDev 1/Nplaceholder. Defaults to a no-op so existing callers/tests are unaffected.
- Returns:
READY when the deterministic validator passes AND the analysis agent returned
completed; the READY cache is written. BLOCKED otherwise – which the caller reports and then proceeds past.- Return type:
ralph.project_policy.policy_mode¶
How a run was asked to treat project policy.
Four CLI flags select a non-default mode, in two pairs. The _ONLY suffix
means “exit after the policy pipeline instead of continuing into the development
run”; the REDO pair wipes the existing policy first, the RUN_AGENTS pair
reviews it in place.
Mode |
Wipes? |
Enters at |
After |
|---|---|---|---|
|
no |
remediation (if findings) |
continue |
|
yes |
remediation |
continue |
|
yes |
remediation |
exit |
|
no |
analysis |
continue |
|
no |
analysis |
exit |
Every explicit mode bypasses the READY cache (a mode that no-ops on a ready
project would be useless) and overrides the AGENTS.md opt-out marker (explicit
CLI intent beats a persisted marker). Only the REDO pair strips the opt-out
marker from the file; RUN_AGENTS merely declines to honor it for this run.
- class ralph.project_policy.policy_mode.PolicyMode(*values)[source]¶
Bases:
StrEnumThe policy behavior selected for this run.
- entry_phase()[source]¶
The phase the policy pipeline enters at.
The
RUN_AGENTSpair enters at ANALYSIS: it reviews the EXISTING policy in place (are the facts still true, do the gates still resolve, do the scripts still obey the gate-script policy) and only routes back to remediation if the review finds something wrong. Nothing is overwritten unless analysis proves it should be.- Return type:
str
- exits_after()[source]¶
True when the run stops after the policy pipeline.
This is the ONLY case in which a policy outcome can produce a non-zero exit code: an
_ONLYinvocation has no development run to proceed to, so its exit code is the only signal it can give. A normal run NEVER exits non-zero because of policy.- Return type:
bool
ralph.project_policy.reset¶
Wipe the project’s policy state so it can be regenerated from scratch.
Backs --redo-policy and --redo-policy-only.
WHY A FULL WIPE, AND NOT JUST A CACHE INVALIDATION¶
Deleting .agent/tmp/policy_readiness_cache.json looks like it should force a
regeneration. It does not. ralph.project_policy.starters.seed_starter_into()
only seeds a starter when the file is absent, so every existing policy file
survives the cache drop – and if those files are structurally complete, the
deterministic validator passes them straight back to READY. The “redo” would be a
silent no-op, which is worse than no flag at all: the user believes their policy
was regenerated when nothing happened.
So the canonical directory is deleted outright. Everything else here exists to leave no dangling reference to it.
All I/O goes through the Workspace seam, so
the reset is exercised against MemoryWorkspace in tests with no real
filesystem.
- ralph.project_policy.reset.reset_policy_state(workspace)[source]¶
Delete every trace of the project’s generated policy. Returns changed paths.
Six steps:
Delete the canonical policy directory outright (see the module docstring for why a cache drop is not enough).
Delete the readiness cache.
Delete the materialized remediation and analysis prompts.
Delete any stale analysis decision artifact.
Strip the managed block and the opt-out marker from AGENTS.md / CLAUDE.md, preserving all unmanaged content.
Strip
migrated ->markers from every migration-candidate document.
Idempotent: a second call on an already-reset workspace changes nothing and returns an empty list.
- Parameters:
workspace (Workspace)
- Return type:
list[str]
ralph.project_policy.cli_integration¶
Run-pipeline integration helpers for the project-policy-readiness preflight.
The preflight lives in ralph.project_policy; the run-pipeline CLI
in ralph.cli.commands.run only needs the orchestrator entry point
plus the dependency-injection helpers (workspace factory, emit factory,
remediation-agent factory). Moving the helpers here keeps the CLI module
under the 1000-line repository cap without dragging the orchestrator’s
helpers into the public package surface.
Every helper here is independent and small; the orchestrator entry point
run_project_policy_readiness() stitches them together so the CLI
call site reads as one line.
- class ralph.project_policy.cli_integration.PolicyMode(*values)[source]¶
Bases:
StrEnumThe policy behavior selected for this run.
- entry_phase()[source]¶
The phase the policy pipeline enters at.
The
RUN_AGENTSpair enters at ANALYSIS: it reviews the EXISTING policy in place (are the facts still true, do the gates still resolve, do the scripts still obey the gate-script policy) and only routes back to remediation if the review finds something wrong. Nothing is overwritten unless analysis proves it should be.- Return type:
str
- exits_after()[source]¶
True when the run stops after the policy pipeline.
This is the ONLY case in which a policy outcome can produce a non-zero exit code: an
_ONLYinvocation has no development run to proceed to, so its exit code is the only signal it can give. A normal run NEVER exits non-zero because of policy.- Return type:
bool
- ralph.project_policy.cli_integration.run_project_policy_readiness(*, load_result, display_context, mode=PolicyMode.NORMAL, workspace_factory=None, emit_factory=None, invoke_remediation_agent_factory=None, select_factory=None, is_tty=None)[source]¶
Run the project-policy preflight at run_pipeline startup. NEVER blocks.
THIS FUNCTION IS A FAULT BOUNDARY. It catches every exception the policy subsystem can raise – including bugs: an
AttributeErrorfrom a bad refactor, aKeyErrorfrom a malformed policy file, anOSErrorfrom a broken filesystem, a template that fails to render. Whatever happens, the development run proceeds as if the policy pipeline had never existed.That is not defensive paranoia, it is the requirement. Project policy is documentation ABOUT the project; it is not a precondition for working on the project. A crash in the documentation subsystem must never cost a user their development run. The catch is deliberately broad (
Exception) and deliberately not broader (BaseExceptionis NOT caught, so Ctrl-C andSystemExitstill work).The one exception to “never blocks” is an
_ONLYmode, which has no development run to proceed to and so reports failure through its exit code.Tests can inject
workspace_factory,emit_factory,invoke_remediation_agent_factory,select_factory, andis_ttyto exercise the preflight without real filesystem I/O, agent invocation, or a real terminal.- Parameters:
load_result (_LoadResult)
display_context (DisplayContext)
mode (PolicyMode)
workspace_factory (Callable[[], Workspace] | None)
emit_factory (Callable[[str], None] | None)
invoke_remediation_agent_factory (Callable[[Workspace], InvokePolicyAgent] | None)
select_factory (_prompt_ui.SelectFn | None)
is_tty (Callable[[], bool] | None)
- Return type:
int
ralph.project_policy.status_bar¶
Status-bar push helper for the project-policy-readiness pipeline.
Lives in its own module so :mod:stays under the 1000-line repository cap while still letting the remediation phase surface a live Remediation N/Max label in the persistent footer (the operator-grade fix the wt-028-display consolidation introduced for policy-remediation). The helper is intentionally defensive: any display failure is swallowed – presentation must NEVER block remediation.
- ralph.project_policy.status_bar.push_remediation_status_bar(display, workspace_scope, max_attempts, *, attempt=1, elapsed_seconds=None, agent_name=None, run_started_monotonic=None)[source]¶
Seed the persistent status bar for the remediation phase.
Mirrors the run loop phase push so the footer shows the working directory and the active phase during remediation instead of nothing. attempt is the 1-indexed live attempt (1 on the first try, 2 on the first re-try, etc.) so the bar surfaces real progress instead of a hardcoded Dev 1/N placeholder. Defensive: any display failure is swallowed – presentation must never block remediation.
- Parameters:
display (object)
workspace_scope (WorkspaceScope)
max_attempts (int)
attempt (int)
elapsed_seconds (float | None)
agent_name (str | None)
run_started_monotonic (float | None)
- Return type:
None
ralph.project_policy.starters¶
Bundled starter policy loader.
Mirrors the resource-access pattern used by ralph.skills._content: the
starter .md files are bundled inside the installed package and read via
importlib.resources.files(__package__). The hatch wheel and sdist
include entries (added to pyproject.toml) ship the same files
with the wheel so the starter loader works in any environment that has
the wheel installed.
Public API:
iter_starter_names()— return every bundled starter name (20 entries: 10 core + 10 conditional).read_starter()— return the content of one bundled starter.seed_starter_into()— copy a bundled starter into the canonical policy directory via the workspace seam, but ONLY when the target is absent (never overwrite an existing project-customized file).
Every starter ships with a template banner, REPLACE-ME guidance comments, and placeholder tokens, so a freshly-seeded starter fails the validator until the remediation agent resolves every one of them. Completion is the absence of unresolved markers — there is no completion marker to add.
Prompts¶
ralph.prompts¶
Prompt capability variables, flag sets, and rendering support.
This package exposes the capability types and variable builders used by phase
handlers when they materialize agent-facing Jinja templates from
ralph/prompts/templates/.
Main entry points:
capability_template_variables(capabilities, flags)— builds the template variable dict for a givenCapabilitySetandPolicyFlagSet. Used when rendering prompts that reference capability gates.capability_template_variables_from_session(session)— convenience wrapper that extracts capabilities and flags from a liveSessionCapabilitiesobject.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.
For template rendering, context building, and payload materialization,
see ralph.prompts.materialize and ralph.prompts.template_engine.
- class ralph.prompts.CapabilitySet(values=None)[source]¶
Bases:
objectLightweight set of Ralph capabilities.
- Parameters:
values (Iterable[RalphCapability] | None)
- class ralph.prompts.PolicyFlag(*values)[source]¶
Bases:
StrEnumPolicy flags that may modify prompt rendering.
- class ralph.prompts.PolicyFlagSet(values=None)[source]¶
Bases:
objectSet of Ralph policy flags.
- Parameters:
values (Iterable[PolicyFlag] | None)
- class ralph.prompts.SessionCapabilities(capabilities, policy_flags, tool_name_prefix='')[source]¶
Bases:
objectHelper bundling capabilities and policy flags for prompt rendering.
- Parameters:
capabilities (CapabilitySet)
policy_flags (PolicyFlagSet)
tool_name_prefix (str)
- 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:
drain (SessionDrain)
tool_name_prefix (str)
- Return type:
- 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:
- classmethod new(capabilities, policy_flags, *, tool_name_prefix='')[source]¶
Build a SessionCapabilities from explicit capability and policy-flag sets.
- Parameters:
capabilities (CapabilitySet)
policy_flags (PolicyFlagSet)
tool_name_prefix (str)
- Return type:
- 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:
capabilities (CapabilitySet)
policy_flags (PolicyFlagSet)
tool_name_prefix (str)
- 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:
session (AgentSession)
tool_name_prefix (str)
- 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:
objectConfiguration 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_md_artifact',), payload_config=None)[source]¶
Return the commit message prompt for the provided diff.
- Parameters:
diff (str)
template_registry (TemplateRegistry | None)
partials (Mapping[str, str] | None)
submit_artifact_tool_names (Sequence[str])
payload_config (CommitPromptPayloadConfig | None)
- 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:
diff (str)
submit_artifact_tool_name (str)
payload_config (CommitPromptPayloadConfig | None)
- 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:
workspace (Workspace)
phase (str)
- 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='', product_criteria_path='', payload_root='', prompt_name_prefix='development', last_retry_error='', prior_result_status='', prior_result_summary='', prior_result_next_steps='', prior_result_continuation='', skills_inline_content='', has_docs_mcp=False, work_unit_id='', work_unit_description='', work_unit_directories='', worker_namespace='', is_continuation=False)[source]¶
Bases:
objectInputs 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)
product_criteria_path (str)
payload_root (str)
prompt_name_prefix (str)
last_retry_error (str)
prior_result_status (str)
prior_result_summary (str)
prior_result_next_steps (str)
prior_result_continuation (str)
skills_inline_content (str)
has_docs_mcp (bool)
work_unit_id (str)
work_unit_description (str)
work_unit_directories (str)
worker_namespace (str)
is_continuation (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='', product_criteria_path='', payload_root='', last_retry_error='', skills_inline_content='', has_docs_mcp=False)[source]¶
Bases:
objectInputs 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)
product_criteria_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:
context (TemplateContext)
inputs (DeveloperPromptInputs)
workspace (Workspace)
session_caps (SessionCapabilities)
template_name (str)
- 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:
context (TemplateContext)
inputs (PlanningPromptInputs)
workspace (Workspace)
session_caps (SessionCapabilities)
template_name (str)
- Return type:
str
ralph.prompts.materialize¶
Policy-selected prompt materialization.
- exception ralph.prompts.materialize.MissingPlanHandoffError[source]¶
Bases:
ValueErrorRaised 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:
objectRequired inputs for prompt materialization: the phase, workspace, and policy bindings.
- Parameters:
phase (str)
workspace (Workspace)
pipeline_policy (PipelinePolicy)
session_caps (SessionCapabilities)
workspace_root (Path)
- 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:
objectOptional 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:
workspace (Workspace)
phase (str)
- 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:
context (PromptPhaseContext | None)
options (PromptPhaseOptions | None)
kwargs (object)
- 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 isralph), so its prompts use theralph_prefix — matching theralph_*permission Ralph already grants in the OpenCode config. Claude AND Codex usemcp__ralph__.- Parameters:
transport (AgentTransport | None)
- Return type:
str
ralph.prompts.materialize_support¶
Shared helper utilities for prompt materialization.
- ralph.prompts.materialize_support.merged_variables(base, session_caps)[source]¶
Merge base template variables with session capability variables.
- Parameters:
base (dict[str, str])
session_caps (ralph.prompts.types.SessionCapabilities)
- Return type:
dict[str, str]
- ralph.prompts.materialize_support.persist_product_criteria(workspace_root, prompt_content, *, worker_namespace=None, backend=<ralph.mcp.artifacts._path_file_backend.PathFileBackend object>)[source]¶
Persist the active prompt content to the workspace prompt file.
- Parameters:
workspace_root (Path)
prompt_content (str | None)
worker_namespace (Path | None)
backend (FileBackend)
- Return type:
str
- ralph.prompts.materialize_support.phase_payload_variables(*, phase, workspace_root, values, worker_namespace=None, backend=<ralph.mcp.artifacts._path_file_backend.PathFileBackend object>)[source]¶
Build prompt payload variables, writing oversized values to disk.
The injected
backendcontrols bothmkdirand the physical write so a byte-identical re-emit of an oversized prompt payload does not advance the file’s mtime or generate an additional fseventsd notification. The default backend is the real-Path backend; tests inject an in-memory counting backend to verify the idempotent skip. The post-condition “the destination file contains the expected payload content” always holds: any read uncertainty or content mismatch falls through to a real write.- Parameters:
phase (str)
workspace_root (Path)
values (dict[str, str])
worker_namespace (Path | None)
backend (FileBackend)
- 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, *, backend=<ralph.mcp.artifacts._path_file_backend.PathFileBackend object>)[source]¶
Write payload content to a directory and return the absolute path.
Routes the physical write through
write_text_if_changed()so a byte-identical re-emit of an oversized prompt payload does not advance the destination’s mtime or generate an additional fseventsd notification. The post-condition “file containssanitize_surrogates(content)” always holds: any read uncertainty or content mismatch falls through to a real write.- Parameters:
output_dir (Path) – Directory under which the payload file is created.
relative_path (str) – Path whose final segment names the payload file.
content (str) – Payload text to persist (sanitized via
sanitize_surrogates()before being written).backend (FileBackend) – Filesystem backend used for
mkdirand the idempotent text write. Defaults to the real-Path backend; tests inject an in-memory counting backend to verify the byte-identical re-emit skip.
- 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.master_prompt¶
Master prompt materialization for supported agent transports.
- ralph.prompts.master_prompt.build_master_prompt(*, planning_style, master_prompt_path, product_criteria_path, current_plan_path=None)[source]¶
Build the master prompt text that points the agent at durable task context files.
- Parameters:
planning_style (bool)
master_prompt_path (str)
product_criteria_path (str)
current_plan_path (str | None)
- Return type:
str
- ralph.prompts.master_prompt.materialize_master_prompt(*, workspace_root, name, default_product_criteria=None, worker_namespace=None, planning_style=False, backend=<ralph.mcp.artifacts._path_file_backend.PathFileBackend object>)[source]¶
Write a master prompt file for the named agent and return its path.
The injected
backendcontrols bothmkdirand the physical text writes so a byte-identical re-emit of the current or system prompt does not advance the file’s mtime or generate an additional fseventsd notification. The default backend is the real-Path backend; tests inject an in-memory counting backend to verify the idempotent skip. The post-condition “file contains the rendered prompt” always holds: any read uncertainty or content mismatch falls through to a real write.- Parameters:
workspace_root (Path)
name (str)
default_product_criteria (str | None)
worker_namespace (Path | None)
planning_style (bool)
backend (FileBackend)
- Return type:
str
ralph.prompts.template_context¶
Template registry/ context for prompt generation.
- class ralph.prompts.template_context.TemplateContext(registry, partials)[source]¶
Bases:
objectBundled registry and partials for prompt template rendering.
- Parameters:
registry (TemplateRegistry)
partials (Mapping[str, str])
ralph.prompts.template_engine¶
Minimal rendering engine for RFC-009 prompt templates.
- class ralph.prompts.template_engine.TemplateRenderer(partials)[source]¶
Bases:
objectReusable Jinja renderer for one immutable partial-template set.
Imports are normalized to consume the current render context, so cached macro templates never retain variables from an earlier render. The compiled-main cache is LRU-bounded so a long-running process cannot accumulate arbitrary prompt sources.
- Parameters:
partials (Mapping[str, str])
ralph.prompts.template_rendering_error¶
Public prompt-template rendering failure.
ralph.prompts.template_registry¶
Simple registry for prompt templates.
- exception ralph.prompts.template_registry.TemplateNotFoundError(template_name)[source]¶
Bases:
ExceptionRaised 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:
objectRegistry that holds prompt templates by name.
- Parameters:
template_dirs (tuple[Path, ...])
_read_text (Callable[[Path], str] | 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.
Directories under
packaged_template_root()are served from the process-wide snapshot so every render in a process sees the same packaged template text; workspace override directories are re-read on each call because they are expected to change while the pipeline runs.- Parameters:
template_dirs (Iterable[Path])
- Return type:
dict[str, str]
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:
StrEnumInternal Ralph capability vocabulary.
- class ralph.prompts.types.SessionCapabilities(capabilities, policy_flags, tool_name_prefix='')[source]¶
Bases:
objectBundle of capability/policy sets plus transport-specific prompt decoration.
- Parameters:
capabilities (CapabilitySet)
policy_flags (PolicyFlagSet)
tool_name_prefix (str)
- 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:
capabilities (CapabilitySet)
policy_flags (PolicyFlagSet)
tool_name_prefix (str)
- 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:
capabilities (CapabilitySet)
policy_flags (PolicyFlagSet)
- 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]
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 fromRALPH_TEST_TIMEOUT_SECONDSandRALPH_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:
objectIn-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:
objectSeeded 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.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, and tests/). Uses AST analysis to find:
Direct writes to
.agent/receipts/,.agent/completion_seen_*.json,.agent/artifacts/<canonical-type>.md(and its obsolete.jsonform), or.agent/tmp/<canonical-type>.json(viawrite_text,write_bytes,open(...), or equivalent file-copy helpers).Calls to
write_artifact_receipt/delete_artifact_receiptoutside 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:
objectA 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_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/, andralph/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:120and:162— composition root that readsMCP_*_ENV/UPSTREAM_MCP_TOOL_CATALOG_ENVto wire the factory.ralph/mcp/websearch/secrets.py:17—os.getenvis used as a callableEnvGetterparameter, 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.pyandralph/mcp/server/_fallback_http_handler.py(the modules named in PROMPT.md proof obligation B as the session factory boundary) and flags ANYcast(...)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:
objectA 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_cast_policy¶
Cast policy audit - detects forbidden typing.cast usage.
The “Type assertions and casts” section of
docs/ralph-workflow-policy/typechecking-policy.md defines a cast
as a proof obligation rather than a conversion. A cast is
permitted ONLY in one of three positions:
SOUND BY CONSTRUCTION - the cast’s leaf types are all universal (
object,Any,None) so it widens to the language’s universal type or refines type PARAMETERS the checker structurally cannot narrow.CONFINED TO A TYPED BOUNDARY - the cast lives inside one of the helpers named in the audit’s boundary registry, one helper per leak kind.
STRUCTURAL SEAM - the cast carries an inline
# cast-policy: seam: <rationale>marker AND its(file_stem, target_type)pair is in the audit’s seam allowlist.
All other casts are violations. Tests MUST NOT use cast at all:
a test that needs one is evidence that the production API is
under-typed, and the API MUST be fixed instead.
Usage:
python -m ralph.testing.audit_cast_policy [codebase_root]
Returns exit code 0 if no cast-policy violations found, 1 otherwise.
- class ralph.testing.audit_cast_policy.CastPolicyViolation(file_path, line, category, detail='')[source]¶
Bases:
objectA single cast-policy violation found during scanning.
- Parameters:
file_path (str)
line (int)
category (str)
detail (str)
- ralph.testing.audit_cast_policy.audit_codebase(codebase_root)[source]¶
Audit the entire codebase for cast-policy violations.
Returns
(violations, files_checked).- Parameters:
codebase_root (Path)
- Return type:
tuple[list[CastPolicyViolation], int]
ralph.testing.audit_fenced_artifact_examples¶
Validate fenced artifact examples in prompt templates and format docs.
The audit covers two ways prompt authors teach artifact Markdown:
literal fenced blocks in every packaged
.jinja/.j2template and bundled format document; andcomplete example documents passed to the shared
render_artifact_submissionmacro, which creates the fence at render time.
Artifact types are resolved fail-closed from an artifact=<registered_type>
fence declaration, the format document’s declared type, the submission macro’s
literal type argument, or concrete type: frontmatter. The commit
frontmatter variants commit and skip resolve to the registered
commit_message spec. Non-artifact code fences and the generic
type: <artifact_type> grammar schematic are intentionally ignored.
Every resolved example is passed to
ralph.mcp.artifacts.markdown.parse_and_validate with the registered spec.
Warnings are permitted because the artifact grammar explicitly uses warnings
for tolerated descriptive vocabulary; any error diagnostic fails the audit.
- Usage:
python -m ralph.testing.audit_fenced_artifact_examples
Exit 0 means every fenced artifact example validates; exit 1 means at least one example is unresolved or invalid.
- ralph.testing.audit_fenced_artifact_examples.check_plan_example_coverage(source_name, source)[source]¶
Check the plan format reference’s task-size example coverage.
Complete grammar validation remains the responsibility of
check_source_examples(); this companion check locks the teaching corpus to tiny, medium, and large plan shapes and verifies that the large shape demonstrates bounded parallel fan-out followed by fan-in verification.- Parameters:
source_name (str) – Stable path-like label used in diagnostics.
source (str) – Plan format-document source text.
- Returns:
Source-anchored coverage and shape failures.
- Return type:
list[str]
- ralph.testing.audit_fenced_artifact_examples.check_source_examples(source_name, source, *, declared_artifact_type=None)[source]¶
Validate every artifact example present in one source document.
- Parameters:
source_name (str) – Stable path-like label used in diagnostics.
source (str) – Prompt-template or format-document source text.
declared_artifact_type (str | None) – Registered spec declared by the surrounding format document, when the frontmatter
typeis a variant such ascommitorskip.
- Returns:
One source-anchored string per extraction, type-resolution, or validation error. Non-artifact fences and validator warnings are omitted.
- Return type:
list[str]
- ralph.testing.audit_fenced_artifact_examples.collect_violations()[source]¶
Validate all packaged prompt-template and format-doc examples.
- Returns:
A sorted list of source-anchored failures. An empty list means every resolved example passed
parse_and_validatewith its registered spec.- Return type:
list[str]
- ralph.testing.audit_fenced_artifact_examples.main(argv=None)[source]¶
Run the fenced artifact example audit.
- Parameters:
argv (list[str] | None) – Unused argument list, accepted for consistency with other audit entry points.
- Returns:
0when every example validates against its registered spec, otherwise1after printing actionable diagnostics.- Return type:
int
ralph.testing.audit_idempotent_write_adoption¶
Idempotent-write adoption drift audit for stable persistence paths.
Ralph Workflow skips byte-identical full-file rewrites on stable paths to
reduce filesystem mutations and macOS fseventsd load without changing the
post-condition that each file contains the requested content. This audit
locks that consolidation across a curated set of persistence modules: every
allowlisted module must exist and must not call a raw write_text method.
Those writes must route through write_text_if_changed or
atomic_write_text_if_changed instead.
The allowlist intentionally excludes writers whose payload includes a fresh
created_at or updated_at timestamp because those bytes are expected to
change on every operation. It also excludes UUID-keyed and one-time paths,
where an identity comparison cannot avoid a repeat mutation. The curated
scope prevents false positives for legitimate append, temporary-file, and
replace operations outside the stable-path consolidation.
The audit uses only ast and Path.read_text over source files. It does
not start subprocesses, sleep, access the network, or mutate production data.
Usage:
python -m ralph.testing.audit_idempotent_write_adoption [package_root]
- Exit codes:
0 = clean 1 = violations found 2 = root not found
- class ralph.testing.audit_idempotent_write_adoption.IdempotentWriteViolation(kind, file_path, line, message)[source]¶
Bases:
objectA single idempotent-write adoption audit violation.
- Parameters:
kind (str)
file_path (str)
line (int)
message (str)
- ralph.testing.audit_idempotent_write_adoption.audit_idempotent_write_adoption(package_root, *, module_paths=('prompts/master_prompt.py', 'prompts/payload_refs.py', 'prompts/materialize_support.py', 'skills/_state_store.py', 'skills/_process_view.py', 'skills/_content.py', 'mcp/artifacts/format_docs/__init__.py', 'mcp/artifacts/handoffs.py', 'pipeline/auto_integrate_agent.py', 'pipeline/cycle_baseline.py', 'pipeline/checkpoint.py', 'pipeline/parallel/worker_runtime.py', 'cli/commands/run.py', 'workspace/fs.py', 'phases/review.py', 'recovery/controller.py'))[source]¶
Return adoption violations for the curated stable-path persistence modules.
- Parameters:
package_root (Path)
module_paths (tuple[str, ...])
- Return type:
list[IdempotentWriteViolation]
ralph.testing.audit_parallelization_dormant¶
Audit that planning keeps delegation optional and non-prescriptive.
ralph.testing.audit_resource_lifecycle¶
Resource-lifecycle audit (AST-based).
Enforces the resource-lifecycle contract documented in
ralph-workflow/docs/agents/memory-lifecycle.md:
threading.Thread(...)/Thread(...)calls MUST havedaemon=True— non-daemon threads can block process exit on the interpreter shutdown atexit join thatconcurrent.futuresregisters for its default executor.httpx.Client(...),httpx.AsyncClient(...), andrequests.Session(...)constructions MUST be the context-manager expression of awithstatement — bare assignment leaks the underlying HTTP connection pool and may not be closed at interpreter exit.os.open(...),os.openpty(...), andos.pipe(...)are allowed ONLY underralph/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.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). Adeque()/collections.deque()call WITHOUTmaxlen=is treated as unbounded. Mutable collection LITERALS ([],{},set()) assigned to a module-level name orself.Xare flagged — they have no cap and grow monotonically across a long session.Direct child-process creation (
subprocess/os/asyncio) is allowed ONLY underralph/process/, where ProcessManager owns teardown.
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 marker SET (_ALLOW_MARKERS) keeps the
resource-lifecycle-ok marker for contracts 1-3 and 5 and the
bounded-accumulator-ok marker for contract 4. Keep markers rare and
justified.
Scope and exclusions (intentional, documented):
ThreadPoolExecutoris NOT covered by the daemon-Thread rule; it has its own.shutdown()lifecycle owned by the caller.Bare
open()is governed byaudit_di_seam(composition-root env/open reads) and is OUT OF SCOPE here.loop.run_in_executor(None, ...)inralph/interrupt/asyncio_bridge.pyis 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 WITHOUTmaxlen.deque(maxlen=...)is clean by construction. Dataclass field defaults (field(default_factory=...)) and local-function variables are out of scope (higher false-positive rate; theBudgetState.failuresleak 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:
NodeVisitorAST 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:
objectA 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_pathis 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:
- ralph.testing.audit_resource_lifecycle.main(argv=None)[source]¶
Run the resource-lifecycle audit and return an exit code.
When
argv(orsys.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_prompt_single_sourcing¶
Detect divergent copies of the three deliberately cross-surface statements.
ralph.testing.audit_template_render_integrity¶
Render-integrity audit for packaged prompt templates and shared partials.
Renders every top-level template under ralph/prompts/templates/*.jinja and
every shared/_* partial through the real rendering path
(TemplateContext.default() + TemplateRenderer). Macro-only partials
receive small call harnesses so their bodies are validated independently
rather than only when a top-level caller happens to reach them. The audit
asserts five integrity properties on every rendered prompt:
No unrendered Jinja markers —
{{,{%and{#must not survive into the rendered output.Include resolution — every template x context combination must render without raising (
TemplateNotFoundor any otherTemplateRenderingErrorfrom the engine IS the failure signal; the audit’s job is to exercise every combination).No duplicated markdown section headings — a heading line (
#/##/… outside fenced code blocks) must not appear twice in one rendered prompt.No verbatim duplicated text blocks — no whitespace-normalized paragraph of >= 120 characters may appear twice within one rendered prompt (catches partial-vs-body restatement drift).
No blank-run or doubled-label defects — outside fenced payloads, no run of 3+ consecutive blank lines; no
LABEL:line immediately followed by an identicalLABEL:line. These are hard failures, matching the renderer’s blank-line normalization contract.
Rendered contexts use real drain capability mappings; capability and tool
variables are never toggled independently. Case-neutral condition discovery
uses Jinja’s parsed undeclared-variable set, so both uppercase and lowercase
branch inputs are covered without treating keywords or local names as inputs.
Optional inputs are combined exhaustively within each reachable nested
conditional path and pairwise across a target’s template closure, while
incompatible transport states such as simultaneously inlined and file-routed
payloads are excluded. Real capability profiles are crossed with those branch
paths. Every remaining undeclared variable receives a short deterministic
placeholder so StrictUndefined cannot mask an unexercised branch.
Cross-document duplication (master prompt vs template) is out of scope.
- Usage:
python -m ralph.testing.audit_template_render_integrity
Exit 0 = clean, 1 = at least one integrity violation.
- ralph.testing.audit_template_render_integrity.check_rendered_prompt(template_name, rendered)[source]¶
Run integrity checks 1 and 3-5 against one rendered prompt.
Returns human-readable violation descriptions (no template/scenario prefix —
collect_violationsadds attribution). Check 2 (include resolution) lives incollect_violationsbecause it is observed as a rendering exception, not as a property of rendered text.- Parameters:
template_name (str)
rendered (str)
- Return type:
list[str]
- ralph.testing.audit_template_render_integrity.collect_violations()[source]¶
Return hard violations across every template/partial render scenario.
- Return type:
list[str]
- ralph.testing.audit_template_render_integrity.main(argv=None)[source]¶
Run the render-integrity audit and return the process exit code.
Renders every packaged top-level template and shared partial across real capability and optional-input scenarios, then prints a one-line summary or a labeled violation list. Has no side effects beyond stdout output.
- Parameters:
argv (list[str] | None) – Unused positional argument list (kept for CLI symmetry with the other audit entry points). Values are ignored.
- Returns:
0when every template x scenario renders cleanly and passes all five checks,1otherwise.- Return type:
int
ralph.testing.audit_terminal_escape_containment¶
Audit that the terminal-escape containment contract is wired into every sink.
Enforces the AST-scoped invariants a regression of the
wt-036-claude-code-permission fix would break. Whole-file
literal presence is not enough – the audit MUST inspect the
specific function / method / SpawnOptions call that wires the
fix so an adversarial edit that removes the strip from one sink
while keeping the helper name in the file is still caught.
The escape containment contract:
ralph/display/line_sanitizer.pyMUST exposestrip_terminal_controland use the full[0-?]CSI parameter-byte range (0x30-0x3F). A narrower class such as[0-9;?]leaks private-parameter CSI sequences (ESC[>0cdevice-attributes reply,ESC[<35;1;2MSGR mouse report) on screen.
ralph/display/_plain_constants.pyMUST NOT carry the SGR-only[0-9;]*mregex that the previous plan revision relied on (it cannot matchESC[?1049halternate screen,ESC[2Jerase display,ESC[>0cprivate-parameter CSI). Stripping MUST go throughstrip_terminal_control.
ralph/display/parallel_display.py:
MUST NOT keep any
_ANSI_ESCAPEreference (the old SGR-only import was the hidden second consumer – an ImportError trap if just the constant is deleted without migrating its users).
ParallelDisplay.strip_markupMUST delegate tostrip_terminal_control(line)(the rewrite target).The module-level
emit_activity_lineMUST call_sanitize(line)in BOTH itsunit_id is Noneandunit_idbranches (the twoconsole.printsites that previously painted the real terminal unsanitized).
ParallelDisplay._render_titled_linesMUST callstrip_terminal_controlon each body line (the artifact / handoff body sink).
ralph/display/line_sanitizer.pyMUST also exposestrip_markup_safe– the single guardedText.from_markupcall site – whose guard stays TOTAL (except Exception). A type-named guard already regressed once: Rich raisesMarkupErrorfrom its ownConsoleErrorhierarchy, soexcept ValueErrornever matched and a grep pattern carrying[/pdf /text /imageb](an unmatched closing tag) crashedemit_parsed_event.
ralph/display/_plain_constants._sanitizeandralph/display/parallel_display._strip_markupMUST delegate tostrip_markup_safeand MUST NOT parse markup themselves.No
from_markup(...)call anywhere underralph/may take a non-literal argument outside_MARKUP_PARSE_ALLOWLIST(package-wide AST scan viaMarkupParseInvariant). Guarding at each call site instead of routing through the choke point is exactly the shape that regressed, so it is rejected by construction.
ralph/display/activity_model.render_event_lineMUST callstrip_terminal_control(content or "")BEFORE the truncation and richescape(the activity_router render path – the original code didrich.markup.escapeonly, which escapes rich markup but NOT ANSI/C0 bytes).
ralph/agents/invoke/_pty_runner.pyMUST NOT containfile=sys.stdout(it painted the rich Live status bar underneath) and MUST NOT containtqdm((the wrapper that wrote it).
ralph/agents/invoke/_process_reader.pyMUST passstdin=subprocess.DEVNULLto itsSpawnOptions(...)call (so the agent child never inherits Ralph’s controlling-terminal stdin) and MUST NOT carrystdin=Noneanywhere (which is INHERIT).
ralph/agents/subprocess_executor.pyMUST passstdin=_DEVNULLto itsSpawnOptions(...)call (file- local alias ofsubprocess.DEVNULLmatching the existingPIPE/STDOUTaliases).
ralph/agents/invoke/_pty_line_reader.pyMUST stillyield queued_line(proves the reader keeps yielding raw VT text – a defence-in-depth pin against an over-eager future fix that sanitizes at the source and silently breaks interactive permission auto-approval).
ralph/process/manager/_spawn_options.pyMUST defaultSpawnOptions.stdintosubprocess.DEVNULL(so no child inherits Ralph’s controlling-terminal stdin by construction). The dataclass field must NOT be reverted toNone.No
SpawnOptions(...)call site anywhere under theralph/package may passstdin=None(package-wide AST scan viaPackageWideCallSiteInvariant– the helper exists in three flavors:Invariant(whole-file),FunctionBodyInvariant(function/method) andCallSiteInvariant(single file callee). The new variant extends coverage to a callee-in-any-file).
ralph/logging.py::configure_loggingMUST NOT handsys.stderrtologger.add(the public library configurator accepts an injectedconsole_sinkkeyword) and MUST NOT enablecolorize=True(the stripper removes the SGR codes that colorizer emits). Sanitization happens throughstrip_terminal_control()via the injectedconsole_sink.
ralph/cli/main.py::_configure_loggingMUST NOT handsys.stderrtologger.addfor ANY of the five verbosity branches. Every branch routes through an injectedconsole_sink(or the library/worker fallbackmake_stderr_log_sink).
ralph/cli/main.py::mainMUST wire the Console-backed sink by passingmake_sanitizing_log_sink(_cli_ctx)toconfigure_logging(the CLI call site). Without this wiring, loguru emits records via rawsys.stderrand the richLivestatus bar races the logger for the terminal.
ralph/display/log_sink.py::make_sanitizing_log_sinkMUST callstrip_terminal_controland print through the injected Console withmarkup=False/highlight=False.
ralph/display/log_sink.py::make_stderr_log_sinkMUST callstrip_terminal_controlbefore writing the line.
ralph/display/log_sink.pyMUST NOT construct arich.Consoleitself (DI invariant: the single source of truth forConsoleconstruction isralph.display.theme).
ralph/process/pty.py::spawn_pty_processMUST keepos.setsid()(new session) andTIOCSCTTY(controlling terminal = slave pty) inside the child branch. Without this pair a PTY child could claim the foreground process group of Ralph’s controlling TTY.
Every literal was grep-verified against the current tree at
implementation time. Restoring any forbidden literal, removing
a required call from the wired sink, or narrowing the CSI
class back to [0-9;?] fails the audit with exit 1 and a
banner that names the violated invariant.
Usage:
python -m ralph.testing.audit_terminal_escape_containment
Exit 0 = clean, 1 = at least one invariant violated.
- class ralph.testing.audit_terminal_escape_containment.CallSiteInvariant(*, rel_path, callee_name, present=(), absent=(), require_any=True)[source]¶
Bases:
objectAST-scoped check: every literal must hold in at least one named callee call site.
Use
CallSiteInvariantwhen the audit must prove a specific argument is passed to a specific constructor (e.g.SpawnOptions(stdin=subprocess.DEVNULL, ...)). Checks are performed against the source segment of each call node, so formatting across lines does not break the literal match.- Parameters:
rel_path (str)
callee_name (str)
present (tuple[str, ...])
absent (tuple[str, ...])
require_any (bool)
- class ralph.testing.audit_terminal_escape_containment.FunctionBodyInvariant(*, rel_path, qualname, present=(), absent=(), min_counts=None)[source]¶
Bases:
objectAST-scoped check: every literal must hold inside a named function body.
Use
FunctionBodyInvariantwhen the audit must prove a sanitiser call is INSIDE the wired sink function – whole-file presence is not enough, because the helper name can appear in imports or docstrings without ever being called from the sink.min_countsmaps a needle to the minimum number of occurrences required in the body. Use it to pin a call to every branch of a sink (e.g. the twoconsole.printsites in the module-levelemit_activity_line) – a singlepresentcheck would let an adversary sanitize only one branch and pass.- Parameters:
rel_path (str)
qualname (str)
present (tuple[str, ...])
absent (tuple[str, ...])
min_counts (dict[str, int] | None)
- class ralph.testing.audit_terminal_escape_containment.Invariant(*, rel_path, present=(), absent=())[source]¶
Bases:
objectOne literal-string check the audit enforces against a whole file.
- Parameters:
rel_path (str)
present (tuple[str, ...])
absent (tuple[str, ...])
- class ralph.testing.audit_terminal_escape_containment.MarkupParseInvariant(*, callee_name='from_markup')[source]¶
Bases:
objectAST-scoped check: no unguarded
from_markupcall anywhere in the package.Text.from_markupis a parser, and Rich rejects malformed input by raising from its ownConsoleErrorhierarchy (MarkupError,StyleSyntaxError) – neverValueError. Agent output routinely carries bracket sequences Rich reads as unmatched closing tags (a grep pattern like[/pdf /text /imageb]), so an unguarded call on agent-origin text crashes the display sink and takes the run down.A call is accepted only when EITHER
it lives in a file listed in
_MARKUP_PARSE_ALLOWLIST, orits first argument is a plain string literal (provably not agent-origin – no name, call, f-string, or concatenation).
Everything else must go through
ralph.display.line_sanitizer.strip_markup_safe(). Catching the exception at each call site instead is NOT acceptable: that is exactly the shape that already regressed once (except ValueErrornever matchedMarkupError), and it re-opens with every new Rich release.- Parameters:
callee_name (str)
- class ralph.testing.audit_terminal_escape_containment.PackageWideCallSiteInvariant(*, callee_name, absent=(), present=())[source]¶
Bases:
objectAST-scoped check: a callee call anywhere under the package MUST avoid a literal.
Use
PackageWideCallSiteInvariantwhen a contract must hold for a constructor at every call site in the entire package – the dataclass default alone is not enough because an explicit re-opt-in (e.g.SpawnOptions(stdin=None)) silently re-opens the leak. Walks every*.pyfile under_PACKAGE_ROOT, collects the source segment of everycallee_name(...)call, and rejects any segment that carries a forbidden literal. Exposes the sameviolations() -> list[str]interface asInvariant/FunctionBodyInvariant/CallSiteInvariantsomain()’s loop is unchanged.The class uses
_read(the same monkeypatchable source fetcher the other invariants use) so adversarial tests can inject a custom source per file via_patch_rel(). The package file list is cached for a single audit run to keep the wall-clock cost off the 60s combined budget.- Parameters:
callee_name (str)
absent (tuple[str, ...])
present (tuple[str, ...])
- ralph.testing.audit_terminal_escape_containment.logging_configurator_violations()[source]¶
Return terminal-containment violations in configured logging paths.
- Return type:
list[str]
- ralph.testing.audit_terminal_escape_containment.main(argv=None)[source]¶
Run the terminal-escape containment audit and return the process exit code.
Iterates the literal-string and AST-scoped invariants in
_INVARIANTSand aggregates every violation across every file the containment contract touches (display modules, the two logging configurators, the SpawnOptions dataclass, the ProcessManager seams, and the PTY spawn path). Prints a one-line summary on success or a labeled, line-broken failure banner on violation. Has no side effects beyond stdout output andsys.exitsemantics.- Parameters:
argv (list[str] | None) – Unused positional argument list (kept for CLI symmetry with other audit entry points). Values are ignored.
- Returns:
0when every invariant passes,1when at least one literal-string or AST-scoped check fails.- 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:
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.
Single canonical owner of ``IdleWatchdog`` class. A top-level class definition named
IdleWatchdogis allowed ONLY atralph/agents/idle_watchdog/idle_watchdog.py. Any other production file underralph/that defines a top-levelclass IdleWatchdograises theduplicate_idle_watchdogviolation. The match is exact-name, not substring —class IdleWatchdogSubclassis NOT flagged.Single canonical owner of ``PostExitWatchdog`` class. A top-level class definition named
PostExitWatchdogis allowed ONLY atralph/agents/idle_watchdog/_post_exit_watchdog.py. Any other production file underralph/that defines a top-levelclass PostExitWatchdograises theduplicate_post_exit_watchdogviolation.``WatchdogFireReason`` construction only in canonical owners. The two canonical owner modules (
idle_watchdog.pyand_post_exit_watchdog.py) are the ONLY files in the production tree that may constructWatchdogFireReasonvalues viaWatchdogFireReason(...)orWatchdogFireReason.<NAME>attribute access. Any other production file that does so raisesfire_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:
objectA 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 topackage_root.parent.
- Returns:
A list of
WatchdogDriftViolationrecords. Empty list means the tree is clean.- Return type:
list[WatchdogDriftViolation]
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, preferringRALPH_WORKSPACEover the current working directory.prompt— resolves the operator-visible source prompt path, preferringPROMPT_PATHover<workspace>/PROMPT.md. Callers operating on the materialisedPRODUCT_CRITERIA.mdMUST NOT use this resolver.marker— read-only reader for the Pro-owned<workspace>/.ralph/run.jsonmarker file and an optional.ralph/heartbeat_tokensidecar.heartbeat— bounded/api/heartbeatclient. The client runs in a daemon thread, uses boundedhttpxtimeouts on every call, and is idempotent onstop()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.mdduring 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_PATHvalue, orNoneif 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.environis read at call time.- Return type:
str | None
- ralph.pro_support.env.get_ralph_workspace(env=None)[source]¶
Return the raw
RALPH_WORKSPACEvalue, orNoneif 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.environis read at call time.- Return type:
str | None
- ralph.pro_support.env.is_pro_mode(env=None)[source]¶
Return True when
RALPH_WORKFLOW_PROis 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.environis 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.environis read at call time.fallback (Path | str | None) – Path to use when
RALPH_WORKSPACEis unset or empty. Defaults toPath.cwd(). The fallback is also resolved viaPath.resolve()so symlinks are normalised.
- Returns:
Canonical, absolute
pathlib.Pathfor 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 theosmodule 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/PRODUCT_CRITERIA.md) must go through
resolve_effective_prompt_path().
Callers operating on the engine-owned materialised
.agent/PRODUCT_CRITERIA.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:
If
PROMPT_PATHis set and non-empty in the suppliedenv:when absolute, return it resolved through
Path.resolve();when relative, resolve it relative to
workspace_rootand return the result throughPath.resolve().
Otherwise return
<workspace_root>/PROMPT.mdresolved throughPath.resolve().
- Parameters:
workspace_root (Path | str) – Workspace root directory. The returned path is always relative to this root when
PROMPT_PATHis unset.env (Mapping[str, str] | None) – Optional env mapping. When
None(default),os.environis 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/heartbeatposts.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
Nonewhen unavailable.Resolution order:
marker['heartbeatToken']if present and non-empty.The sidecar file at
<workspace>/.ralph/heartbeat_token(its stripped contents).Nonewhen both are absent or empty.
The function never raises; it returns
Noneon 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
Noneon 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.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
httpxcall so the bounded-subprocess audit (ralph.testing.audit_mcp_timeout) catches any regression;treats
401and404responses 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:
objectBounded, 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_loaderandheartbeat_factoryso tests can drive the loop with fakes.No ``time.sleep`` in production. The default
sleeperisself._stop_event.wait(timeout=...)so astop()call from the main thread interrupts the wait immediately.Daemon thread. The thread is marked
daemon=Trueso the process can always exit even ifstop()is missed.Read-only against the marker. The default
marker_loaderonly callsread_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:
objectDaemon-threaded watcher that polls for the Pro marker and starts the heartbeat.
The watcher polls the marker via an injectable
marker_loadereverypoll_interval_seconds. On the first successful read of a complete payload (run_id,token,port), it invokesheartbeat_factoryand 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 (mirroringProHeartbeatClient.stop).- Parameters:
workspace_root (Path | None)
poll_interval_seconds (float)
marker_loader (_MarkerLoader | None)
heartbeat_factory (_HeartbeatFactory | None)
clock (_Clock | None)
sleeper (_Sleeper | 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), cascadestop()to the client so the heartbeat drain thread is reaped. The cascade is wrapped insuppress(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) -> PolicyBundleregistry_factory:(UnifiedConfig) -> AgentRegistrystate_factory:(UnifiedConfig, AgentsPolicy, PipelinePolicy, dict[str, int] | None) -> PipelineStaterecovery_controller_factory:(PipelineState, PolicyBundle, UnifiedConfig) -> tuple[RecoveryController, int]marker_watcher_factory:(Path) -> ProMarkerWatcher
1 override:
policy_bundle_override: PolicyBundle | None; when set, the linepolicy_bundle = factory(workspace_scope, config)is replaced withpolicy_bundle = pro_hooks.policy_bundle_override.1 passthrough:
snapshot_registry: SnapshotRegistry | None; when set, the inner loop publishes aPipelineStateSnapshotto this registry on each reduce step.6 collaborator overrides that are applied to
PipelineDepsbybuild_default_pipeline_deps:display_context: overrides the display context.model_identity: overrides the multimodal model identity.master_prompt_materializer: overrides the master-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, master_prompt_materializer=None, phase_prompt_materializer=None, artifact_requirements_resolver=None, recovery_sleep=None)[source]¶
Bases:
objectDI seam: lets Pro inject custom pipeline collaborators into
run().All fields default to
None. When a factory isNone,run()uses the production helper (the existing behaviour). Whenpolicy_bundle_overrideis notNone,policy_bundle_factoryis short-circuited.The five collaborator overrides on the modular surface (
display_context,model_identity,master_prompt_materializer,phase_prompt_materializer,artifact_requirements_resolver) are the only fields a Pro plumbing consumer must know about when targetingralph.pipeline.factory.PipelineCore; they are applied viaapply_pro_hooks_to_core(). The extended surface (PipelineDeps) additionally consumesrecovery_sleepand the main-pipeline factories; those are handled byralph.pipeline.factory.apply_pro_hooks_to_deps().- Parameters:
policy_bundle_factory (PolicyBundleFactory | None)
registry_factory (RegistryFactory | None)
state_factory (StateFactory | None)
recovery_controller_factory (RecoveryControllerFactory | None)
marker_watcher_factory (MarkerWatcherFactory | None)
policy_bundle_override (PolicyBundle | None)
snapshot_registry (SnapshotRegistry | None)
display_context (DisplayContext | None)
model_identity (MultimodalModelIdentity | None)
master_prompt_materializer (MaterializeMasterPromptFn | None)
phase_prompt_materializer (PhasePromptMaterializerFn | None)
artifact_requirements_resolver (ArtifactRequirementsResolverFn | None)
recovery_sleep (Callable[[float], None] | None)
- 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 notrun()kwargs; they are fields thatbuild_default_pipeline_depsinspects separately when composingPipelineDeps.policy_bundle_overrideis also intentionally excluded because it is not arun()kwarg; it is a field thatrun()inspects separately to short-circuitpolicy_bundle_factory.- Return type:
dict[str, object]
- ralph.pro_support.hooks.apply_pro_hooks_to_core(core, pro_hooks)[source]¶
Return a new
PipelineCorewith Pro collaborator overrides applied.Only the five PROMPT-mandated collaborators are propagated:
display_context,model_identity,master_prompt_materializer,phase_prompt_materializer, andartifact_requirements_resolver. Extended fields such aspolicy_bundle_override,registry_factory,state_factory,recovery_controller_factory,marker_watcher_factory,snapshot_registry, andrecovery_sleepare ignored because they belong to the main-pipelinePipelineDepssurface.- Parameters:
core (PipelineCore)
pro_hooks (ProPipelineHooks)
- Return type:
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-copieddictfields; the livePipelineStateis never referenced from the snapshot.Plain ``dict`` for nested mapping fields.
metricsis a pydanticRunMetrics.model_dump()(plain dict), andouter_progress/loop_iterations/budget_capsare shallowdictcopies.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:
objectFrozen, 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:
objectMutable 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
Noneif 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_idfrom the marker file. When the marker is missing,run_idisNone.
- Return type: