Skip to content

Code Index

Guide version 1.1 — OpenCDA-MARL 2.1 (2026-10)

Open this when you need to find where something lives, how a run executes, or which seam a change belongs to. Every path below exists in the repository (a test checks), so the index is a map you can trust rather than a narrative. The edit policy per area is in the Agent Workflow.

The package tree states the architecture, and a test enforces it (test/basic/test_architecture.py):

opencda_marl/
  _kernel/      the complete lifecycle — SEALED. Never imports a plugin;
                external code never imports it. Everything replaceable is
                reached through api/ and the plugin registry.
  api/          the public surface: data contracts, the Policy contract,
                and the registration door for every plugin kind.
  plugins/      the one plugin system: discovery (entry points, env,
                config, contrib), enumeration, API-version rollback,
                and the startup freeze. No hot reload — a run's
                behaviour set is fixed at initialize.
  builtin/      the framework's own plugin set: default implementations
                of every kind, loaded through the same doors an external
                package uses. Never imports the kernel.
  envs/         public facades: the PettingZoo port, the backend env
                classes, the dependency-free schema env.
  telemetry/  evaluation/  utils/  tools/    services both sides may use.

How a run executes

opencda.py -t <family>/<scenario>/<profile> --marl
 └─ opencda_marl/utils/config_locator.py     resolve default.yaml → base.yaml → profile (+ extra roots)
 └─ opencda_marl/utils/config_merger.py      smart_merge of the three layers (an empty key keeps the lower layer's mapping)
 └─ opencda_marl/_kernel/lifecycle/coordinator.py    MARLCoordinator(config) turns null top-level sections into {}
     │    (normalize_run_config), then initialize() → run()
     │    plugins load (entry points, $OPENCDA_MARL_PLUGINS, MARL.plugins, contrib),
     │    every part constructs through the registry, then the registry FREEZES
     ├─ CARLA: _kernel/lifecycle/pipeline_builder.py builds
     │    MARLScenarioManager (_kernel/lifecycle/scenario_manager.py)  CARLA client, world, CavWorld
     │    MARLManager          (_kernel/policy/manager.py)             observations → policy → rewards
     │    MARLEnv              (_kernel/envs/marl_env.py)              the CARLA episode
     │    WorldManager         (_kernel/lifecycle/world_manager.py)    periodic world reloads with state carry-over
     └─ SUMO:  SumoMARLEnv    (_kernel/envs/sumo_marl_env.py)         the whole backend in one class (libsumo/traci)
 └─ one step (coordinator.step):
      env.step → MARLTrafficManager spawns / retires vehicles (_kernel/traffic/traffic_manager.py)
               → MARLAgentManager drives each vehicle's agent (_kernel/agent_manager.py)
               → MARLManager.extract states (the observation_extractor plugin) → algorithm.select_action(s)
               → the reward_calculator plugin (api/rewards.py resolves profiles) → objective vectors (api/objectives.py)
               → algorithm.store_transition / update via AlgorithmStrategy (_kernel/policy/strategy.py)
               → TrainingMetrics (evaluation/training_metrics.py), MetricsStore (telemetry/store.py)
 └─ episode end (coordinator.reset_episode):
      EvaluationManager.end_episode (evaluation/manager.py) → CheckpointManager (_kernel/policy/checkpoint.py)
      → MARLManager.reset_episode → env.reset

Package map

opencda_marl/api/ — the public surface

What external packages import; the kernel and the plugins meet here.

module role
events.py StepEvent: the per-step event record every backend emits
contracts.py NormalizedObservation, RewardSpec, RewardOutput, normalize_observation
policy.py the Policy / IntentPolicy / PolicyCapabilities contract every algorithm implements
algorithms.py register_algorithm, AlgorithmSpec, resolve_algorithm_spec — the algorithm kind
components.py register_component / resolve_component / component_key, the _BUILTINS default rows, BASELINE_AGENT_TYPES, KIND_SIGNATURES
rewards.py reward profiles: register_reward_profile, resolve_reward_configuration, the built-in profile rows
objectives.py objective profiles and vector mechanics: register_objective_profile, ObjectiveSpec, scalarisation
preferences.py preference profiles: register_preference_profile, PreferenceRegistry
route_intent.py RouteIntent candidates, legality masks and diagnostics; the per-exit-lane commitment counts (committed_exit_lane_occupancy)
intent.py the IntentProvider callable shape and build_provider_from_config
intent_observation.py the spawn observation an intent policy sees, and its dimensions
safety.py TtcMetricState — TTC metric mechanics shared by envs and calculators
exceptions.py PlannerFailureException and friends

opencda_marl/plugins/ — the one plugin system

module role
registry.py the federation: every kind reports here — journal, cross-kind rollback, describe_plugins, freeze() / thawed()
loader.py discovery: entry-point group opencda_marl.plugins, $OPENCDA_MARL_PLUGINS, MARL.plugins, the API-version pin
__main__.py python -m opencda_marl.plugins — list everything registered

opencda_marl/_kernel/ — the sealed lifecycle

Edit policy: changes here are framework contributions; plugins never need them. The kernel resolves every replaceable part through the registry.

area role
lifecycle/ MARLCoordinator (initialize / step / run / reset_episode / close, plugin freeze, traffic view), PipelineBuilder, MARLScenarioManager, WorldManager + TrainingStateSnapshot, ActorRegistry, SensorRig
envs/ MARLEnv (CARLA), SumoMARLEnv, sumo_runtime.py predicates, SumoEpisodeState
policy/ MARLManager (also the mixed-autonomy gate: the learner acts on and learns from its own population only), factory.py, per-family strategy.py, checkpoint.py, action_space.py, path_lifecycle.py, competition.py (crossing order, per junction), accumulator.py, decision_zone.py (spawn vs junction-approach trigger), intent_bridge.py
agent_manager.py MARLAgentManager: drives each vehicle's agent, world snapshots
adapter/ MARLVehicleAdapter (one per vehicle), world_snapshot.py, _nearby/, _physics/, _spatial/
traffic/ MARLTrafficManager, flows, spawn events, recordings, SUMO bindings (_sumo_home.py, _sumo_lib.py, sumo_adapter.py — also the typed carla stand-in for slim installs), preference sampling at spawn, per-vehicle population (population.py), and the SUMO↔CARLA frame bridge read from the map (coordinate_bridge.py)
sensing/ CarlaMonitor, CarlaSpectator, MARLCollisionSensor (the future perception axis gets an api contract)
scenarios/ ScenarioBuilder and templates; a scenario names its map with scenario.map.town or scenario.map.xodr_path — the latter builds the world from OpenDRIVE alone (road surface and signs, no buildings)

opencda_marl/builtin/ — the built-in plugin set

Default implementations of every kind; replace any of them from your own package without touching anything here.

area kind(s)
algorithms/ the eight learners + intent/ + contrib/ drop-in folder (auto-imported; contrib/random_speed.py is the smallest complete learner) — registered via the wheel's own opencda_marl.plugins entry point
agents/ agent types behavior / vanilla / rule_based / marl and AgentFactory
planners/ marl_planner.py (MARLPlanner) + waypoint helpers
rewards/ calculator.py — the default reward_calculator
extractors.py ObservationExtractor — the default observation_extractor
intent_providers/ tao_fixed / naive_random / rule_based_path providers

opencda_marl/envs/ — public facades

module role
parallel_env.py OpenCDAMARLParallelEnv: the PettingZoo port; bring your own training loop
traffic_schema_intersection.py, traffic_schema.py, spaces.py the dependency-free schema env used by unit tests and tools/schema_env_demo.py

opencda_marl/evaluation/, telemetry/, utils/, tools/ — services

package role
evaluation/ EvaluationManager (summary mode; mirrors Eval/* into the run's own metrics store, or a separate evaluation/ run when the run has none), TrainingMetrics (per-episode archive), aggregate.py, hypervolume.py, objective_metrics.py
telemetry/ MetricsStore (SQLite per run, store.py), the monitor — the framework's one GUI, auto-started with every run (monitor.py backend, static/ frontend), the schematic traffic view (traffic_view.py), the trace writer (trace.py)
utils/ config resolution (config_locator.py, config_merger.py), seeding.py — pure library helpers
tools/ runnable python -m maintenance tools: config_lint.py, xodr_fixer.py, list_vehicle_types.py, schema_env_demo.py

opencda/ — vendored OpenCDA

The upstream OpenCDA core (scenario testing, perception, localization, planning, control, V2X, platooning). Every local modification is a row in the patch ledger; opencda/scenario_testing/ holds the non-MARL tutorial scenarios run without --marl.

Where state lives

state owner notes
per-episode SUMO fields SumoEpisodeState (_kernel/envs/episode_state.py) never loose attributes on the env
what survives a CARLA world reload TrainingStateSnapshot (_kernel/lifecycle/world_manager.py) learner networks/optimizers/buffers, counters, route-intent providers
checkpoints, resume offsets CheckpointManager (_kernel/policy/checkpoint.py), training_metadata.json latest / best / episode_NNNN; a built-in learner's checkpoint config is TrainingLogger.checkpoint_config(): plain values, no tensorboard / metrics_store block, nested-block interpolations kept as written (top-level values arrive resolved), values that are absolute paths replaced. No loader reads it
per-update and per-episode scalars MetricsStore (telemetry/store.py) artifacts/metrics/<algo>/<run>/metrics.sqlite; <algo> is the registered algorithm key (the class-derived name only for a learner built without MARLManager)
TensorBoard event files TrainingLogger (builtin/algorithms/training_logger.py) <tensorboard dir>/<algo>/<run>/, with the metrics store's <algo>: both writers take it from TrainingLogger._run_algorithm_name
full per-episode metrics archive TrainingMetrics (evaluation/training_metrics.py) metrics_history/<run>/metrics_history_epN.pkl
CARLA actors ActorRegistry (_kernel/lifecycle/actor_registry.py) the only place actors are destroyed
the run's plugin set opencda_marl/plugins/registry.py frozen at the end of initialize()

Extension points

Everything goes through opencda_marl.api; python -m opencda_marl.plugins lists what is registered and from where. The user-facing map of the same seams — contracts, doors, config keys, built-ins — is Architecture; keep the two in step.

you want to… touch
add a learner nothing inside the framework: drop a module into opencda_marl/builtin/algorithms/contrib/ (cloned repo) or ship a package with an opencda_marl.plugins entry point — Add an Algorithm
drive the scenarios from your own RL loop opencda_marl/envs/parallel_env.py
add a planner / agent type / reward calculator / observation extractor / intent provider register_component (opencda_marl.api) in your package; select with scenario.traffic.planner.type, agents.agent_type, MARL.components.<kind>
add a reward / objective / preference profile register_reward_profile / register_objective_profile / register_preference_profile (opencda_marl.api)
invent a new axis register_component("your_kind", "your_key") — unknown kinds are accepted and listed
run mixed autonomy agents.population: {mix: {marl: 0.5, rule_based: 0.5}, seed: N} — shares exact over each window of resolution vehicles, reproducible per seed; the learner commands and learns from its own population only. The mix must name agents.agent_type; on CARLA it is validated at construction (no learning population: rejected; zero learner share: warning; every positive-share name must be a registered agent type, case-sensitive). On SUMO only that population gets the RL runtime mode and a commanded speed; every other population keeps SUMO's safeguards and car-following model
swap the neighbour-context encoder MARL.<algo>.context_encoder: <registered key> (default lstm); a context_encoder component implements forward / forward_padded over the ego-first neighbour sequence
give a vehicle its own driver parameters put them in the spawn event's metadata.driver; the agent factory receives the whole block as config["spawn_metadata"], and SUMO applies the known keys to background vehicles
fix the SUMO frame convention for a map runtime.coordinate_landmarks (or a <net>_landmarks.json / <net>_junction_graph.json beside the network, keyed by junction id as {"12": [x, y]} or {"junctions": {"12": {"centre": [x, y]}}}): junction centres in CARLA's frame decide y_flip; a map without landmarks is read with no y flip, so a CARLA town export needs them
give vehicles per-vehicle objective preferences agents.preference: {mode, sampler, dim, alpha, seed, regime} — the canonical home (moved out of route_intent, where the legacy location still resolves)
choose what one path action means MARL.action_space.path.provider: <registered intent_provider>; the built-in semantic action (left / straight / right / keep) is the framework's example, and selector: learned \| rule_based \| random_legal \| keep_current still names the built-ins
decide at the junction instead of at spawn MARL.action_space.path.decision_zone: {trigger: approach, distance_m: 30}
observe neighbours' committed exit lanes MARL.<algo>.features.committed_exit_lanes: <width> (off by default; the state width changes with it)
add a scenario or profile a YAML under opencda_marl/_bundled/configs/<family>/<scenario>/ stating only what changes — or the same path under your own --marl-config-root
add a traffic recording record mode writes one under artifacts/recordings/; copy it into opencda_marl/_bundled/recordings/ to adopt it as a benchmark — the packaged files are evaluation ground truth
add a monitor chart type opencda_marl/telemetry/static/ (vanilla JS, no build step) against the /api/runs/... endpoints in monitor.py
feed the live traffic view from a new backend implement collect_traffic_view() / collect_traffic_geometry() on the env (opencda_marl/telemetry/traffic_view.py documents the file shapes)

Configs

The families are package data, under opencda_marl/_bundled/configs/: <family>/default.yaml → <family>/<scenario>/base.yaml → <family>/<scenario>/<profile>.yaml, merged by opencda_marl/utils/config_merger.py. Families: marl/ (baselines, tutorials, the RAL-era learners), pcma/ (preference-conditioned speed control on CARLA), momarl/ (the path + speed example family on CARLA), sumo-marl/ (the SUMO equivalents), cosim-marl/, and opencda/ (the classic OpenCDA scenarios for standard mode). Resolution order: explicit roots (--marl-config-root, OPENCDA_MARL_CONFIG_ROOTS), a checkout-level configs/ if one exists, the bundle last. The matrix launcher writes its per-cell profiles under artifacts/_runtime/ and passes that as an extra root. opencda_marl/tools/config_lint.py checks every shipped profile statically (test/basic/test_config_lint.py); test/basic/test_bundled_data.py pins the layout. Next to the configs, opencda_marl/_bundled/checkpoints/ral/ carries the framework paper's baseline weights (eval-only) with a manifest and checksums; load_checkpoint: ral/<run> resolves there through bundled_checkpoint, and test/basic/test_bundled_baselines.py loads every entry through the manager.

Scripts

script role
scripts/verify.py pixi run verify: the repeatable battery — suite, registry, plugins, a real SUMO training run (--full adds CARLA)
scripts/convert_opendrive_maps.py OpenDRIVE → SUMO with netconvert, then checks each network loads and reports its junction/edge/lane/connection counts (--emit-opendrive for the reverse)
scripts/experiments/launch_matrix.py plan and run training cells (parallel slots, per-slot CARLA, resume, partitions)
scripts/experiments/launch_eval_matrix.py, run_matrix_eval.py evaluation of trained cells (two routes)
scripts/experiments/analyze_matrix_results.py, analyze_matrix_eval.py outcome tables
scripts/experiments/launch_distributed_matrix.py, workers.example.yaml several machines
scripts/experiments/matrix_common.py slugs, map profiles (canonical_map / canonical_maps / map_profile: a slug always carries the profile key), condition tables, schema validation shared by the launchers
scripts/docker_carla.py, scripts/docker_train.py Docker helpers
scripts/inspect_traffic_manager.py, scripts/experiments/render_traffic_trace.py traffic / route-trace inspection

Tests

suite covers
test/basic/ config resolution and lint, rewards, replay buffer, map loading, docs boundary, the architecture seal
test/marl/ lifecycle contracts: checkpoints, resume numbering, evaluation close, metrics flush, SUMO reset, telemetry store, traffic view, on-policy rollouts
test/route_intent/ the plugin system (unified registry, cross-kind rollback, freeze), policy contract, strategies, components, route intent, intent learners, SAC/TD3 internals, world snapshot and reload, launcher planning, seeding
test/opencda/ vendored-core behaviour the patches rely on

test/conftest.py puts the repository root on sys.path, forces the Agg backend, disables the metrics store, and unfreezes the plugin registry around each test. Source-scan tests enforce invariants (str-keyed observations, no int(agent_id)).