marla.runtime

Local vs. distributed execution, device resolution, and the SPADE-independent lifecycle/failure logic.

Local execution mode (spec section 4.1).

All configured agents run in one Python process, started from one spade.run() main function, sharing one asyncio event loop – but they still communicate exclusively through real SPADE messages over a real (here, embedded) XMPP server, never through direct Python method calls between agents.

exception marla.runtime.local.LocalRunError[source]

Bases: Exception

Raised when a local-mode run ends in failure (mirrors EXPERIMENT_FAILED).

marla.runtime.local.resolve_debug_dir(config, run_id)[source]

Where --debug writes every Plan Maker query/response pair (see PlanMakerAgent).

Deliberately its own top-level debug/ directory, not nested under metrics.output_directory (runs/ by default) – it holds ad hoc inspection files for a human, not run metrics/results, and is easy to overlook several directories deep.

Parameters:
Return type:

Path

marla.runtime.local.resolve_password(password_env, alias)[source]
Parameters:
  • password_env (str | None)

  • alias (str)

Return type:

str

marla.runtime.local.resolve_run_dir(config)[source]

<metrics.output_directory>/<experiment-name>/<run-id>/ (spec section 21).

Parameters:

config (Config)

Return type:

Path

marla.runtime.local.resolve_scenario_path(config, config_dir)[source]
Parameters:
Return type:

str

marla.runtime.local.run_local(config, config_dir, num_rollouts, embedded_xmpp_server=True, debug=False)[source]

Run a local-mode experiment; blocks until the RL Orchestrator finishes.

Raises LocalRunError if the run ended in failure. Returns the stopped RLOrchestratorAgent (holding training_result) on success.

SPADE’s Container is a process-wide singleton, and its event loop is closed at the end of spade.run() – calling run_local more than once in the same process reuses a closed loop and fails. This matches real usage (marla run is always a fresh process); tests that need more than one local-mode run must isolate each in its own subprocess.

Parameters:

Distributed execution mode (spec section 4.2).

Each marla run process starts only the agents named via repeatable --agent options (by alias or JID), connecting to the configured, externally-reachable XMPP server – distributed mode never uses the embedded pyjabber server, that is a local-mode convenience only. Every process loads the identical configuration and shares experiment.run_id (already enforced by config validation); only the process that starts rl_orchestrator owns NASimEmu and writes central metrics.

exception marla.runtime.distributed.DistributedRunError[source]

Bases: Exception

Raised when a distributed-mode process ends in failure.

marla.runtime.distributed.run_distributed(config, config_dir, selected_aliases, num_rollouts, debug=False)[source]

Entry point for marla run in distributed mode.

Starts only the agents in selected_aliases and connects to the configured (real, externally reachable) XMPP server. Blocks until every locally-started agent finishes; raises DistributedRunError if any of them ended in failure.

Like marla.runtime.local.run_local(), this can only be called once per process (SPADE’s Container is a process-wide singleton) – which matches real usage, since marla run is always a fresh process.

Parameters:

Startup/shutdown/failure coordination logic (spec section 5), SPADE-free.

There is no Coordinator component; the RL Orchestrator coordinates startup and shutdown directly. This module holds the pure decision logic – “given these READY/failure events, are we ready to start, and if not, why did we fail” – independent of SPADE/XMPP wiring, so it can be unit tested with a plain asyncio.Queue instead of a real agent connection. The SPADE glue that turns real messages and presence callbacks into the events this module consumes lives in marla.agents.lifecycle_behaviours.

There is no elapsed-time response timeout (spec section 5/10): this module waits indefinitely on the event queue. Only an explicit failure/disconnect event ends the wait early.

class marla.runtime.lifecycle.FailureSignal(alias, reason)[source]

Bases: object

A participant failed, disconnected, or reported EXPERIMENT_FAILED.

Parameters:
alias: str
reason: str
exception marla.runtime.lifecycle.LifecycleFailedError(alias, reason)[source]

Bases: Exception

Raised when a required participant fails or disconnects during startup.

Parameters:
class marla.runtime.lifecycle.ReadySignal(alias, jid, schema_version, model_version, resolved_device)[source]

Bases: object

A participant reported READY.

Parameters:
  • alias (str)

  • jid (str)

  • schema_version (str)

  • model_version (str)

  • resolved_device (str)

alias: str
jid: str
model_version: str
resolved_device: str
schema_version: str
marla.runtime.lifecycle.register_sigint_handler(stop_event)[source]

Arm a one-shot Ctrl+C handler that requests a graceful stop.

A synchronous, blocking Plan Maker consultation can’t safely be interrupted mid-computation (there is no clean cancellation point inside a torch forward pass), so the first SIGINT just sets stop_event (checked between environment steps – see learning.rollout.RolloutCollector.collect) and removes itself. A stopped run still finishes the in-flight step and then follows the same STOP_EXPERIMENT/agent.stop() shutdown path as a normal completion, rather than tearing down mid-message. A second Ctrl+C falls through to Python’s default KeyboardInterrupt handling, for an immediate, unconditional exit if the graceful stop doesn’t return promptly enough.

Parameters:

stop_event (Event)

Return type:

None

async marla.runtime.lifecycle.wait_for_all_ready(required_aliases, events)[source]

Block until every required alias has signaled READY.

Raises LifecycleFailedError on the first failure/disconnect event concerning a required alias. Events for aliases outside required_aliases are ignored (defensive; should not occur in practice). Returns immediately with an empty dict if required_aliases is empty (the baseline variant has no participants to wait for).

Parameters:
Return type:

dict[str, ReadySignal]

Resolve the device: cpu | gpu | auto configuration option.

Each machine/process resolves its own device independently (this matters in distributed mode, where the Plan Maker may run on different hardware than the RL Orchestrator). Remote API model backends are never subject to these checks – only locally executed ML models are.

exception marla.runtime.device.DeviceResolutionError[source]

Bases: RuntimeError

Raised when device: gpu is requested but CUDA is unavailable/unusable.

class marla.runtime.device.ResolvedDevice(requested, resolved)[source]

Bases: object

Requested vs. actually-resolved device, recorded for reproducibility.

Parameters:
  • requested (Literal['cpu', 'gpu', 'auto'])

  • resolved (Literal['cpu', 'cuda'])

requested: Literal['cpu', 'gpu', 'auto']
resolved: Literal['cpu', 'cuda']
property torch_device: object
marla.runtime.device.resolve_device(requested)[source]

Resolve requested into a concrete device, per spec section 18.

  • cpu: always resolves to CPU.

  • gpu: must successfully initialize CUDA (availability check plus an actual tensor allocation), otherwise raises DeviceResolutionError so startup fails before the agent reports ready.

  • auto: CUDA if available, else CPU.

Parameters:

requested (Literal['cpu', 'gpu', 'auto'])

Return type:

ResolvedDevice